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.
@@ -0,0 +1,62 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ import { computeCodexConfigUpdate as computeAxiCodexConfigUpdate, computeSessionStartHookUpdate, installSessionStartHooks, shouldInstallHooksForNodeAxiExecPath, } from "axi-sdk-js";
4
+ const HOOK_MARKER = "opera-browser-cli";
5
+ /**
6
+ * Only install hooks from packaged or installed entrypoints.
7
+ * Development TypeScript entrypoints should not self-register.
8
+ */
9
+ export function shouldInstallHooksForExecPath(execPath) {
10
+ return shouldInstallHooksForNodeAxiExecPath(execPath, {
11
+ marker: HOOK_MARKER,
12
+ binaryNames: [HOOK_MARKER],
13
+ distEntrypoints: ["dist/bin/opera-browser-cli.js"],
14
+ });
15
+ }
16
+ /**
17
+ * Returns hook installation targets for supported agents.
18
+ */
19
+ export function getHookTargets() {
20
+ const home = homedir();
21
+ return [
22
+ { path: join(home, ".claude", "settings.json") },
23
+ { path: join(home, ".codex", "hooks.json") },
24
+ { path: join(home, ".codex", "config.toml") },
25
+ ];
26
+ }
27
+ /**
28
+ * Pure function: compute the hook update for agent settings.
29
+ * Works for both Claude Code (settings.json) and Codex CLI (hooks.json).
30
+ * Returns [updatedSettings, changed].
31
+ */
32
+ export function computeHookUpdate(settings, execPath) {
33
+ return computeSessionStartHookUpdate(settings, {
34
+ marker: HOOK_MARKER,
35
+ command: execPath,
36
+ timeoutSeconds: 10,
37
+ });
38
+ }
39
+ /**
40
+ * Pure function: ensure Codex hooks are enabled in config.toml.
41
+ * Returns [updatedToml, changed].
42
+ */
43
+ export function computeCodexConfigUpdate(content) {
44
+ return computeAxiCodexConfigUpdate(content);
45
+ }
46
+ /**
47
+ * Idempotently install session hooks into all supported agents.
48
+ * Silently does nothing on any error.
49
+ */
50
+ export function installHooks() {
51
+ try {
52
+ installSessionStartHooks({
53
+ marker: HOOK_MARKER,
54
+ timeoutSeconds: 10,
55
+ shouldInstall: shouldInstallHooksForExecPath,
56
+ });
57
+ }
58
+ catch {
59
+ // Best-effort — never fail the CLI over hook installation
60
+ }
61
+ }
62
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EACL,wBAAwB,IAAI,2BAA2B,EACvD,6BAA6B,EAC7B,wBAAwB,EACxB,oCAAoC,GACrC,MAAM,YAAY,CAAC;AAyBpB,MAAM,WAAW,GAAG,mBAAmB,CAAC;AAExC;;;GAGG;AACH,MAAM,UAAU,6BAA6B,CAAC,QAAgB;IAC5D,OAAO,oCAAoC,CAAC,QAAQ,EAAE;QACpD,MAAM,EAAE,WAAW;QACnB,WAAW,EAAE,CAAC,WAAW,CAAC;QAC1B,eAAe,EAAE,CAAC,+BAA+B,CAAC;KACnD,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,OAAO;QACL,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,eAAe,CAAC,EAAE;QAChD,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,YAAY,CAAC,EAAE;QAC5C,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,aAAa,CAAC,EAAE;KAC9C,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAAsB,EACtB,QAAgB;IAEhB,OAAO,6BAA6B,CAAC,QAAQ,EAAE;QAC7C,MAAM,EAAE,WAAW;QACnB,OAAO,EAAE,QAAQ;QACjB,cAAc,EAAE,EAAE;KACnB,CAA4B,CAAC;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAe;IACtD,OAAO,2BAA2B,CAAC,OAAO,CAAC,CAAC;AAC9C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY;IAC1B,IAAI,CAAC;QACH,wBAAwB,CAAC;YACvB,MAAM,EAAE,WAAW;YACnB,cAAc,EAAE,EAAE;YAClB,aAAa,EAAE,6BAA6B;SAC7C,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,0DAA0D;IAC5D,CAAC;AACH,CAAC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Script runner for `opera-browser-cli run`.
3
+ *
4
+ * Reads a script from stdin, provides a minimal `page` global, and executes it.
5
+ * Only the script's own console.log output is visible to the caller.
6
+ */
7
+ type CallTool = (name: string, args?: Record<string, unknown>) => Promise<string>;
8
+ /** Extract the actual JS value from MCP evaluate_script response wrapper. */
9
+ export declare function parseEvalOutput(output: string): unknown;
10
+ /** Returns true when the string looks like a @uid ref (e.g. "@12", "26_181"). */
11
+ export declare function isUidRef(s: string): boolean;
12
+ export interface OpenResult {
13
+ url: string;
14
+ status: number | null;
15
+ }
16
+ export interface PageHelper {
17
+ open(url: string): Promise<OpenResult>;
18
+ eval(jsOrFn: string | ((...args: unknown[]) => unknown)): Promise<unknown>;
19
+ wait(ms: number): Promise<void>;
20
+ wait(selector: string, timeout?: number): Promise<void>;
21
+ snapshot(): Promise<string>;
22
+ click(refOrSelector: string): Promise<void>;
23
+ fill(refOrSelector: string, text: string): Promise<void>;
24
+ type(text: string): Promise<void>;
25
+ press(key: string): Promise<void>;
26
+ back(): Promise<void>;
27
+ }
28
+ export declare function createPageHelper(callTool: CallTool): PageHelper;
29
+ export interface RunResult {
30
+ stdout: string;
31
+ }
32
+ /** Read all of stdin into a string. */
33
+ export declare function readStdin(): Promise<string>;
34
+ export declare function runScript(content: string, callTool: CallTool): Promise<RunResult>;
35
+ export {};
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Script runner for `opera-browser-cli run`.
3
+ *
4
+ * Reads a script from stdin, provides a minimal `page` global, and executes it.
5
+ * Only the script's own console.log output is visible to the caller.
6
+ */
7
+ import { mkdtempSync, writeFileSync, unlinkSync, rmdirSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { tmpdir } from "node:os";
10
+ import { CdpError } from "./client.js";
11
+ // --- Value parsing ---
12
+ /** Extract the actual JS value from MCP evaluate_script response wrapper. */
13
+ export function parseEvalOutput(output) {
14
+ const jsonBlock = output.match(/```json\n([\s\S]*?)\n```/);
15
+ if (jsonBlock) {
16
+ try {
17
+ return JSON.parse(jsonBlock[1].trim());
18
+ }
19
+ catch {
20
+ return jsonBlock[1].trim();
21
+ }
22
+ }
23
+ const preamble = "Script ran on page and returned:";
24
+ if (output.includes(preamble)) {
25
+ const raw = output.slice(output.indexOf(preamble) + preamble.length).trim();
26
+ try {
27
+ return JSON.parse(raw);
28
+ }
29
+ catch {
30
+ return raw;
31
+ }
32
+ }
33
+ return output.trim();
34
+ }
35
+ /** Strip MCP preamble/headers from snapshot text, returning just the accessibility tree. */
36
+ function stripSnapshotHeader(text) {
37
+ const lines = text.split("\n");
38
+ const treeStart = lines.findIndex((l) => /\bRootWebArea\b|\buid=/.test(l));
39
+ if (treeStart > 0)
40
+ return lines.slice(treeStart).join("\n");
41
+ return text.replace(/^[\s\S]*?##\s+Latest page snapshot\s*\n/, "");
42
+ }
43
+ /** Strip leading @ from uid ref string. */
44
+ function parseUid(ref) {
45
+ return ref.startsWith("@") ? ref.slice(1) : ref;
46
+ }
47
+ /** Check if an open error is recoverable by falling back to new_page. */
48
+ function isRecoverableOpenError(error) {
49
+ if (!(error instanceof CdpError))
50
+ return false;
51
+ if (error.code !== "BROWSER_ERROR")
52
+ return false;
53
+ return /not connected|session (?:closed|not found)|no page/i.test(error.message);
54
+ }
55
+ // --- Selector detection ---
56
+ const UID_RE = /^@?\d[\d_]*$/;
57
+ /** Returns true when the string looks like a @uid ref (e.g. "@12", "26_181"). */
58
+ export function isUidRef(s) {
59
+ return UID_RE.test(s);
60
+ }
61
+ const DEFAULT_WAIT_TIMEOUT = 30_000;
62
+ export function createPageHelper(callTool) {
63
+ /** Run JS in the page and return the parsed value. */
64
+ async function evalJs(code) {
65
+ const output = await callTool("evaluate_script", { function: code });
66
+ return parseEvalOutput(output);
67
+ }
68
+ return {
69
+ async open(url) {
70
+ if (!url) {
71
+ throw new CdpError("Missing URL", "VALIDATION_ERROR", [
72
+ 'Start with `await page.open("https://example.com")`',
73
+ ]);
74
+ }
75
+ try {
76
+ await callTool("navigate_page", { type: "url", url });
77
+ }
78
+ catch (error) {
79
+ if (!isRecoverableOpenError(error))
80
+ throw error;
81
+ await callTool("new_page", { url });
82
+ }
83
+ const info = await evalJs(`() => ({ url: location.href, status: performance.getEntriesByType('navigation').pop()?.responseStatus ?? null })`);
84
+ const result = info;
85
+ return {
86
+ url: String(result?.url ?? url),
87
+ status: typeof result?.status === "number" ? result.status : null,
88
+ };
89
+ },
90
+ async eval(jsOrFn) {
91
+ const fn = typeof jsOrFn === "function"
92
+ ? String(jsOrFn)
93
+ : `() => (${jsOrFn.trim()})`;
94
+ return evalJs(fn);
95
+ },
96
+ async wait(msOrSelector, timeout) {
97
+ if (typeof msOrSelector === "number") {
98
+ await callTool("evaluate_script", {
99
+ function: `new Promise(r => setTimeout(r, ${msOrSelector}))`,
100
+ });
101
+ }
102
+ else {
103
+ const ms = timeout ?? DEFAULT_WAIT_TIMEOUT;
104
+ const sel = JSON.stringify(msOrSelector);
105
+ await callTool("evaluate_script", {
106
+ function: `new Promise((resolve, reject) => {
107
+ const sel = ${sel};
108
+ if (document.querySelector(sel)) { resolve(); return; }
109
+ const observer = new MutationObserver(() => {
110
+ if (document.querySelector(sel)) {
111
+ observer.disconnect();
112
+ clearTimeout(timer);
113
+ resolve();
114
+ }
115
+ });
116
+ const timer = setTimeout(() => {
117
+ observer.disconnect();
118
+ reject(new Error('Timeout waiting for: ' + sel));
119
+ }, ${ms});
120
+ observer.observe(document.body, { childList: true, subtree: true, attributes: true });
121
+ })`,
122
+ });
123
+ }
124
+ },
125
+ async snapshot() {
126
+ const result = await callTool("take_snapshot");
127
+ return stripSnapshotHeader(result);
128
+ },
129
+ async click(refOrSelector) {
130
+ if (isUidRef(refOrSelector)) {
131
+ await callTool("click", { uid: parseUid(refOrSelector) });
132
+ }
133
+ else {
134
+ const sel = JSON.stringify(refOrSelector);
135
+ await callTool("evaluate_script", {
136
+ function: `(() => {
137
+ const el = document.querySelector(${sel});
138
+ if (!el) throw new Error('Element not found: ' + ${sel});
139
+ el.scrollIntoView({ block: 'center' });
140
+ el.click();
141
+ })()`,
142
+ });
143
+ }
144
+ },
145
+ async fill(refOrSelector, text) {
146
+ if (isUidRef(refOrSelector)) {
147
+ await callTool("fill", { uid: parseUid(refOrSelector), value: text });
148
+ }
149
+ else {
150
+ const sel = JSON.stringify(refOrSelector);
151
+ const val = JSON.stringify(text);
152
+ await callTool("evaluate_script", {
153
+ function: `(() => {
154
+ const el = document.querySelector(${sel});
155
+ if (!el) throw new Error('Element not found: ' + ${sel});
156
+ el.focus();
157
+ el.value = ${val};
158
+ el.dispatchEvent(new Event('input', { bubbles: true }));
159
+ el.dispatchEvent(new Event('change', { bubbles: true }));
160
+ })()`,
161
+ });
162
+ }
163
+ },
164
+ async type(text) {
165
+ await callTool("type_text", { text });
166
+ },
167
+ async press(key) {
168
+ await callTool("press_key", { key });
169
+ },
170
+ async back() {
171
+ await callTool("navigate_page", { type: "back" });
172
+ },
173
+ };
174
+ }
175
+ /** Read all of stdin into a string. */
176
+ export async function readStdin() {
177
+ const chunks = [];
178
+ for await (const chunk of process.stdin) {
179
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
180
+ }
181
+ return Buffer.concat(chunks).toString("utf-8");
182
+ }
183
+ export async function runScript(content, callTool) {
184
+ const page = createPageHelper(callTool);
185
+ // Write to a temp .mjs so dynamic import supports top-level await
186
+ const tmpDir = mkdtempSync(join(tmpdir(), "cda-run-"));
187
+ const tmpFile = join(tmpDir, "script.mjs");
188
+ writeFileSync(tmpFile, content, "utf-8");
189
+ // Capture console.log output from the script
190
+ const lines = [];
191
+ const origLog = console.log;
192
+ const captureLog = (...args) => {
193
+ lines.push(args.map(String).join(" "));
194
+ };
195
+ // Inject page global and capture console
196
+ const prevPage = globalThis.page;
197
+ globalThis.page = page;
198
+ console.log = captureLog;
199
+ try {
200
+ const mod = await import(tmpFile);
201
+ // Support optional default export function
202
+ if (typeof mod.default === "function") {
203
+ await mod.default();
204
+ }
205
+ }
206
+ finally {
207
+ console.log = origLog;
208
+ if (prevPage === undefined) {
209
+ delete globalThis.page;
210
+ }
211
+ else {
212
+ globalThis.page = prevPage;
213
+ }
214
+ // Clean up temp file
215
+ try {
216
+ unlinkSync(tmpFile);
217
+ rmdirSync(tmpDir);
218
+ }
219
+ catch {
220
+ /* best effort */
221
+ }
222
+ }
223
+ const stdout = lines.length > 0 ? lines.join("\n") + "\n" : "";
224
+ return { stdout };
225
+ }
226
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/run.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ export interface RefInfo {
2
+ ref: string;
3
+ label: string;
4
+ type: string;
5
+ }
6
+ /** Count interactive refs (uid=...) in snapshot text. */
7
+ export declare function countRefs(snapshot: string): number;
8
+ /** Extract ref IDs with labels and types from snapshot text. */
9
+ export declare function extractRefs(snapshot: string): RefInfo[];
10
+ /** Extract page title from snapshot (RootWebArea or first heading). */
11
+ export declare function extractTitle(snapshot: string): string;
12
+ export interface TruncationResult {
13
+ text: string;
14
+ truncated: boolean;
15
+ totalLength: number;
16
+ }
17
+ export declare function truncateSnapshot(snapshot: string, full: boolean, limit?: number): TruncationResult;
18
+ export declare function truncateText(text: string, limit?: number): TruncationResult;
19
+ /** Check if a ref type is an input/form field. */
20
+ export declare function isInputType(type: string): boolean;
@@ -0,0 +1,69 @@
1
+ /** Count interactive refs (uid=...) in snapshot text. */
2
+ export function countRefs(snapshot) {
3
+ const matches = snapshot.match(/\buid=\S+/g);
4
+ return matches ? matches.length : 0;
5
+ }
6
+ /** Extract ref IDs with labels and types from snapshot text. */
7
+ export function extractRefs(snapshot) {
8
+ const refs = [];
9
+ for (const line of snapshot.split("\n")) {
10
+ const m = line.match(/\buid=(\S+)\s+(\w+)\s+"([^"]*)"/);
11
+ if (!m)
12
+ continue;
13
+ refs.push({ ref: m[1], type: m[2], label: m[3] });
14
+ }
15
+ return refs;
16
+ }
17
+ /** Extract page title from snapshot (RootWebArea or first heading). */
18
+ export function extractTitle(snapshot) {
19
+ const rootMatch = snapshot.match(/RootWebArea\s+"([^"]+)"/);
20
+ if (rootMatch)
21
+ return rootMatch[1];
22
+ const headingMatch = snapshot.match(/\bheading\s+"([^"]+)"/);
23
+ if (headingMatch)
24
+ return headingMatch[1];
25
+ return "";
26
+ }
27
+ export function truncateSnapshot(snapshot, full, limit = 16000) {
28
+ const totalLength = snapshot.length;
29
+ if (full || totalLength <= limit) {
30
+ return { text: snapshot, truncated: false, totalLength };
31
+ }
32
+ const cut = snapshot.lastIndexOf("\n", limit);
33
+ const text = cut > 0 ? snapshot.slice(0, cut) : snapshot.slice(0, limit);
34
+ return { text, truncated: true, totalLength };
35
+ }
36
+ /**
37
+ * Truncate arbitrary text keeping both head and tail so recent/trailing data is preserved.
38
+ * Used for eval output where the end of the result is often as important as the beginning.
39
+ */
40
+ const MARKER_OVERHEAD = 50;
41
+ export function truncateText(text, limit = 8000) {
42
+ const totalLength = text.length;
43
+ if (totalLength <= limit) {
44
+ return { text, truncated: false, totalLength };
45
+ }
46
+ // The omission marker adds overhead; skip truncation when
47
+ // the text is short enough that truncating would produce a longer result.
48
+ if (totalLength <= limit + MARKER_OVERHEAD) {
49
+ return { text, truncated: false, totalLength };
50
+ }
51
+ const headBudget = Math.floor(limit * 0.4);
52
+ const tailBudget = limit - headBudget;
53
+ // Cut at line boundaries when possible
54
+ const headCut = text.lastIndexOf("\n", headBudget);
55
+ const head = headCut > 0 ? text.slice(0, headCut) : text.slice(0, headBudget);
56
+ const tailStart = text.indexOf("\n", totalLength - tailBudget);
57
+ const tail = tailStart > 0 && tailStart < totalLength
58
+ ? text.slice(tailStart + 1)
59
+ : text.slice(totalLength - tailBudget);
60
+ const omitted = totalLength - head.length - tail.length;
61
+ const result = `${head}\n\n... (${omitted} chars omitted, ${totalLength} total) ...\n\n${tail}`;
62
+ return { text: result, truncated: true, totalLength };
63
+ }
64
+ const INPUT_TYPES = ["textbox", "searchbox", "input", "combobox", "textarea"];
65
+ /** Check if a ref type is an input/form field. */
66
+ export function isInputType(type) {
67
+ return INPUT_TYPES.includes(type);
68
+ }
69
+ //# sourceMappingURL=snapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/snapshot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export interface SuggestionContext {
2
+ command: string;
3
+ url?: string;
4
+ snapshot?: string;
5
+ }
6
+ export declare function getSuggestions(ctx: SuggestionContext): string[];
@@ -0,0 +1,51 @@
1
+ import { extractRefs, isInputType } from "./snapshot.js";
2
+ export function getSuggestions(ctx) {
3
+ // Commands without auto-snapshot — suggest viewing page state
4
+ if (ctx.command === "wait" || ctx.command === "eval") {
5
+ return ["Run `opera-browser-cli snapshot` to see current page state"];
6
+ }
7
+ const refs = ctx.snapshot ? extractRefs(ctx.snapshot) : [];
8
+ const links = refs.filter((r) => r.type === "link");
9
+ const buttons = refs.filter((r) => r.type === "button");
10
+ const inputs = refs.filter((r) => isInputType(r.type));
11
+ const lines = [];
12
+ // After filling a field, suggest submitting
13
+ if (ctx.command === "fill") {
14
+ const submitBtn = buttons.find((r) => /submit|search|go|send|login|sign|ok/i.test(r.label));
15
+ if (submitBtn) {
16
+ lines.push(`Run \`opera-browser-cli click @${submitBtn.ref}\` to click "${submitBtn.label}"`);
17
+ }
18
+ else {
19
+ lines.push("Run `opera-browser-cli press Enter` to submit the form");
20
+ }
21
+ }
22
+ // Suggest filling inputs (unless we just filled one)
23
+ if (inputs.length > 0 && ctx.command !== "fill") {
24
+ const inp = inputs[0];
25
+ const label = inp.label ? `the "${inp.label}" field` : "the input field";
26
+ lines.push(`Run \`opera-browser-cli fill @${inp.ref} "text"\` to fill ${label}`);
27
+ }
28
+ // Suggest clicking buttons
29
+ if (buttons.length > 0) {
30
+ const btn = ctx.command === "fill"
31
+ ? (buttons.find((r) => !/submit|search|go|send|login|sign|ok/i.test(r.label)) ?? buttons[0])
32
+ : buttons[0];
33
+ if (btn && !lines.some((l) => l.includes(`@${btn.ref}`))) {
34
+ const label = btn.label ? `"${btn.label}" ` : "";
35
+ lines.push(`Run \`opera-browser-cli click @${btn.ref}\` to click the ${label}button`);
36
+ }
37
+ }
38
+ // Suggest clicking links
39
+ if (links.length > 0) {
40
+ const link = links[0];
41
+ lines.push(`Run \`opera-browser-cli click @${link.ref}\` to click the "${link.label}" link`);
42
+ }
43
+ // Suggest scrolling if page has many elements
44
+ if (refs.length > 5) {
45
+ lines.push("Run `opera-browser-cli scroll down` to scroll down");
46
+ }
47
+ // Teach eval syntax — use IIFE for multi-statement logic
48
+ lines.push('Use `opera-browser-cli eval <expr>` for JS expressions. For multi-statement code, wrap in an IIFE: `eval "(() => { ...; return result })()"`');
49
+ return lines;
50
+ }
51
+ //# sourceMappingURL=suggestions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggestions.js","sourceRoot":"","sources":["../../src/suggestions.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "opera-browser-cli",
3
+ "version": "0.1.27",
4
+ "description": "AXI-compliant opera-devtools-mcp wrapper — combined operations, TOON output, contextual suggestions",
5
+ "type": "module",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/operasoftware/opera-browser-cli.git"
9
+ },
10
+ "keywords": [
11
+ "chrome",
12
+ "devtools",
13
+ "browser",
14
+ "cli",
15
+ "agent",
16
+ "axi",
17
+ "toon",
18
+ "mcp"
19
+ ],
20
+ "bin": {
21
+ "opera-browser-cli": "dist/bin/opera-browser-cli.js"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "LICENSE",
26
+ "README.md",
27
+ "SKILL.md"
28
+ ],
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "scripts": {
33
+ "build": "tsc && chmod +x dist/bin/opera-browser-cli.js",
34
+ "dev": "tsx bin/opera-cli.ts",
35
+ "test": "vitest run",
36
+ "test:watch": "vitest",
37
+ "prepublishOnly": "npm run build"
38
+ },
39
+ "license": "MIT",
40
+ "engines": {
41
+ "node": ">=20"
42
+ },
43
+ "dependencies": {
44
+ "@modelcontextprotocol/sdk": "^1.12.1",
45
+ "@toon-format/toon": "^2.1.0",
46
+ "axi-sdk-js": "^0.1.4"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "^22.0.0",
50
+ "tsx": "^4.0.0",
51
+ "typescript": "^5.7.0",
52
+ "vitest": "^3.0.0"
53
+ }
54
+ }