@pxlarified/browser 0.1.0 → 1.9.4

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/README.md CHANGED
@@ -1,151 +1,232 @@
1
- # OpenBrowser
1
+ <h1 align="center">
2
+ <br>
3
+ OpenBrowser
4
+ <br>
5
+ </h1>
2
6
 
3
- OpenBrowser is a minimal browser-control skill and CLI. It controls one browser tab that it creates and owns, using a browser extension plus a local, user-scoped native bridge.
7
+ <h4 align="center">A minimal local browser-control CLI and agent skill that drives one owned browser tab through a WebExtension bridge.</h4>
4
8
 
5
- The npm package is intended to be published as `@pxlarified/browser` and used with:
9
+ <p align="center">
10
+ <img alt="Node.js" src="https://img.shields.io/badge/Node.js-%3E%3D20-339933?style=flat-square&logo=node.js&logoColor=white">
11
+ <img alt="Version" src="https://img.shields.io/badge/version-1.9.4-blue?style=flat-square">
12
+ <img alt="Browser" src="https://img.shields.io/badge/browser-Zen-7F52FF?style=flat-square">
13
+ <img alt="Firefox Extension" src="https://img.shields.io/badge/Firefox-WebExtension-FF7139?style=flat-square&logo=firefoxbrowser&logoColor=white">
14
+ </p>
6
15
 
7
- ```bash
8
- npx @pxlarified/browser <command>
9
- ```
10
-
11
- The OpenBrowser skill lives in `skills/OpenBrowser/` and is not included as an npm-published skill package.
16
+ ## Overview
12
17
 
13
- ## Status
18
+ OpenBrowser is a JavaScript browser-control package for local automation. It provides a CLI named `OpenBrowser`, a browser extension, and a user-scoped native bridge so tools and agents can control a single browser tab without exposing a public browser-control server.
14
19
 
15
- - Initial browser support: Zen, a Firefox-based browser.
16
- - Extension artifact for Firefox-based browsers: `.xpi`.
17
- - Chromium support is intentionally adapter-ready but not implemented yet.
18
- - The default build creates an unsigned development `.xpi`.
19
- - Firefox release signing is implemented as a separate `web-ext sign --channel unlisted` step.
20
+ OpenBrowser is intentionally conservative. Each supported browser may have at most one active OpenBrowser session, and that session is exactly one tab created and owned by OpenBrowser. It never closes, reuses, inspects, navigates, or modifies tabs opened manually by the user.
20
21
 
21
- ## Security model
22
+ The npm package is published as `@pxlarified/browser` and is intended to be used through `npx`.
22
23
 
23
- The CLI does not expose browser control through a public network service. The extension talks to a native messaging host, and the CLI talks to that host through a user-scoped local bridge:
24
+ ```sh
25
+ npx @pxlarified/browser <command>
26
+ ```
24
27
 
25
- - Unix/macOS: `~/OpenBrowser/bridge/<browser>.sock` with user-only permissions.
26
- - Windows: a user-local named pipe.
28
+ The local agent skill lives in `skills/OpenBrowser/` and is not published as a separate npm skill package.
27
29
 
28
- ## Session model
30
+ ## Features
29
31
 
30
- Each supported browser may have at most one active OpenBrowser session.
32
+ - Provides a minimal `OpenBrowser` CLI for browser session lifecycle, navigation, inspection, screenshots, scrolling, and interaction.
33
+ - Controls exactly one OpenBrowser-owned tab per supported browser.
34
+ - Tracks the owned browser tab ID in extension storage and automatically clears the session if the user closes the tab.
35
+ - Communicates through a local, user-scoped native bridge instead of a publicly accessible network service.
36
+ - Uses temporary OpenBrowser-generated element references such as `e_1`, `e_2`, and `e_3`.
37
+ - Invalidates references after navigation or relevant DOM updates and returns stale-reference errors for old refs.
38
+ - Saves screenshots to `~/OpenBrowser/screenshots/<uuid>.png` or returns Base64 image data.
39
+ - Includes an extensible browser-adapter architecture for future Firefox-family and Chromium-family browsers.
40
+ - Initially supports Zen, a Firefox-based browser.
41
+ - Includes a minimal extension logo and uses it as the Firefox extension icon.
42
+ - Builds the Firefox extension into a bundled `.xpi` artifact.
43
+ - Includes a release-signing pipeline for Mozilla unlisted signing with `web-ext sign --channel unlisted`.
31
44
 
32
- - A session is exactly one browser tab created by OpenBrowser.
33
- - `open` fails if a session already exists for that browser.
34
- - `close` closes only the OpenBrowser-owned tab.
35
- - OpenBrowser never closes, reuses, inspects, navigates, or modifies tabs opened manually by the user.
36
- - If the user manually closes the owned tab, the session is automatically considered closed.
45
+ ## Requirements
37
46
 
38
- ## Install and build
47
+ - Node.js 20 or newer.
48
+ - npm access to install or run `@pxlarified/browser`.
49
+ - Zen Browser for the initial supported browser target.
50
+ - A Zen profile created on disk. Open Zen once before running the installer.
51
+ - For signed Firefox releases, Mozilla Add-ons API credentials.
39
52
 
40
- ```bash
41
- npm install
42
- npm run build
43
- ```
53
+ ## Installation
44
54
 
45
- This builds:
55
+ Install the bundled extension and native bridge for Zen.
46
56
 
47
- ```text
48
- dist/extensions/firefox/openbrowser-dev.xpi
49
- ```
50
-
51
- The Firefox development artifact is unsigned. It is useful for local development and for browser builds/profiles that allow unsigned extensions.
52
-
53
- ## Install the extension and native bridge
54
-
55
- ```bash
57
+ ```sh
56
58
  npx @pxlarified/browser install zen
57
59
  ```
58
60
 
59
- The installer:
61
+ The installer does the following.
60
62
 
61
63
  1. Copies the bundled Firefox `.xpi` into detected Zen profiles as `openbrowser@mizius.com.xpi`.
62
64
  2. Installs the user-scoped native messaging manifest.
63
65
  3. Installs the native host launcher under `~/OpenBrowser/native-host/`.
66
+ 4. Replaces an existing staged OpenBrowser extension with the bundled version.
64
67
 
65
- If an OpenBrowser extension is already staged in a Zen profile, it is overwritten with the bundled version. Restart Zen if the update does not load immediately.
68
+ Restart Zen if the extension update does not load immediately.
66
69
 
67
- ## CLI commands
70
+ ## Usage
68
71
 
69
72
  `--browser zen` is optional while Zen is the only supported browser.
70
73
 
71
- ```bash
72
- # Installation and session lifecycle
74
+ ```sh
75
+ npx @pxlarified/browser open https://example.com --browser zen
76
+ npx @pxlarified/browser state --browser zen
77
+ npx @pxlarified/browser click e_1 --browser zen
78
+ npx @pxlarified/browser screenshot --browser zen
79
+ npx @pxlarified/browser close --browser zen
80
+ ```
81
+
82
+ ### Session lifecycle
83
+
84
+ ```sh
73
85
  npx @pxlarified/browser install zen
74
86
  npx @pxlarified/browser open <url> --browser zen
75
87
  npx @pxlarified/browser close --browser zen
76
88
  npx @pxlarified/browser status --browser zen
89
+ ```
90
+
91
+ ### Navigation
77
92
 
78
- # Navigation
93
+ ```sh
79
94
  npx @pxlarified/browser navigate <url> --browser zen
80
95
  npx @pxlarified/browser reload --browser zen
81
96
  npx @pxlarified/browser back --browser zen
82
97
  npx @pxlarified/browser forward --browser zen
98
+ ```
99
+
100
+ ### Page state
83
101
 
84
- # Page state
102
+ ```sh
85
103
  npx @pxlarified/browser state --browser zen
104
+ ```
105
+
106
+ `state` returns the current URL, page title, viewport information, and actionable elements.
107
+
108
+ ```json
109
+ {"url":"https://example.com","title":"Example Domain","elements":[{"ref":"e_1","role":"link","name":"More information"}]}
110
+ ```
111
+
112
+ ### Screenshots
86
113
 
87
- # Screenshots
114
+ ```sh
88
115
  npx @pxlarified/browser screenshot --browser zen
89
116
  npx @pxlarified/browser screenshot --base64 --browser zen
117
+ ```
118
+
119
+ `screenshot` saves a PNG file under the user OpenBrowser directory and prints only the absolute file path to stdout.
90
120
 
91
- # Interaction
121
+ ```text
122
+ ~/OpenBrowser/screenshots/<8-character-uuid>.png
123
+ ```
124
+
125
+ `screenshot --base64` prints only the Base64-encoded PNG data to stdout. Diagnostic logs are written to stderr.
126
+
127
+ ### Interaction
128
+
129
+ ```sh
92
130
  npx @pxlarified/browser click <ref> --browser zen
93
131
  npx @pxlarified/browser keys <text> --browser zen
94
132
  npx @pxlarified/browser press <key> --browser zen
95
133
  npx @pxlarified/browser select <ref> <option> --browser zen
134
+ ```
135
+
136
+ ### Content inspection
96
137
 
97
- # Content inspection
138
+ ```sh
98
139
  npx @pxlarified/browser get --html --browser zen
99
140
  npx @pxlarified/browser get --html --ref <ref> --browser zen
141
+ ```
142
+
143
+ ### Scrolling
100
144
 
101
- # Scrolling
145
+ ```sh
102
146
  npx @pxlarified/browser scroll up [pixels] --browser zen
103
147
  npx @pxlarified/browser scroll down [pixels] --browser zen
104
148
  npx @pxlarified/browser scroll --to <ref> --browser zen
105
149
  ```
106
150
 
107
- Most commands print JSON to stdout. Screenshot commands are special:
151
+ ## Session model
108
152
 
109
- - `screenshot` saves a PNG to `~/OpenBrowser/screenshots/<8-char-uuid>.png` and prints only the absolute file path to stdout.
110
- - `screenshot --base64` prints only the Base64 PNG data to stdout.
111
- - Diagnostics are written to stderr.
153
+ OpenBrowser follows a strict ownership model.
112
154
 
113
- ## Page state and references
155
+ 1. Creating a session opens one new browser tab.
156
+ 2. A session may never contain more than one tab.
157
+ 3. `open` fails if an OpenBrowser session already exists for that browser.
158
+ 4. `close` closes only the tab owned by OpenBrowser.
159
+ 5. OpenBrowser never controls tabs the user opened manually.
160
+ 6. If the user manually closes the OpenBrowser-owned tab, the session is considered closed automatically.
161
+ 7. Opening a new session requires the existing OpenBrowser session to be closed first.
114
162
 
115
- `state` returns the current URL, title, viewport, and actionable elements. Element references are generated by OpenBrowser and do not depend on raw webpage IDs.
163
+ ## References
116
164
 
117
- ```json
118
- {"url":"https://example.com","title":"Example Domain","elements":[{"ref":"e_1","role":"link","name":"More information"}]}
119
- ```
165
+ Actionable elements use temporary OpenBrowser-generated references. These references do not depend on raw webpage HTML IDs.
166
+
167
+ References become invalid after navigation or relevant DOM updates. Commands that use invalid references return a `STALE_REFERENCE` error. Run `state` again to get fresh references.
168
+
169
+ ## Browser support
170
+
171
+ OpenBrowser uses browser-specific adapters so additional browsers can be added later.
172
+
173
+ Currently supported:
174
+
175
+ - Zen - Firefox-based browser.
120
176
 
121
- References become invalid after navigation or relevant DOM updates. Commands that use invalid references return a clear `STALE_REFERENCE` error. Run `state` again to get fresh references.
177
+ Planned architecture support:
122
178
 
123
- ## Firefox signing and release pipeline
179
+ - Firefox-family browsers through signed `.xpi` artifacts.
180
+ - Chromium-family browsers through `.zip` extension artifacts.
124
181
 
125
- The development build is unsigned:
182
+ ## Extension artifacts
126
183
 
127
- ```bash
184
+ The browser extension source lives in `extensions/`.
185
+
186
+ Build the Firefox development artifact.
187
+
188
+ ```sh
128
189
  npm run build:firefox
129
190
  ```
130
191
 
131
- When Mozilla Add-ons credentials are available, save them in a local uncommitted `.env` file:
192
+ The unsigned development artifact is written to:
193
+
194
+ ```text
195
+ dist/extensions/firefox/openbrowser-dev.xpi
196
+ ```
197
+
198
+ When a signed release artifact exists, the installer prefers:
199
+
200
+ ```text
201
+ dist/extensions/firefox/openbrowser.xpi
202
+ ```
203
+
204
+ Otherwise it falls back to the unsigned development artifact.
205
+
206
+ ## Firefox signing
207
+
208
+ Firefox release signing is handled as a separate release step. Save Mozilla Add-ons credentials in a local uncommitted `.env` file.
132
209
 
133
210
  ```env
134
211
  AMO_JWT_ISSUER="..."
135
212
  AMO_JWT_SECRET="..."
136
213
  ```
137
214
 
138
- Then create the signed unlisted release artifact with:
215
+ Then run:
139
216
 
140
- ```bash
217
+ ```sh
141
218
  npm run release:firefox
142
219
  ```
143
220
 
144
- You can also override the `.env` values with shell environment variables.
221
+ The release script submits the extension for Mozilla unlisted signing with `web-ext sign --channel unlisted` and bundles the resulting signed XPI at:
145
222
 
146
- The release script runs the equivalent of:
223
+ ```text
224
+ dist/extensions/firefox/openbrowser.xpi
225
+ ```
147
226
 
148
- ```bash
227
+ Equivalent signing command:
228
+
229
+ ```sh
149
230
  npx web-ext sign \
150
231
  --source-dir ./extensions/ \
151
232
  --channel unlisted \
@@ -153,30 +234,62 @@ npx web-ext sign \
153
234
  --api-secret "$AMO_JWT_SECRET"
154
235
  ```
155
236
 
156
- It then bundles the resulting signed XPI at:
237
+ ## Publishing
157
238
 
158
- ```text
159
- dist/extensions/firefox/openbrowser.xpi
239
+ Build and validate the package before publishing.
240
+
241
+ ```sh
242
+ npm install
243
+ npm run build
244
+ npx web-ext lint --source-dir extensions
245
+ npm pack --dry-run
160
246
  ```
161
247
 
162
- That signed `.xpi` is the artifact that should be included in the npm package for Firefox-based browser installs.
248
+ Publish the package publicly.
163
249
 
164
- ### Signing information you need to provide
250
+ ```sh
251
+ npm publish --access public
252
+ ```
165
253
 
166
- Before publishing a signed Firefox release, provide:
254
+ The package is configured with:
167
255
 
168
- 1. `AMO_JWT_ISSUER` - the Mozilla Add-ons API key / JWT issuer.
169
- 2. `AMO_JWT_SECRET` - the Mozilla Add-ons API secret / JWT secret.
170
- 3. Confirmation of the final extension ID: `openbrowser@mizius.com`.
171
- 4. Confirmation of the package/version to submit to AMO for unlisted signing.
256
+ ```json
257
+ {
258
+ "publishConfig": {
259
+ "access": "public"
260
+ }
261
+ }
262
+ ```
172
263
 
173
- ## Project layout
264
+ The npm package includes the CLI, source, extension source, and bundled extension artifacts. It does not publish `.env`, `node_modules`, build scratch files, or the local `skills/` directory.
174
265
 
175
- ```text
176
- bin/OpenBrowser.js CLI entry point
177
- src/ CLI, browser adapters, installer, native host
178
- extensions/ Browser extension source
179
- scripts/build.js Development XPI build
180
- scripts/sign-firefox.js Unlisted Firefox signing pipeline
181
- skills/OpenBrowser/SKILL.md Local agent skill, not npm-published
266
+ ## Development
267
+
268
+ Install dependencies.
269
+
270
+ ```sh
271
+ npm install
272
+ ```
273
+
274
+ Build the Firefox extension artifact.
275
+
276
+ ```sh
277
+ npm run build
182
278
  ```
279
+
280
+ Run the CLI locally without publishing.
281
+
282
+ ```sh
283
+ npm exec --package . OpenBrowser -- --help
284
+ ```
285
+
286
+ Project entry points:
287
+
288
+ - Logo source - `assets/logo.svg`
289
+ - CLI - `bin/OpenBrowser.js`
290
+ - CLI implementation - `src/cli.js`
291
+ - Zen adapter - `src/browsers/zen.js`
292
+ - Firefox-family installer - `src/browsers/firefox-family.js`
293
+ - Native bridge host - `src/native-host.cjs`
294
+ - Extension background script - `extensions/background.js`
295
+ - Extension content script - `extensions/content.js`
@@ -0,0 +1,19 @@
1
+ <svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OpenBrowser logo">
2
+ <rect width="128" height="128" rx="30" fill="url(#bg)"/>
3
+ <path d="M28 43C28 35.268 34.268 29 42 29H86C93.732 29 100 35.268 100 43V85C100 92.732 93.732 99 86 99H42C34.268 99 28 92.732 28 85V43Z" fill="#0B1020" fill-opacity="0.74"/>
4
+ <circle cx="39" cy="38" r="4" fill="#FF6B7A"/>
5
+ <circle cx="52" cy="38" r="4" fill="#FFD166"/>
6
+ <circle cx="65" cy="38" r="4" fill="#4BE1A0"/>
7
+ <path d="M73.863 54.873L49.586 65.072C47.191 66.079 47.307 69.514 49.765 70.357L61.008 74.211L65.025 85.396C65.905 87.846 69.342 87.911 70.312 85.496L80.139 61.03C81.643 57.285 77.582 53.31 73.863 54.873Z" fill="url(#pointer)"/>
8
+ <defs>
9
+ <linearGradient id="bg" x1="18" y1="10" x2="112" y2="120" gradientUnits="userSpaceOnUse">
10
+ <stop stop-color="#7C4DFF"/>
11
+ <stop offset="0.48" stop-color="#2563EB"/>
12
+ <stop offset="1" stop-color="#14B8A6"/>
13
+ </linearGradient>
14
+ <linearGradient id="pointer" x1="49" y1="54" x2="80" y2="87" gradientUnits="userSpaceOnUse">
15
+ <stop stop-color="#FFFFFF"/>
16
+ <stop offset="1" stop-color="#B8D7FF"/>
17
+ </linearGradient>
18
+ </defs>
19
+ </svg>
@@ -0,0 +1,19 @@
1
+ <svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OpenBrowser logo">
2
+ <rect width="128" height="128" rx="30" fill="url(#bg)"/>
3
+ <path d="M28 43C28 35.268 34.268 29 42 29H86C93.732 29 100 35.268 100 43V85C100 92.732 93.732 99 86 99H42C34.268 99 28 92.732 28 85V43Z" fill="#0B1020" fill-opacity="0.74"/>
4
+ <circle cx="39" cy="38" r="4" fill="#FF6B7A"/>
5
+ <circle cx="52" cy="38" r="4" fill="#FFD166"/>
6
+ <circle cx="65" cy="38" r="4" fill="#4BE1A0"/>
7
+ <path d="M73.863 54.873L49.586 65.072C47.191 66.079 47.307 69.514 49.765 70.357L61.008 74.211L65.025 85.396C65.905 87.846 69.342 87.911 70.312 85.496L80.139 61.03C81.643 57.285 77.582 53.31 73.863 54.873Z" fill="url(#pointer)"/>
8
+ <defs>
9
+ <linearGradient id="bg" x1="18" y1="10" x2="112" y2="120" gradientUnits="userSpaceOnUse">
10
+ <stop stop-color="#7C4DFF"/>
11
+ <stop offset="0.48" stop-color="#2563EB"/>
12
+ <stop offset="1" stop-color="#14B8A6"/>
13
+ </linearGradient>
14
+ <linearGradient id="pointer" x1="49" y1="54" x2="80" y2="87" gradientUnits="userSpaceOnUse">
15
+ <stop stop-color="#FFFFFF"/>
16
+ <stop offset="1" stop-color="#B8D7FF"/>
17
+ </linearGradient>
18
+ </defs>
19
+ </svg>
@@ -1,8 +1,13 @@
1
1
  {
2
2
  "manifest_version": 2,
3
3
  "name": "OpenBrowser",
4
- "version": "0.1.0",
4
+ "version": "1.9.4",
5
5
  "description": "Local user-scoped browser control bridge for the OpenBrowser CLI.",
6
+ "icons": {
7
+ "48": "assets/logo.svg",
8
+ "96": "assets/logo.svg",
9
+ "128": "assets/logo.svg"
10
+ },
6
11
  "permissions": [
7
12
  "nativeMessaging",
8
13
  "storage",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pxlarified/browser",
3
- "version": "0.1.0",
3
+ "version": "1.9.4",
4
4
  "description": "Minimal local browser-control CLI for OpenBrowser.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,6 +18,8 @@
18
18
  "extensions/manifest.json",
19
19
  "extensions/background.js",
20
20
  "extensions/content.js",
21
+ "extensions/assets/",
22
+ "assets/",
21
23
  "dist/extensions/",
22
24
  "scripts/",
23
25
  "README.md",
package/scripts/build.js CHANGED
@@ -23,6 +23,7 @@ fs.mkdirSync(outDir, { recursive: true });
23
23
  copyFile(path.join(sourceDir, "manifest.json"), path.join(buildDir, "manifest.json"));
24
24
  copyFile(path.join(sourceDir, "background.js"), path.join(buildDir, "background.js"));
25
25
  copyFile(path.join(sourceDir, "content.js"), path.join(buildDir, "content.js"));
26
+ copyDirectory(path.join(sourceDir, "assets"), path.join(buildDir, "assets"));
26
27
 
27
28
  await zipDirectory(buildDir, outFile);
28
29
  console.log(`Built ${path.relative(root, outFile)}`);
@@ -32,6 +33,16 @@ function copyFile(from, to) {
32
33
  fs.copyFileSync(from, to);
33
34
  }
34
35
 
36
+ function copyDirectory(from, to) {
37
+ if (!fs.existsSync(from)) return;
38
+ for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
39
+ const source = path.join(from, entry.name);
40
+ const destination = path.join(to, entry.name);
41
+ if (entry.isDirectory()) copyDirectory(source, destination);
42
+ else copyFile(source, destination);
43
+ }
44
+ }
45
+
35
46
  function zipDirectory(directory, destination) {
36
47
  return new Promise((resolve, reject) => {
37
48
  const zip = new yazl.ZipFile();