opera-browser-cli 0.1.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kun Chen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,316 @@
1
+ <h1 align="center">opera-browser-cli</h1>
2
+
3
+ <h3 align="center">The most agent-ergonomic browser automation</h3>
4
+
5
+ `opera-browser-cli` is a fork of [chrome-devtools-axi](https://github.com/kunchenguid/chrome-devtools-axi).
6
+ It wraps [opera-devtools-mcp](https://github.com/operasoftware/opera-devtools-mcp) with an [AXI](https://axi.md)-compliant CLI.
7
+
8
+ - **Token-efficient** — TOON-encoded output cuts token usage ~40% vs raw JSON
9
+ - **Combined operations** — one command navigates, captures, and suggests next steps
10
+ - **Contextual suggestions** — every response includes actionable next-step hints
11
+
12
+ ## Quick Start
13
+
14
+ ```sh
15
+ $ opera-browser-cli open https://example.com
16
+ page: {title: "Example Domain", url: "https://example.com", refs: 1}
17
+ snapshot:
18
+ RootWebArea "Example Domain"
19
+ heading "Example Domain"
20
+ paragraph "This domain is for use in illustrative examples..."
21
+ uid=1 link "More information..."
22
+ help[1]:
23
+ Run `opera-browser-cli click @1` to click the "More information..." link
24
+
25
+ $ opera-browser-cli click @1
26
+ page: {title: "IANA — IANA-Managed Reserved Domains", refs: 12}
27
+ snapshot:
28
+ ...
29
+ ```
30
+
31
+ ## Install
32
+
33
+ Prerequisites: **Node.js >= 20**, **Opera** browser ([Opera Neon](https://www.operaneon.com) recommended for AI features), **[opera-devtools-mcp](https://github.com/operasoftware/opera-devtools-mcp)**.
34
+
35
+ ### From source
36
+
37
+ **1. Build and link `opera-devtools-mcp`:**
38
+
39
+ ```sh
40
+ git clone https://github.com/operasoftware/opera-devtools-mcp
41
+ cd opera-devtools-mcp
42
+ npm install && npm run build && npm link
43
+ ```
44
+
45
+ **2. Build and link `opera-browser-cli`:**
46
+
47
+ ```sh
48
+ # in this repo
49
+ npm install && npm run build && npm link
50
+ ```
51
+
52
+ **3. Run first-time setup:**
53
+
54
+ ```sh
55
+ opera-browser-cli setup
56
+ ```
57
+
58
+ This detects Opera installations, lets you pick one, saves configuration to `~/.opera-browser-cli/config`, and installs the Claude Code skill to `~/.claude/skills/opera-browser-cli/SKILL.md`.
59
+
60
+ Verify:
61
+
62
+ ```sh
63
+ opera-browser-cli --version
64
+ opera-browser-cli open https://example.com
65
+ ```
66
+
67
+ ### Usage examples
68
+
69
+ ```sh
70
+ # Basic navigation
71
+ opera-browser-cli open https://example.com
72
+
73
+ # Use Opera as the browser
74
+ OPERA_CLI_EXECUTABLE_PATH="/Applications/Opera.app/Contents/MacOS/Opera" \
75
+ opera-browser-cli open https://example.com
76
+
77
+ # Headed mode (visible browser window)
78
+ OPERA_CLI_HEADED=1 opera-browser-cli open https://example.com
79
+
80
+ # Persistent profile (stay logged in across sessions)
81
+ OPERA_CLI_USER_DATA_DIR=~/.opera-profile opera-browser-cli open https://example.com
82
+
83
+ # Connect to already-running browser
84
+ OPERA_CLI_BROWSER_URL=http://127.0.0.1:9222 opera-browser-cli open https://example.com
85
+ ```
86
+
87
+ ## How It Works
88
+
89
+ ```
90
+ ┌───────────────────────┐
91
+ │ opera-browser-cli │ CLI — parse args, format output
92
+ └──────────┬────────────┘
93
+ │ HTTP (localhost:9224)
94
+
95
+ ┌───────────────────────┐
96
+ │ Bridge Server │ Persistent process, manages MCP session
97
+ └──────────┬────────────┘
98
+ │ stdio
99
+
100
+ ┌───────────────────────┐
101
+ │ opera-devtools-mcp │ Headless Chrome via DevTools Protocol
102
+ └───────────────────────┘
103
+ ```
104
+
105
+ - **Persistent bridge** — a detached process keeps the MCP session alive across commands, so Chrome doesn't restart every invocation
106
+ - **Auto-lifecycle** — the bridge starts on first command and writes a PID file to `~/.opera-browser-cli/bridge.pid`
107
+ - **Snapshot parsing** — accessibility tree snapshots are extracted and analyzed for interactive elements (`uid=` refs)
108
+ - **TOON encoding** — structured metadata uses [TOON format](https://www.npmjs.com/package/@toon-format/toon) for compact, token-efficient output
109
+
110
+ ## CLI Reference
111
+
112
+ ### Navigation
113
+
114
+ | Command | Description |
115
+ | ----------------- | -------------------------------------------- |
116
+ | `open <url>` | Navigate to URL and snapshot |
117
+ | `snapshot` | Capture current page state |
118
+ | `screenshot <p>` | Save a screenshot to a file |
119
+ | `scroll <dir>` | Scroll: up, down, top, bottom |
120
+ | `back` | Navigate back |
121
+ | `wait <ms\|text>` | Wait for time or text to appear |
122
+ | `eval <js>` | Evaluate a JavaScript expression or function |
123
+ | `run` | Execute a multi-step script from stdin |
124
+
125
+ `eval` wraps plain input as `() => (<expr>)` before sending it to DevTools. For multi-statement logic, pass an arrow function, `function`, or IIFE yourself.
126
+
127
+ ```sh
128
+ opera-browser-cli eval "document.title"
129
+ opera-browser-cli eval "(() => { const rows = [...document.querySelectorAll('tr')]; return rows.map((row) => row.textContent) })()"
130
+ ```
131
+
132
+ ### Interaction
133
+
134
+ | Command | Description |
135
+ | -------------------------- | ------------------------------ |
136
+ | `click @<uid>` | Click an element by ref |
137
+ | `fill @<uid> <text>` | Fill a form field |
138
+ | `type <text>` | Type text at current focus |
139
+ | `press <key>` | Press a keyboard key |
140
+ | `hover @<uid>` | Hover over an element |
141
+ | `drag @<from> @<to>` | Drag an element onto another |
142
+ | `fillform @<uid>=<val>...` | Fill multiple form fields |
143
+ | `dialog <accept\|dismiss>` | Handle a browser dialog |
144
+ | `upload @<uid> <path>` | Upload a file through an input |
145
+
146
+ ### Page Management
147
+
148
+ | Command | Description |
149
+ | ----------------- | --------------------------- |
150
+ | `pages` | List all open tabs |
151
+ | `newpage <url>` | Open a new tab |
152
+ | `selectpage <id>` | Switch to a tab by ID |
153
+ | `closepage <id>` | Close a tab by ID |
154
+ | `resize <w> <h>` | Resize the browser viewport |
155
+
156
+ ### Emulation
157
+
158
+ | Command | Description |
159
+ | --------- | ------------------------------- |
160
+ | `emulate` | Emulate device/network/viewport |
161
+
162
+ ### DevTools Debugging
163
+
164
+ | Command | Description |
165
+ | ------------------ | ------------------------------ |
166
+ | `console` | List console messages |
167
+ | `console-get <id>` | Get a specific console message |
168
+ | `network` | List network requests |
169
+ | `network-get [id]` | Get a specific network request |
170
+
171
+ ### Performance
172
+
173
+ | Command | Description |
174
+ | --------------------------- | ----------------------------- |
175
+ | `lighthouse` | Run a Lighthouse audit |
176
+ | `perf-start` | Start a performance trace |
177
+ | `perf-stop` | Stop the performance trace |
178
+ | `perf-insight <set> <name>` | Analyze a performance insight |
179
+ | `heap <path>` | Capture a heap snapshot |
180
+
181
+ ### Opera AI
182
+
183
+ Requires Opera Neon with an active Opera account.
184
+
185
+ | Command | Description |
186
+ | ------------------- | --------------------------------------------- |
187
+ | `chat <prompt>` | Send a chat message to Opera's built-in AI |
188
+ | `invoke-do <prompt>`| Ask the AI to perform a complex browsing task |
189
+ | `make <prompt>` | Ask the AI to build a webpage or app |
190
+ | `research <prompt>` | Ask the AI to research a topic in depth |
191
+
192
+ `research` accepts `--type local` (default), `--type one-minute`, or `--type deep`.
193
+
194
+ ### Configuration
195
+
196
+ | Command | Description |
197
+ | -------- | ------------------------------------------------ |
198
+ | `setup` | Interactive first-time setup (browser path, etc) |
199
+ | `doctor` | Check configuration and environment |
200
+ | `logs` | Show bridge server logs |
201
+
202
+ ### Bridge
203
+
204
+ | Command | Description |
205
+ | ------- | ----------------------- |
206
+ | `start` | Start the bridge server |
207
+ | `stop` | Stop the bridge server |
208
+
209
+ Running with no command shows the CLI home view. It prepends `bin` and
210
+ `description` metadata, then includes the current snapshot when a browser
211
+ session is active or the no-session status/help block when one is not.
212
+
213
+ ### Flags
214
+
215
+ | Flag | Description |
216
+ | --------------------------- | ------------------------------------------- |
217
+ | `--help` | Show usage information |
218
+ | `-v`, `-V`, `--version` | Show the installed CLI version |
219
+ | `--full` | Show complete output without truncation |
220
+ | `--background` | Open new page in background (newpage) |
221
+ | `--uid @<uid>` | Target a specific element (screenshot) |
222
+ | `--full-page` | Capture entire scrollable page (screenshot) |
223
+ | `--format <fmt>` | Image format: png, jpeg, webp (screenshot) |
224
+ | `--viewport <spec>` | Viewport like "390x844x3,mobile" (emulate) |
225
+ | `--color-scheme <value>` | dark, light, or auto (emulate) |
226
+ | `--network <condition>` | Network throttle: Slow 3G, etc. (emulate) |
227
+ | `--cpu <rate>` | CPU throttling rate 1-20 (emulate) |
228
+ | `--geolocation <lat>x<lon>` | Set geolocation (emulate) |
229
+ | `--user-agent <string>` | Custom user agent (emulate) |
230
+ | `--type <type>` | Filter by type (console, network) |
231
+ | `--limit <n>` | Max items to return (console, network) |
232
+ | `--page <n>` | Pagination (console, network) |
233
+ | `--device <device>` | desktop or mobile (lighthouse) |
234
+ | `--mode <mode>` | navigation or snapshot (lighthouse) |
235
+ | `--output-dir <path>` | Directory for reports (lighthouse) |
236
+ | `--no-reload` | Skip page reload (perf-start) |
237
+ | `--no-auto-stop` | Disable auto-stop (perf-start) |
238
+ | `--file <path>` | Save trace data to file (perf-start/stop) |
239
+ | `--response-file <path>` | Save response body (network-get) |
240
+ | `--request-file <path>` | Save request body (network-get) |
241
+
242
+ ## Configuration
243
+
244
+ | Variable | Default | Purpose |
245
+ | --- | --- | --- |
246
+ | `OPERA_CLI_PORT` | `9224` | Bridge server port |
247
+ | `OPERA_CLI_MCP_BIN` | `opera-devtools-mcp` | MCP server binary |
248
+ | `OPERA_CLI_EXECUTABLE_PATH` | _(system Chrome)_ | Custom browser binary |
249
+ | `OPERA_CLI_BROWSER_URL` | — | Connect to an existing browser instance instead of launching one |
250
+ | `OPERA_CLI_USER_DATA_DIR` | — | Persistent Chrome profile directory (skips isolated mode) |
251
+ | `OPERA_CLI_HEADED` | — | Set to `1` to run in headed (visible) mode |
252
+ | `OPERA_CLI_CHROME_ARGS` | — | Extra Chrome flags, space-separated |
253
+ | `OPERA_CLI_DISABLE_HOOKS` | — | Set to `1` to skip auto-installing session hooks |
254
+
255
+ State is stored in `~/.opera-browser-cli/`:
256
+
257
+ | File | Purpose |
258
+ | ------------ | ---------------------------------- |
259
+ | `bridge.pid` | PID and port of the running bridge |
260
+
261
+ ### Session Hooks
262
+
263
+ On supported agents, the packaged CLI also installs a `SessionStart` hook in `~/.claude/settings.json` and `~/.codex/hooks.json`, and enables `codex_hooks` in `~/.codex/config.toml`.
264
+
265
+ Set `OPERA_CLI_DISABLE_HOOKS=1` to skip that auto-install behavior.
266
+
267
+ Development entrypoints such as `npm run dev` and `bin/opera-browser-cli.ts` do not modify those hook files.
268
+
269
+ ## Local Setup (Full Stack)
270
+
271
+ Both `opera-devtools-mcp` and `opera-browser-cli` need to be built and linked so they're available in PATH.
272
+
273
+ **1. Build and link `opera-devtools-mcp`:**
274
+
275
+ ```sh
276
+ # in the opera-devtools-mcp repo
277
+ npm install
278
+ npm run build
279
+ npm link
280
+ ```
281
+
282
+ **2. Build and link `opera-browser-cli`:**
283
+
284
+ ```sh
285
+ # in this repo
286
+ npm install
287
+ npm run build
288
+ npm link
289
+ ```
290
+
291
+ **3. Set the browser executable path:**
292
+
293
+ ```sh
294
+ export OPERA_CLI_EXECUTABLE_PATH="/Applications/Opera Neon Developer.app/Contents/MacOS/Opera"
295
+ ```
296
+
297
+ **Tip:** Set `OPERA_CLI_MCP_BIN` to point to the locally linked `opera-devtools-mcp`:
298
+
299
+ ```sh
300
+ export OPERA_CLI_MCP_BIN=opera-devtools-mcp
301
+ ```
302
+
303
+ **Tip:** Set `OPERA_CLI_HEADED=1` to launch the browser in headed (visible) mode — useful during development to watch what's happening:
304
+
305
+ ```sh
306
+ export OPERA_CLI_HEADED=1
307
+ ```
308
+
309
+ ## Development
310
+
311
+ ```sh
312
+ npm run build # Compile TypeScript to dist/
313
+ npm run dev # Run CLI directly with tsx
314
+ npm test # Run tests with vitest
315
+ npm run test:watch # Run tests in watch mode
316
+ ```
package/SKILL.md ADDED
@@ -0,0 +1,19 @@
1
+ ---
2
+ name: opera-browser-cli
3
+ description: Browser automation and web interaction using the opera-browser-cli tool. Use for navigating pages, clicking elements, filling forms, taking screenshots, inspecting console/network, running performance audits, and Opera Neon AI features (chat, invoke-do, make, research) when Opera Neon is the active browser.
4
+ ---
5
+
6
+ # Skill: opera-browser-cli Browser Automation
7
+
8
+ `opera-browser-cli` controls a Opera browser browser session.
9
+
10
+ - **Standard commands** (`open`, `click`, `fill`, `screenshot`, etc.) — work with any Opera browser session.
11
+ - **Opera AI commands** (`chat`, `invoke-do`, `make`, `research`) — require **Opera Neon** with an active sign-in.
12
+
13
+ Run `opera-browser-cli --help` for the full command list, or `opera-browser-cli <command> --help` for per-command flags and examples.
14
+
15
+ ```bash
16
+ opera-browser-cli open https://example.com # start here — navigate and snapshot the page
17
+ ```
18
+
19
+ If a user hits `Opera Neon: user is not signed in` or wants to use AI commands, suggest they run `opera-browser-cli setup` (interactive wizard) and sign in to Opera Neon. Run `opera-browser-cli doctor` to diagnose configuration issues.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env tsx
2
+ export {};
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env tsx
2
+ import { getErrorMessage, runBridge } from "../src/bridge.js";
3
+ runBridge().catch((error) => {
4
+ process.stderr.write(`[opera-browser-cli] Fatal: ${getErrorMessage(error)}\n`);
5
+ process.exit(1);
6
+ });
7
+ //# sourceMappingURL=opera-browser-cli-bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opera-browser-cli-bridge.js","sourceRoot":"","sources":["../../bin/opera-browser-cli-bridge.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE9D,SAAS,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8BAA8B,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { main } from '../src/cli.js';
3
+ main(process.argv.slice(2));
4
+ //# sourceMappingURL=opera-browser-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opera-browser-cli.js","sourceRoot":"","sources":["../../bin/opera-browser-cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAErC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env tsx
2
+ export {};
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env tsx
2
+ import { getErrorMessage, runBridge } from "../src/bridge.js";
3
+ runBridge().catch((error) => {
4
+ process.stderr.write(`[opera-cli] Fatal: ${getErrorMessage(error)}\n`);
5
+ process.exit(1);
6
+ });
7
+ //# sourceMappingURL=opera-cli-bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opera-cli-bridge.js","sourceRoot":"","sources":["../../bin/opera-cli-bridge.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE9D,SAAS,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sBAAsB,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACvE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { main } from '../src/cli.js';
3
+ main(process.argv.slice(2));
4
+ //# sourceMappingURL=opera-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opera-cli.js","sourceRoot":"","sources":["../../bin/opera-cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAErC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Persistent MCP bridge server for opera-browser-cli.
3
+ *
4
+ * Spawns opera-devtools-mcp as a child process and maintains a single
5
+ * persistent MCP session. Exposes a simple HTTP API:
6
+ * POST /call { name, args } → { result }
7
+ * GET /tools → [{ name, description }]
8
+ * GET /health → { status: "ok" }
9
+ *
10
+ * Writes a PID file to ~/.opera-browser-cli/bridge.pid on startup.
11
+ */
12
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
13
+ import type { RequestOptions } from "@modelcontextprotocol/sdk/shared/protocol.js";
14
+ import { type IncomingMessage, type Server, type ServerResponse } from "node:http";
15
+ export interface BridgeContentBlock {
16
+ type: string;
17
+ text?: string;
18
+ }
19
+ export interface BridgeCallPayload {
20
+ name: string;
21
+ args: Record<string, unknown>;
22
+ }
23
+ interface BridgeToolDescription {
24
+ name: string;
25
+ description?: string;
26
+ }
27
+ export interface BridgeClient {
28
+ listTools(): Promise<{
29
+ tools: BridgeToolDescription[];
30
+ }>;
31
+ callTool(request: {
32
+ name: string;
33
+ arguments: Record<string, unknown>;
34
+ }, resultSchema?: unknown, options?: RequestOptions): Promise<unknown>;
35
+ close(): Promise<void>;
36
+ }
37
+ export declare function isBridgeClientConnected(client: BridgeClient): Promise<boolean>;
38
+ export declare function getErrorMessage(error: unknown): string;
39
+ export declare function extractToolText(content: BridgeContentBlock[]): string;
40
+ export declare function parseBridgeCallPayload(body: string): BridgeCallPayload;
41
+ export declare function resolveBridgeScript(importMetaDir: string): string;
42
+ export declare function handleBridgeRequest(client: BridgeClient, req: IncomingMessage, res: ServerResponse, captureNextId?: () => Promise<string>): Promise<void>;
43
+ export declare function createBridgeServer(client: BridgeClient, captureNextId?: () => Promise<string>): Server;
44
+ export declare function buildTransportArgs(): string[];
45
+ /**
46
+ * Wraps transport.send to intercept outgoing JSON-RPC request IDs so each
47
+ * streaming callTool call can register its own log writer before the first
48
+ * notification arrives.
49
+ *
50
+ * INVARIANT: The MCP SDK's Client.request() calls transport.send() synchronously
51
+ * inside its Promise constructor (see @modelcontextprotocol/sdk shared/protocol.js).
52
+ * This means callTool() triggers transport.send before yielding, allowing us to
53
+ * capture the ID before any concurrent handler can interleave.
54
+ * Verify this invariant when upgrading @modelcontextprotocol/sdk.
55
+ */
56
+ export declare function wrapTransportForIdCapture(transport: StdioClientTransport): () => Promise<string>;
57
+ export declare function runBridge(port?: number): Promise<void>;
58
+ export {};