rechrome 1.2.0 → 1.4.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 +10 -5
- package/README.md +11 -7
- package/package.json +1 -1
- package/rech.js +4 -44
- package/rech.ts +4 -44
- package/serve.js +3 -18
- package/serve.ts +3 -18
package/.env.example
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
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
|
-
#
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
# Custom playwright-cli binary path (default: playwright-cli)
|
|
8
|
-
# For full multi-tab & multi-session support, use playwright-cli-multi-tab:
|
|
4
|
+
# Custom playwright-cli binary (default: playwright-cli). Supports space-separated runtime prefix.
|
|
5
|
+
# For global install: PLAYWRIGHT_CLI=playwright-cli-multi-tab
|
|
6
|
+
# For local submodule (with bun): PLAYWRIGHT_CLI=bun ./lib/playwright-cli/playwright-cli.js
|
|
9
7
|
# PLAYWRIGHT_CLI=playwright-cli-multi-tab
|
|
10
8
|
|
|
11
9
|
# Bind address for the server (default: 127.0.0.1, use 0.0.0.0 for remote access)
|
|
@@ -14,3 +12,10 @@
|
|
|
14
12
|
# Playwright MCP extension settings (can be set on server or client; client overrides server)
|
|
15
13
|
# PLAYWRIGHT_MCP_EXTENSION_ID=your-extension-id
|
|
16
14
|
# PLAYWRIGHT_MCP_EXTENSION_TOKEN=your-extension-token
|
|
15
|
+
|
|
16
|
+
# Chrome profile to use when connecting via --extension (macOS/Windows multi-profile setups)
|
|
17
|
+
# Set USER_DATA_DIR to Chrome's user data directory, and PROFILE_DIRECTORY to the sub-profile name.
|
|
18
|
+
# macOS default: ~/Library/Application Support/Google/Chrome
|
|
19
|
+
# Profile names: Default, "Profile 1", "Profile 2", etc. (check chrome://version for yours)
|
|
20
|
+
# PLAYWRIGHT_MCP_USER_DATA_DIR=/Users/yourname/Library/Application Support/Google/Chrome
|
|
21
|
+
# PLAYWRIGHT_MCP_PROFILE_DIRECTORY=Profile 2
|
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,11 +78,19 @@ 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
|
-
| `PLAYWRIGHT_MCP_EXTENSION_ID`
|
|
89
|
-
| `PLAYWRIGHT_MCP_EXTENSION_TOKEN`
|
|
83
|
+
| `PLAYWRIGHT_MCP_EXTENSION_ID` | Chrome extension ID (client overrides server) | — |
|
|
84
|
+
| `PLAYWRIGHT_MCP_EXTENSION_TOKEN` | Chrome extension token (client overrides server) | — |
|
|
85
|
+
| `PLAYWRIGHT_MCP_USER_DATA_DIR` | Chrome user data directory — use to pin connections to a specific Chrome install (client overrides server) | — |
|
|
86
|
+
| `PLAYWRIGHT_MCP_PROFILE_DIRECTORY` | Chrome profile sub-directory (e.g. `Profile 2`) — use when multiple profiles share the same user data dir (client overrides server) | — |
|
|
87
|
+
|
|
88
|
+
> **Multi-profile tip:** If you have multiple Chrome profiles open and only one has the Playwright MCP Bridge extension, set both vars so the extension connection always targets the correct profile:
|
|
89
|
+
> ```
|
|
90
|
+
> PLAYWRIGHT_MCP_USER_DATA_DIR=/Users/yourname/Library/Application Support/Google/Chrome
|
|
91
|
+
> PLAYWRIGHT_MCP_PROFILE_DIRECTORY=Profile 2
|
|
92
|
+
> ```
|
|
93
|
+
> To find your profile directory name, open `chrome://version` in the target Chrome profile and look for **Profile Path**.
|
|
90
94
|
|
|
91
95
|
### Remote access
|
|
92
96
|
|
package/package.json
CHANGED
package/rech.js
CHANGED
|
@@ -35,44 +35,12 @@ 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",
|
|
75
41
|
"PLAYWRIGHT_MCP_EXTENSION_TOKEN",
|
|
42
|
+
"PLAYWRIGHT_MCP_USER_DATA_DIR",
|
|
43
|
+
"PLAYWRIGHT_MCP_PROFILE_DIRECTORY",
|
|
76
44
|
] as const;
|
|
77
45
|
|
|
78
46
|
export function log(msg: string) {
|
|
@@ -164,10 +132,6 @@ function getClientEnv(): Record<string, string> {
|
|
|
164
132
|
async function run(url: string, args: string[]) {
|
|
165
133
|
const { key, host, port } = parseUrl(url);
|
|
166
134
|
|
|
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
135
|
const identity = await getClientIdentity();
|
|
172
136
|
console.error(
|
|
173
137
|
`[rech] connecting to ${host}:${port} (identity: ${identity.gitUrl || `${identity.hostname}:${identity.cwd}`})`,
|
|
@@ -175,7 +139,7 @@ async function run(url: string, args: string[]) {
|
|
|
175
139
|
const res = await fetch(`http://${host}:${port}/run`, {
|
|
176
140
|
method: "POST",
|
|
177
141
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
|
|
178
|
-
body: JSON.stringify({ args
|
|
142
|
+
body: JSON.stringify({ args, identity, env: getClientEnv() }),
|
|
179
143
|
signal: AbortSignal.timeout(70_000),
|
|
180
144
|
}).catch((e) => {
|
|
181
145
|
console.error(`[rech] ${e.message}`);
|
|
@@ -187,12 +151,11 @@ async function run(url: string, args: string[]) {
|
|
|
187
151
|
process.exit(1);
|
|
188
152
|
}
|
|
189
153
|
|
|
190
|
-
const { status, stdout, stderr, files,
|
|
154
|
+
const { status, stdout, stderr, files, existingSession } = (await res.json()) as {
|
|
191
155
|
status: number;
|
|
192
156
|
stdout: string;
|
|
193
157
|
stderr: string;
|
|
194
158
|
files?: string[];
|
|
195
|
-
descriptions?: Record<string, string>;
|
|
196
159
|
existingSession?: boolean;
|
|
197
160
|
};
|
|
198
161
|
|
|
@@ -217,9 +180,6 @@ async function run(url: string, args: string[]) {
|
|
|
217
180
|
const dest = join(dlDir, basename(name));
|
|
218
181
|
await Bun.write(dest, fileRes);
|
|
219
182
|
console.error(`[rech] downloaded: ${dest}`);
|
|
220
|
-
if (descriptions?.[name]) {
|
|
221
|
-
console.error(`[rech] vision: ${descriptions[name]}`);
|
|
222
|
-
}
|
|
223
183
|
}
|
|
224
184
|
}
|
|
225
185
|
|
package/rech.ts
CHANGED
|
@@ -35,44 +35,12 @@ 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",
|
|
75
41
|
"PLAYWRIGHT_MCP_EXTENSION_TOKEN",
|
|
42
|
+
"PLAYWRIGHT_MCP_USER_DATA_DIR",
|
|
43
|
+
"PLAYWRIGHT_MCP_PROFILE_DIRECTORY",
|
|
76
44
|
] as const;
|
|
77
45
|
|
|
78
46
|
export function log(msg: string) {
|
|
@@ -164,10 +132,6 @@ function getClientEnv(): Record<string, string> {
|
|
|
164
132
|
async function run(url: string, args: string[]) {
|
|
165
133
|
const { key, host, port } = parseUrl(url);
|
|
166
134
|
|
|
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
135
|
const identity = await getClientIdentity();
|
|
172
136
|
console.error(
|
|
173
137
|
`[rech] connecting to ${host}:${port} (identity: ${identity.gitUrl || `${identity.hostname}:${identity.cwd}`})`,
|
|
@@ -175,7 +139,7 @@ async function run(url: string, args: string[]) {
|
|
|
175
139
|
const res = await fetch(`http://${host}:${port}/run`, {
|
|
176
140
|
method: "POST",
|
|
177
141
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
|
|
178
|
-
body: JSON.stringify({ args
|
|
142
|
+
body: JSON.stringify({ args, identity, env: getClientEnv() }),
|
|
179
143
|
signal: AbortSignal.timeout(70_000),
|
|
180
144
|
}).catch((e) => {
|
|
181
145
|
console.error(`[rech] ${e.message}`);
|
|
@@ -187,12 +151,11 @@ async function run(url: string, args: string[]) {
|
|
|
187
151
|
process.exit(1);
|
|
188
152
|
}
|
|
189
153
|
|
|
190
|
-
const { status, stdout, stderr, files,
|
|
154
|
+
const { status, stdout, stderr, files, existingSession } = (await res.json()) as {
|
|
191
155
|
status: number;
|
|
192
156
|
stdout: string;
|
|
193
157
|
stderr: string;
|
|
194
158
|
files?: string[];
|
|
195
|
-
descriptions?: Record<string, string>;
|
|
196
159
|
existingSession?: boolean;
|
|
197
160
|
};
|
|
198
161
|
|
|
@@ -217,9 +180,6 @@ async function run(url: string, args: string[]) {
|
|
|
217
180
|
const dest = join(dlDir, basename(name));
|
|
218
181
|
await Bun.write(dest, fileRes);
|
|
219
182
|
console.error(`[rech] downloaded: ${dest}`);
|
|
220
|
-
if (descriptions?.[name]) {
|
|
221
|
-
console.error(`[rech] vision: ${descriptions[name]}`);
|
|
222
|
-
}
|
|
223
183
|
}
|
|
224
184
|
}
|
|
225
185
|
|
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 = "";
|
|
@@ -96,7 +93,7 @@ export async function serve() {
|
|
|
96
93
|
});
|
|
97
94
|
const namespacedSession = clientSession ? `${sessionId}-${clientSession}` : sessionId;
|
|
98
95
|
|
|
99
|
-
const bin = process.env.PLAYWRIGHT_CLI || "playwright-cli";
|
|
96
|
+
const [bin, ...binArgs] = (process.env.PLAYWRIGHT_CLI || "playwright-cli").split(" ");
|
|
100
97
|
|
|
101
98
|
if (filteredArgs.length === 0) {
|
|
102
99
|
filteredArgs.push("--help");
|
|
@@ -108,7 +105,7 @@ export async function serve() {
|
|
|
108
105
|
const isOpenCmd = filteredArgs[0] === "open";
|
|
109
106
|
if (isOpenCmd) {
|
|
110
107
|
try {
|
|
111
|
-
const listProc = Bun.spawn([bin, "tab-list", "--extension", `-s=${namespacedSession}`], {
|
|
108
|
+
const listProc = Bun.spawn([bin, ...binArgs, "tab-list", "--extension", `-s=${namespacedSession}`], {
|
|
112
109
|
cwd: workDir,
|
|
113
110
|
stdin: "ignore",
|
|
114
111
|
stdout: "pipe",
|
|
@@ -150,7 +147,7 @@ export async function serve() {
|
|
|
150
147
|
...(clientName ? { PLAYWRIGHT_MCP_CLIENT_NAME: clientName } : {}),
|
|
151
148
|
...passthroughEnv,
|
|
152
149
|
};
|
|
153
|
-
const proc = Bun.spawn([bin, ...filteredArgs, "--extension", `-s=${namespacedSession}`], {
|
|
150
|
+
const proc = Bun.spawn([bin, ...binArgs, ...filteredArgs, "--extension", `-s=${namespacedSession}`], {
|
|
154
151
|
cwd: workDir,
|
|
155
152
|
stdin: "ignore",
|
|
156
153
|
stdout: "pipe",
|
|
@@ -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 = "";
|
|
@@ -96,7 +93,7 @@ export async function serve() {
|
|
|
96
93
|
});
|
|
97
94
|
const namespacedSession = clientSession ? `${sessionId}-${clientSession}` : sessionId;
|
|
98
95
|
|
|
99
|
-
const bin = process.env.PLAYWRIGHT_CLI || "playwright-cli";
|
|
96
|
+
const [bin, ...binArgs] = (process.env.PLAYWRIGHT_CLI || "playwright-cli").split(" ");
|
|
100
97
|
|
|
101
98
|
if (filteredArgs.length === 0) {
|
|
102
99
|
filteredArgs.push("--help");
|
|
@@ -108,7 +105,7 @@ export async function serve() {
|
|
|
108
105
|
const isOpenCmd = filteredArgs[0] === "open";
|
|
109
106
|
if (isOpenCmd) {
|
|
110
107
|
try {
|
|
111
|
-
const listProc = Bun.spawn([bin, "tab-list", "--extension", `-s=${namespacedSession}`], {
|
|
108
|
+
const listProc = Bun.spawn([bin, ...binArgs, "tab-list", "--extension", `-s=${namespacedSession}`], {
|
|
112
109
|
cwd: workDir,
|
|
113
110
|
stdin: "ignore",
|
|
114
111
|
stdout: "pipe",
|
|
@@ -150,7 +147,7 @@ export async function serve() {
|
|
|
150
147
|
...(clientName ? { PLAYWRIGHT_MCP_CLIENT_NAME: clientName } : {}),
|
|
151
148
|
...passthroughEnv,
|
|
152
149
|
};
|
|
153
|
-
const proc = Bun.spawn([bin, ...filteredArgs, "--extension", `-s=${namespacedSession}`], {
|
|
150
|
+
const proc = Bun.spawn([bin, ...binArgs, ...filteredArgs, "--extension", `-s=${namespacedSession}`], {
|
|
154
151
|
cwd: workDir,
|
|
155
152
|
stdin: "ignore",
|
|
156
153
|
stdout: "pipe",
|
|
@@ -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
|
});
|