rechrome 1.1.0 → 1.2.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/README.md CHANGED
@@ -10,7 +10,7 @@ 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
13
+ - **Vision descriptions** — opt-in Gemini-powered screenshot descriptions (`--gemini-vision`)
14
14
  - **Hot-reload config** — `.env.local` changes are picked up without restart
15
15
  - **Security** — bearer auth, path traversal protection, env allowlisting for child processes
16
16
 
@@ -61,6 +61,9 @@ rechrome open https://example.com
61
61
  # Take a screenshot
62
62
  rechrome screenshot
63
63
 
64
+ # Take a screenshot with Gemini vision description
65
+ rechrome screenshot --gemini-vision
66
+
64
67
  # List open tabs
65
68
  rechrome tab-list
66
69
 
@@ -76,14 +79,14 @@ Copy `.env.example` to `.env.local` and edit:
76
79
  cp .env.example .env.local
77
80
  ```
78
81
 
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) | — |
82
+ | Variable | Description | Default |
83
+ | -------------------------------- | -------------------------------------------------------------------------------------------------- | ---------------- |
84
+ | `REMOTE_CHROME_URL` | Connection URL (auto-generated by `rechrome serve`) | — |
85
+ | `GEMINI_API_KEY` | Gemini API key for `--gemini-vision` screenshot descriptions | — |
86
+ | `PLAYWRIGHT_CLI` | Path to playwright-cli binary (recommended: `playwright-cli-multi-tab` for full multi-tab support) | `playwright-cli` |
87
+ | `RECH_HOST` | Server bind address | `127.0.0.1` |
88
+ | `PLAYWRIGHT_MCP_EXTENSION_ID` | Chrome extension ID (client overrides server) | — |
89
+ | `PLAYWRIGHT_MCP_EXTENSION_TOKEN` | Chrome extension token (client overrides server) | — |
87
90
 
88
91
  ### Remote access
89
92
 
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.2.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
@@ -141,7 +141,12 @@ async function getClientIdentity(): Promise<{ gitUrl?: string; hostname?: string
141
141
  }
142
142
  if (branch) gitUrl += `/tree/${branch}`;
143
143
  // Strip any embedded credentials from the URL
144
- try { const u = new URL(gitUrl); u.username = ""; u.password = ""; gitUrl = u.toString(); } catch {}
144
+ try {
145
+ const u = new URL(gitUrl);
146
+ u.username = "";
147
+ u.password = "";
148
+ gitUrl = u.toString();
149
+ } catch {}
145
150
  return { gitUrl };
146
151
  }
147
152
  } catch {}
@@ -158,6 +163,11 @@ function getClientEnv(): Record<string, string> {
158
163
 
159
164
  async function run(url: string, args: string[]) {
160
165
  const { key, host, port } = parseUrl(url);
166
+
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
+
161
171
  const identity = await getClientIdentity();
162
172
  console.error(
163
173
  `[rech] connecting to ${host}:${port} (identity: ${identity.gitUrl || `${identity.hostname}:${identity.cwd}`})`,
@@ -165,7 +175,7 @@ async function run(url: string, args: string[]) {
165
175
  const res = await fetch(`http://${host}:${port}/run`, {
166
176
  method: "POST",
167
177
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
168
- body: JSON.stringify({ args, identity, env: getClientEnv() }),
178
+ body: JSON.stringify({ args: filteredArgs, identity, env: getClientEnv(), geminiVision }),
169
179
  signal: AbortSignal.timeout(70_000),
170
180
  }).catch((e) => {
171
181
  console.error(`[rech] ${e.message}`);
package/rech.ts CHANGED
@@ -141,7 +141,12 @@ async function getClientIdentity(): Promise<{ gitUrl?: string; hostname?: string
141
141
  }
142
142
  if (branch) gitUrl += `/tree/${branch}`;
143
143
  // Strip any embedded credentials from the URL
144
- try { const u = new URL(gitUrl); u.username = ""; u.password = ""; gitUrl = u.toString(); } catch {}
144
+ try {
145
+ const u = new URL(gitUrl);
146
+ u.username = "";
147
+ u.password = "";
148
+ gitUrl = u.toString();
149
+ } catch {}
145
150
  return { gitUrl };
146
151
  }
147
152
  } catch {}
@@ -158,6 +163,11 @@ function getClientEnv(): Record<string, string> {
158
163
 
159
164
  async function run(url: string, args: string[]) {
160
165
  const { key, host, port } = parseUrl(url);
166
+
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
+
161
171
  const identity = await getClientIdentity();
162
172
  console.error(
163
173
  `[rech] connecting to ${host}:${port} (identity: ${identity.gitUrl || `${identity.hostname}:${identity.cwd}`})`,
@@ -165,7 +175,7 @@ async function run(url: string, args: string[]) {
165
175
  const res = await fetch(`http://${host}:${port}/run`, {
166
176
  method: "POST",
167
177
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
168
- body: JSON.stringify({ args, identity, env: getClientEnv() }),
178
+ body: JSON.stringify({ args: filteredArgs, identity, env: getClientEnv(), geminiVision }),
169
179
  signal: AbortSignal.timeout(70_000),
170
180
  }).catch((e) => {
171
181
  console.error(`[rech] ${e.message}`);
package/serve.js CHANGED
@@ -2,7 +2,15 @@ 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
+ describeImage,
11
+ RECH_DIR,
12
+ PASSTHROUGH_ENV_KEYS,
13
+ } from "./rech.js";
6
14
 
7
15
  export function isUnderDir(base: string, candidate: string): boolean {
8
16
  const absBase = resolve(base) + "/";
@@ -45,6 +53,7 @@ export async function serve() {
45
53
  let sessionId: string;
46
54
  let clientName = "";
47
55
  let clientEnv: Record<string, string> = {};
56
+ let geminiVision = false;
48
57
  if (Array.isArray(body)) {
49
58
  args = body;
50
59
  const clientAddr = `${req.headers.get("x-forwarded-for") || server.requestIP(req)?.address || "unknown"}`;
@@ -73,6 +82,7 @@ export async function serve() {
73
82
  if (typeof body.env[key] === "string") clientEnv[key] = body.env[key];
74
83
  }
75
84
  }
85
+ if (body.geminiVision) geminiVision = true;
76
86
  }
77
87
 
78
88
  let clientSession = "";
@@ -192,12 +202,14 @@ export async function serve() {
192
202
  }
193
203
  }
194
204
 
195
- // Auto-describe screenshot files with Gemini vision
205
+ // Auto-describe screenshot files with Gemini vision (opt-in via --gemini-vision)
196
206
  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;
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
+ }
201
213
  }
202
214
  }
203
215
 
package/serve.ts CHANGED
@@ -2,7 +2,15 @@ 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
+ describeImage,
11
+ RECH_DIR,
12
+ PASSTHROUGH_ENV_KEYS,
13
+ } from "./rech.ts";
6
14
 
7
15
  export function isUnderDir(base: string, candidate: string): boolean {
8
16
  const absBase = resolve(base) + "/";
@@ -45,6 +53,7 @@ export async function serve() {
45
53
  let sessionId: string;
46
54
  let clientName = "";
47
55
  let clientEnv: Record<string, string> = {};
56
+ let geminiVision = false;
48
57
  if (Array.isArray(body)) {
49
58
  args = body;
50
59
  const clientAddr = `${req.headers.get("x-forwarded-for") || server.requestIP(req)?.address || "unknown"}`;
@@ -73,6 +82,7 @@ export async function serve() {
73
82
  if (typeof body.env[key] === "string") clientEnv[key] = body.env[key];
74
83
  }
75
84
  }
85
+ if (body.geminiVision) geminiVision = true;
76
86
  }
77
87
 
78
88
  let clientSession = "";
@@ -192,12 +202,14 @@ export async function serve() {
192
202
  }
193
203
  }
194
204
 
195
- // Auto-describe screenshot files with Gemini vision
205
+ // Auto-describe screenshot files with Gemini vision (opt-in via --gemini-vision)
196
206
  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;
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
+ }
201
213
  }
202
214
  }
203
215