rechrome 1.1.0 → 1.3.0

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/.env.example CHANGED
@@ -1,9 +1,6 @@
1
1
  # Remote Chrome connection URL (auto-generated by `rech serve` if not set)
2
2
  # REMOTE_CHROME_URL=remote-chrome://key@host:13775
3
3
 
4
- # Gemini API key for screenshot vision descriptions (optional)
5
- # GEMINI_API_KEY=your-gemini-api-key
6
-
7
4
  # Custom playwright-cli binary path (default: playwright-cli)
8
5
  # For full multi-tab & multi-session support, use playwright-cli-multi-tab:
9
6
  # PLAYWRIGHT_CLI=playwright-cli-multi-tab
package/README.md CHANGED
@@ -10,7 +10,6 @@ Built on top of [playwright-multi-tab](https://github.com/snomiao/playwright-mul
10
10
 
11
11
  - **Session isolation** — clients are automatically namespaced by git repo or hostname
12
12
  - **File transfer** — screenshots and PDFs are automatically downloaded to the client
13
- - **Vision descriptions** — optional Gemini-powered screenshot descriptions
14
13
  - **Hot-reload config** — `.env.local` changes are picked up without restart
15
14
  - **Security** — bearer auth, path traversal protection, env allowlisting for child processes
16
15
 
@@ -76,14 +75,13 @@ Copy `.env.example` to `.env.local` and edit:
76
75
  cp .env.example .env.local
77
76
  ```
78
77
 
79
- | Variable | Description | Default |
80
- |---|---|---|
81
- | `REMOTE_CHROME_URL` | Connection URL (auto-generated by `rechrome serve`) | — |
82
- | `GEMINI_API_KEY` | Gemini API key for screenshot vision descriptions | |
83
- | `PLAYWRIGHT_CLI` | Path to playwright-cli binary (recommended: `playwright-cli-multi-tab` for full multi-tab support) | `playwright-cli` |
84
- | `RECH_HOST` | Server bind address | `127.0.0.1` |
85
- | `PLAYWRIGHT_MCP_EXTENSION_ID` | Chrome extension ID (client overrides server) | — |
86
- | `PLAYWRIGHT_MCP_EXTENSION_TOKEN` | Chrome extension token (client overrides server) | — |
78
+ | Variable | Description | Default |
79
+ | -------------------------------- | -------------------------------------------------------------------------------------------------- | ---------------- |
80
+ | `REMOTE_CHROME_URL` | Connection URL (auto-generated by `rechrome serve`) | — |
81
+ | `PLAYWRIGHT_CLI` | Path to playwright-cli binary (recommended: `playwright-cli-multi-tab` for full multi-tab support) | `playwright-cli` |
82
+ | `RECH_HOST` | Server bind address | `127.0.0.1` |
83
+ | `PLAYWRIGHT_MCP_EXTENSION_ID` | Chrome extension ID (client overrides server) | — |
84
+ | `PLAYWRIGHT_MCP_EXTENSION_TOKEN` | Chrome extension token (client overrides server) | — |
87
85
 
88
86
  ### Remote access
89
87
 
package/package.json CHANGED
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "name": "rechrome",
3
- "version": "1.1.0",
4
- "type": "module",
3
+ "version": "1.3.0",
5
4
  "repository": {
6
5
  "type": "git",
7
6
  "url": "https://github.com/snomiao/rechrome.git"
@@ -10,6 +9,16 @@
10
9
  "rech": "./rech.js",
11
10
  "rechrome": "./rech.js"
12
11
  },
12
+ "files": [
13
+ ".env.example",
14
+ "LICENSE",
15
+ "README.md",
16
+ "rech.js",
17
+ "rech.ts",
18
+ "serve.js",
19
+ "serve.ts"
20
+ ],
21
+ "type": "module",
13
22
  "scripts": {
14
23
  "serve": "bun run rech.ts serve",
15
24
  "test": "bun test",
@@ -22,14 +31,5 @@
22
31
  "branches": [
23
32
  "main"
24
33
  ]
25
- },
26
- "files": [
27
- "rech.ts",
28
- "rech.js",
29
- "serve.ts",
30
- "serve.js",
31
- ".env.example",
32
- "README.md",
33
- "LICENSE"
34
- ]
34
+ }
35
35
  }
package/rech.js CHANGED
@@ -35,40 +35,6 @@ if (existsSync(envFile)) {
35
35
  });
36
36
  }
37
37
 
38
- /** Describe an image using Gemini vision API. Returns description or null on failure. */
39
- export async function describeImage(imagePath: string): Promise<string | null> {
40
- const apiKey = process.env.GEMINI_API_KEY;
41
- if (!apiKey) return null;
42
- try {
43
- const imageData = await file(imagePath).arrayBuffer();
44
- const base64 = Buffer.from(imageData).toString("base64");
45
- const mimeType = imagePath.endsWith(".png") ? "image/png" : "image/jpeg";
46
- const res = await fetch(
47
- `https://generativelanguage.googleapis.com/v1beta/models/gemini-3.1-pro-preview:generateContent?key=${apiKey}`,
48
- {
49
- method: "POST",
50
- headers: { "Content-Type": "application/json" },
51
- body: JSON.stringify({
52
- contents: [
53
- {
54
- parts: [
55
- {
56
- text: "Describe this browser screenshot concisely in 2-3 sentences. Focus on what's visible: page layout, content, any errors or issues.",
57
- },
58
- { inline_data: { mime_type: mimeType, data: base64 } },
59
- ],
60
- },
61
- ],
62
- }),
63
- },
64
- );
65
- if (!res.ok) return null;
66
- const json = await res.json();
67
- return json.candidates?.[0]?.content?.parts?.[0]?.text?.trim() ?? null;
68
- } catch {
69
- return null;
70
- }
71
- }
72
38
 
73
39
  export const PASSTHROUGH_ENV_KEYS = [
74
40
  "PLAYWRIGHT_MCP_EXTENSION_ID",
@@ -141,7 +107,12 @@ async function getClientIdentity(): Promise<{ gitUrl?: string; hostname?: string
141
107
  }
142
108
  if (branch) gitUrl += `/tree/${branch}`;
143
109
  // Strip any embedded credentials from the URL
144
- try { const u = new URL(gitUrl); u.username = ""; u.password = ""; gitUrl = u.toString(); } catch {}
110
+ try {
111
+ const u = new URL(gitUrl);
112
+ u.username = "";
113
+ u.password = "";
114
+ gitUrl = u.toString();
115
+ } catch {}
145
116
  return { gitUrl };
146
117
  }
147
118
  } catch {}
@@ -158,6 +129,7 @@ function getClientEnv(): Record<string, string> {
158
129
 
159
130
  async function run(url: string, args: string[]) {
160
131
  const { key, host, port } = parseUrl(url);
132
+
161
133
  const identity = await getClientIdentity();
162
134
  console.error(
163
135
  `[rech] connecting to ${host}:${port} (identity: ${identity.gitUrl || `${identity.hostname}:${identity.cwd}`})`,
@@ -177,12 +149,11 @@ async function run(url: string, args: string[]) {
177
149
  process.exit(1);
178
150
  }
179
151
 
180
- const { status, stdout, stderr, files, descriptions, existingSession } = (await res.json()) as {
152
+ const { status, stdout, stderr, files, existingSession } = (await res.json()) as {
181
153
  status: number;
182
154
  stdout: string;
183
155
  stderr: string;
184
156
  files?: string[];
185
- descriptions?: Record<string, string>;
186
157
  existingSession?: boolean;
187
158
  };
188
159
 
@@ -207,9 +178,6 @@ async function run(url: string, args: string[]) {
207
178
  const dest = join(dlDir, basename(name));
208
179
  await Bun.write(dest, fileRes);
209
180
  console.error(`[rech] downloaded: ${dest}`);
210
- if (descriptions?.[name]) {
211
- console.error(`[rech] vision: ${descriptions[name]}`);
212
- }
213
181
  }
214
182
  }
215
183
 
package/rech.ts CHANGED
@@ -35,40 +35,6 @@ if (existsSync(envFile)) {
35
35
  });
36
36
  }
37
37
 
38
- /** Describe an image using Gemini vision API. Returns description or null on failure. */
39
- export async function describeImage(imagePath: string): Promise<string | null> {
40
- const apiKey = process.env.GEMINI_API_KEY;
41
- if (!apiKey) return null;
42
- try {
43
- const imageData = await file(imagePath).arrayBuffer();
44
- const base64 = Buffer.from(imageData).toString("base64");
45
- const mimeType = imagePath.endsWith(".png") ? "image/png" : "image/jpeg";
46
- const res = await fetch(
47
- `https://generativelanguage.googleapis.com/v1beta/models/gemini-3.1-pro-preview:generateContent?key=${apiKey}`,
48
- {
49
- method: "POST",
50
- headers: { "Content-Type": "application/json" },
51
- body: JSON.stringify({
52
- contents: [
53
- {
54
- parts: [
55
- {
56
- text: "Describe this browser screenshot concisely in 2-3 sentences. Focus on what's visible: page layout, content, any errors or issues.",
57
- },
58
- { inline_data: { mime_type: mimeType, data: base64 } },
59
- ],
60
- },
61
- ],
62
- }),
63
- },
64
- );
65
- if (!res.ok) return null;
66
- const json = await res.json();
67
- return json.candidates?.[0]?.content?.parts?.[0]?.text?.trim() ?? null;
68
- } catch {
69
- return null;
70
- }
71
- }
72
38
 
73
39
  export const PASSTHROUGH_ENV_KEYS = [
74
40
  "PLAYWRIGHT_MCP_EXTENSION_ID",
@@ -141,7 +107,12 @@ async function getClientIdentity(): Promise<{ gitUrl?: string; hostname?: string
141
107
  }
142
108
  if (branch) gitUrl += `/tree/${branch}`;
143
109
  // Strip any embedded credentials from the URL
144
- try { const u = new URL(gitUrl); u.username = ""; u.password = ""; gitUrl = u.toString(); } catch {}
110
+ try {
111
+ const u = new URL(gitUrl);
112
+ u.username = "";
113
+ u.password = "";
114
+ gitUrl = u.toString();
115
+ } catch {}
145
116
  return { gitUrl };
146
117
  }
147
118
  } catch {}
@@ -158,6 +129,7 @@ function getClientEnv(): Record<string, string> {
158
129
 
159
130
  async function run(url: string, args: string[]) {
160
131
  const { key, host, port } = parseUrl(url);
132
+
161
133
  const identity = await getClientIdentity();
162
134
  console.error(
163
135
  `[rech] connecting to ${host}:${port} (identity: ${identity.gitUrl || `${identity.hostname}:${identity.cwd}`})`,
@@ -177,12 +149,11 @@ async function run(url: string, args: string[]) {
177
149
  process.exit(1);
178
150
  }
179
151
 
180
- const { status, stdout, stderr, files, descriptions, existingSession } = (await res.json()) as {
152
+ const { status, stdout, stderr, files, existingSession } = (await res.json()) as {
181
153
  status: number;
182
154
  stdout: string;
183
155
  stderr: string;
184
156
  files?: string[];
185
- descriptions?: Record<string, string>;
186
157
  existingSession?: boolean;
187
158
  };
188
159
 
@@ -207,9 +178,6 @@ async function run(url: string, args: string[]) {
207
178
  const dest = join(dlDir, basename(name));
208
179
  await Bun.write(dest, fileRes);
209
180
  console.error(`[rech] downloaded: ${dest}`);
210
- if (descriptions?.[name]) {
211
- console.error(`[rech] vision: ${descriptions[name]}`);
212
- }
213
181
  }
214
182
  }
215
183
 
package/serve.js CHANGED
@@ -2,7 +2,14 @@ import { file } from "bun";
2
2
  import { createHash } from "crypto";
3
3
  import { mkdirSync } from "fs";
4
4
  import { join, resolve, relative, isAbsolute } from "path";
5
- import { log, parseUrl, getOrCreateUrl, authCheck, describeImage, RECH_DIR, PASSTHROUGH_ENV_KEYS } from "./rech.js";
5
+ import {
6
+ log,
7
+ parseUrl,
8
+ getOrCreateUrl,
9
+ authCheck,
10
+ RECH_DIR,
11
+ PASSTHROUGH_ENV_KEYS,
12
+ } from "./rech.js";
6
13
 
7
14
  export function isUnderDir(base: string, candidate: string): boolean {
8
15
  const absBase = resolve(base) + "/";
@@ -192,22 +199,12 @@ export async function serve() {
192
199
  }
193
200
  }
194
201
 
195
- // Auto-describe screenshot files with Gemini vision
196
- const descriptions: Record<string, string> = {};
197
- for (const f of outputFiles) {
198
- if (/\.(?:png|jpe?g)$/i.test(f)) {
199
- const desc = await describeImage(join(workDir, f));
200
- if (desc) descriptions[f] = desc;
201
- }
202
- }
203
-
204
202
  const rebrand = (s: string) => s.replaceAll("npx playwright-cli", "rech");
205
203
  return Response.json({
206
204
  status,
207
205
  stdout: rebrand(stdout),
208
206
  stderr: rebrand(stderr),
209
207
  files: outputFiles,
210
- descriptions,
211
208
  });
212
209
  },
213
210
  });
package/serve.ts CHANGED
@@ -2,7 +2,14 @@ import { file } from "bun";
2
2
  import { createHash } from "crypto";
3
3
  import { mkdirSync } from "fs";
4
4
  import { join, resolve, relative, isAbsolute } from "path";
5
- import { log, parseUrl, getOrCreateUrl, authCheck, describeImage, RECH_DIR, PASSTHROUGH_ENV_KEYS } from "./rech.ts";
5
+ import {
6
+ log,
7
+ parseUrl,
8
+ getOrCreateUrl,
9
+ authCheck,
10
+ RECH_DIR,
11
+ PASSTHROUGH_ENV_KEYS,
12
+ } from "./rech.ts";
6
13
 
7
14
  export function isUnderDir(base: string, candidate: string): boolean {
8
15
  const absBase = resolve(base) + "/";
@@ -192,22 +199,12 @@ export async function serve() {
192
199
  }
193
200
  }
194
201
 
195
- // Auto-describe screenshot files with Gemini vision
196
- const descriptions: Record<string, string> = {};
197
- for (const f of outputFiles) {
198
- if (/\.(?:png|jpe?g)$/i.test(f)) {
199
- const desc = await describeImage(join(workDir, f));
200
- if (desc) descriptions[f] = desc;
201
- }
202
- }
203
-
204
202
  const rebrand = (s: string) => s.replaceAll("npx playwright-cli", "rech");
205
203
  return Response.json({
206
204
  status,
207
205
  stdout: rebrand(stdout),
208
206
  stderr: rebrand(stderr),
209
207
  files: outputFiles,
210
- descriptions,
211
208
  });
212
209
  },
213
210
  });