rechrome 1.2.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** — opt-in Gemini-powered screenshot descriptions (`--gemini-vision`)
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
 
@@ -61,9 +60,6 @@ rechrome open https://example.com
61
60
  # Take a screenshot
62
61
  rechrome screenshot
63
62
 
64
- # Take a screenshot with Gemini vision description
65
- rechrome screenshot --gemini-vision
66
-
67
63
  # List open tabs
68
64
  rechrome tab-list
69
65
 
@@ -82,7 +78,6 @@ cp .env.example .env.local
82
78
  | Variable | Description | Default |
83
79
  | -------------------------------- | -------------------------------------------------------------------------------------------------- | ---------------- |
84
80
  | `REMOTE_CHROME_URL` | Connection URL (auto-generated by `rechrome serve`) | — |
85
- | `GEMINI_API_KEY` | Gemini API key for `--gemini-vision` screenshot descriptions | — |
86
81
  | `PLAYWRIGHT_CLI` | Path to playwright-cli binary (recommended: `playwright-cli-multi-tab` for full multi-tab support) | `playwright-cli` |
87
82
  | `RECH_HOST` | Server bind address | `127.0.0.1` |
88
83
  | `PLAYWRIGHT_MCP_EXTENSION_ID` | Chrome extension ID (client overrides server) | — |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rechrome",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/snomiao/rechrome.git"
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",
@@ -164,10 +130,6 @@ function getClientEnv(): Record<string, string> {
164
130
  async function run(url: string, args: string[]) {
165
131
  const { key, host, port } = parseUrl(url);
166
132
 
167
- // Extract --gemini-vision flag (not forwarded to server args)
168
- const geminiVision = args.includes("--gemini-vision");
169
- const filteredArgs = args.filter((a) => a !== "--gemini-vision");
170
-
171
133
  const identity = await getClientIdentity();
172
134
  console.error(
173
135
  `[rech] connecting to ${host}:${port} (identity: ${identity.gitUrl || `${identity.hostname}:${identity.cwd}`})`,
@@ -175,7 +137,7 @@ async function run(url: string, args: string[]) {
175
137
  const res = await fetch(`http://${host}:${port}/run`, {
176
138
  method: "POST",
177
139
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
178
- body: JSON.stringify({ args: filteredArgs, identity, env: getClientEnv(), geminiVision }),
140
+ body: JSON.stringify({ args, identity, env: getClientEnv() }),
179
141
  signal: AbortSignal.timeout(70_000),
180
142
  }).catch((e) => {
181
143
  console.error(`[rech] ${e.message}`);
@@ -187,12 +149,11 @@ async function run(url: string, args: string[]) {
187
149
  process.exit(1);
188
150
  }
189
151
 
190
- const { status, stdout, stderr, files, descriptions, existingSession } = (await res.json()) as {
152
+ const { status, stdout, stderr, files, existingSession } = (await res.json()) as {
191
153
  status: number;
192
154
  stdout: string;
193
155
  stderr: string;
194
156
  files?: string[];
195
- descriptions?: Record<string, string>;
196
157
  existingSession?: boolean;
197
158
  };
198
159
 
@@ -217,9 +178,6 @@ async function run(url: string, args: string[]) {
217
178
  const dest = join(dlDir, basename(name));
218
179
  await Bun.write(dest, fileRes);
219
180
  console.error(`[rech] downloaded: ${dest}`);
220
- if (descriptions?.[name]) {
221
- console.error(`[rech] vision: ${descriptions[name]}`);
222
- }
223
181
  }
224
182
  }
225
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",
@@ -164,10 +130,6 @@ function getClientEnv(): Record<string, string> {
164
130
  async function run(url: string, args: string[]) {
165
131
  const { key, host, port } = parseUrl(url);
166
132
 
167
- // Extract --gemini-vision flag (not forwarded to server args)
168
- const geminiVision = args.includes("--gemini-vision");
169
- const filteredArgs = args.filter((a) => a !== "--gemini-vision");
170
-
171
133
  const identity = await getClientIdentity();
172
134
  console.error(
173
135
  `[rech] connecting to ${host}:${port} (identity: ${identity.gitUrl || `${identity.hostname}:${identity.cwd}`})`,
@@ -175,7 +137,7 @@ async function run(url: string, args: string[]) {
175
137
  const res = await fetch(`http://${host}:${port}/run`, {
176
138
  method: "POST",
177
139
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
178
- body: JSON.stringify({ args: filteredArgs, identity, env: getClientEnv(), geminiVision }),
140
+ body: JSON.stringify({ args, identity, env: getClientEnv() }),
179
141
  signal: AbortSignal.timeout(70_000),
180
142
  }).catch((e) => {
181
143
  console.error(`[rech] ${e.message}`);
@@ -187,12 +149,11 @@ async function run(url: string, args: string[]) {
187
149
  process.exit(1);
188
150
  }
189
151
 
190
- const { status, stdout, stderr, files, descriptions, existingSession } = (await res.json()) as {
152
+ const { status, stdout, stderr, files, existingSession } = (await res.json()) as {
191
153
  status: number;
192
154
  stdout: string;
193
155
  stderr: string;
194
156
  files?: string[];
195
- descriptions?: Record<string, string>;
196
157
  existingSession?: boolean;
197
158
  };
198
159
 
@@ -217,9 +178,6 @@ async function run(url: string, args: string[]) {
217
178
  const dest = join(dlDir, basename(name));
218
179
  await Bun.write(dest, fileRes);
219
180
  console.error(`[rech] downloaded: ${dest}`);
220
- if (descriptions?.[name]) {
221
- console.error(`[rech] vision: ${descriptions[name]}`);
222
- }
223
181
  }
224
182
  }
225
183
 
package/serve.js CHANGED
@@ -7,7 +7,6 @@ import {
7
7
  parseUrl,
8
8
  getOrCreateUrl,
9
9
  authCheck,
10
- describeImage,
11
10
  RECH_DIR,
12
11
  PASSTHROUGH_ENV_KEYS,
13
12
  } from "./rech.js";
@@ -53,7 +52,6 @@ export async function serve() {
53
52
  let sessionId: string;
54
53
  let clientName = "";
55
54
  let clientEnv: Record<string, string> = {};
56
- let geminiVision = false;
57
55
  if (Array.isArray(body)) {
58
56
  args = body;
59
57
  const clientAddr = `${req.headers.get("x-forwarded-for") || server.requestIP(req)?.address || "unknown"}`;
@@ -82,7 +80,6 @@ export async function serve() {
82
80
  if (typeof body.env[key] === "string") clientEnv[key] = body.env[key];
83
81
  }
84
82
  }
85
- if (body.geminiVision) geminiVision = true;
86
83
  }
87
84
 
88
85
  let clientSession = "";
@@ -202,24 +199,12 @@ export async function serve() {
202
199
  }
203
200
  }
204
201
 
205
- // Auto-describe screenshot files with Gemini vision (opt-in via --gemini-vision)
206
- const descriptions: Record<string, string> = {};
207
- if (geminiVision) {
208
- for (const f of outputFiles) {
209
- if (/\.(?:png|jpe?g)$/i.test(f)) {
210
- const desc = await describeImage(join(workDir, f));
211
- if (desc) descriptions[f] = desc;
212
- }
213
- }
214
- }
215
-
216
202
  const rebrand = (s: string) => s.replaceAll("npx playwright-cli", "rech");
217
203
  return Response.json({
218
204
  status,
219
205
  stdout: rebrand(stdout),
220
206
  stderr: rebrand(stderr),
221
207
  files: outputFiles,
222
- descriptions,
223
208
  });
224
209
  },
225
210
  });
package/serve.ts CHANGED
@@ -7,7 +7,6 @@ import {
7
7
  parseUrl,
8
8
  getOrCreateUrl,
9
9
  authCheck,
10
- describeImage,
11
10
  RECH_DIR,
12
11
  PASSTHROUGH_ENV_KEYS,
13
12
  } from "./rech.ts";
@@ -53,7 +52,6 @@ export async function serve() {
53
52
  let sessionId: string;
54
53
  let clientName = "";
55
54
  let clientEnv: Record<string, string> = {};
56
- let geminiVision = false;
57
55
  if (Array.isArray(body)) {
58
56
  args = body;
59
57
  const clientAddr = `${req.headers.get("x-forwarded-for") || server.requestIP(req)?.address || "unknown"}`;
@@ -82,7 +80,6 @@ export async function serve() {
82
80
  if (typeof body.env[key] === "string") clientEnv[key] = body.env[key];
83
81
  }
84
82
  }
85
- if (body.geminiVision) geminiVision = true;
86
83
  }
87
84
 
88
85
  let clientSession = "";
@@ -202,24 +199,12 @@ export async function serve() {
202
199
  }
203
200
  }
204
201
 
205
- // Auto-describe screenshot files with Gemini vision (opt-in via --gemini-vision)
206
- const descriptions: Record<string, string> = {};
207
- if (geminiVision) {
208
- for (const f of outputFiles) {
209
- if (/\.(?:png|jpe?g)$/i.test(f)) {
210
- const desc = await describeImage(join(workDir, f));
211
- if (desc) descriptions[f] = desc;
212
- }
213
- }
214
- }
215
-
216
202
  const rebrand = (s: string) => s.replaceAll("npx playwright-cli", "rech");
217
203
  return Response.json({
218
204
  status,
219
205
  stdout: rebrand(stdout),
220
206
  stderr: rebrand(stderr),
221
207
  files: outputFiles,
222
- descriptions,
223
208
  });
224
209
  },
225
210
  });