rechrome 1.0.0 → 1.1.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
@@ -10,3 +10,7 @@
10
10
 
11
11
  # Bind address for the server (default: 127.0.0.1, use 0.0.0.0 for remote access)
12
12
  # RECH_HOST=127.0.0.1
13
+
14
+ # Playwright MCP extension settings (can be set on server or client; client overrides server)
15
+ # PLAYWRIGHT_MCP_EXTENSION_ID=your-extension-id
16
+ # PLAYWRIGHT_MCP_EXTENSION_TOKEN=your-extension-token
package/README.md CHANGED
@@ -82,6 +82,8 @@ cp .env.example .env.local
82
82
  | `GEMINI_API_KEY` | Gemini API key for screenshot vision descriptions | — |
83
83
  | `PLAYWRIGHT_CLI` | Path to playwright-cli binary (recommended: `playwright-cli-multi-tab` for full multi-tab support) | `playwright-cli` |
84
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) | — |
85
87
 
86
88
  ### Remote access
87
89
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rechrome",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
package/rech.js CHANGED
@@ -70,6 +70,11 @@ export async function describeImage(imagePath: string): Promise<string | null> {
70
70
  }
71
71
  }
72
72
 
73
+ export const PASSTHROUGH_ENV_KEYS = [
74
+ "PLAYWRIGHT_MCP_EXTENSION_ID",
75
+ "PLAYWRIGHT_MCP_EXTENSION_TOKEN",
76
+ ] as const;
77
+
73
78
  export function log(msg: string) {
74
79
  mkdirSync(LOG_DIR, { recursive: true });
75
80
  const ts = new Date().toISOString();
@@ -143,6 +148,14 @@ async function getClientIdentity(): Promise<{ gitUrl?: string; hostname?: string
143
148
  return { hostname: hostname(), cwd };
144
149
  }
145
150
 
151
+ function getClientEnv(): Record<string, string> {
152
+ const env: Record<string, string> = {};
153
+ for (const key of PASSTHROUGH_ENV_KEYS) {
154
+ if (process.env[key]) env[key] = process.env[key];
155
+ }
156
+ return env;
157
+ }
158
+
146
159
  async function run(url: string, args: string[]) {
147
160
  const { key, host, port } = parseUrl(url);
148
161
  const identity = await getClientIdentity();
@@ -152,7 +165,7 @@ async function run(url: string, args: string[]) {
152
165
  const res = await fetch(`http://${host}:${port}/run`, {
153
166
  method: "POST",
154
167
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
155
- body: JSON.stringify({ args, identity }),
168
+ body: JSON.stringify({ args, identity, env: getClientEnv() }),
156
169
  signal: AbortSignal.timeout(70_000),
157
170
  }).catch((e) => {
158
171
  console.error(`[rech] ${e.message}`);
package/rech.ts CHANGED
@@ -70,6 +70,11 @@ export async function describeImage(imagePath: string): Promise<string | null> {
70
70
  }
71
71
  }
72
72
 
73
+ export const PASSTHROUGH_ENV_KEYS = [
74
+ "PLAYWRIGHT_MCP_EXTENSION_ID",
75
+ "PLAYWRIGHT_MCP_EXTENSION_TOKEN",
76
+ ] as const;
77
+
73
78
  export function log(msg: string) {
74
79
  mkdirSync(LOG_DIR, { recursive: true });
75
80
  const ts = new Date().toISOString();
@@ -143,6 +148,14 @@ async function getClientIdentity(): Promise<{ gitUrl?: string; hostname?: string
143
148
  return { hostname: hostname(), cwd };
144
149
  }
145
150
 
151
+ function getClientEnv(): Record<string, string> {
152
+ const env: Record<string, string> = {};
153
+ for (const key of PASSTHROUGH_ENV_KEYS) {
154
+ if (process.env[key]) env[key] = process.env[key];
155
+ }
156
+ return env;
157
+ }
158
+
146
159
  async function run(url: string, args: string[]) {
147
160
  const { key, host, port } = parseUrl(url);
148
161
  const identity = await getClientIdentity();
@@ -152,7 +165,7 @@ async function run(url: string, args: string[]) {
152
165
  const res = await fetch(`http://${host}:${port}/run`, {
153
166
  method: "POST",
154
167
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
155
- body: JSON.stringify({ args, identity }),
168
+ body: JSON.stringify({ args, identity, env: getClientEnv() }),
156
169
  signal: AbortSignal.timeout(70_000),
157
170
  }).catch((e) => {
158
171
  console.error(`[rech] ${e.message}`);
package/serve.js CHANGED
@@ -2,7 +2,7 @@ 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 } from "./rech.js";
5
+ import { log, parseUrl, getOrCreateUrl, authCheck, describeImage, RECH_DIR, PASSTHROUGH_ENV_KEYS } from "./rech.js";
6
6
 
7
7
  export function isUnderDir(base: string, candidate: string): boolean {
8
8
  const absBase = resolve(base) + "/";
@@ -44,6 +44,7 @@ export async function serve() {
44
44
  let args: string[];
45
45
  let sessionId: string;
46
46
  let clientName = "";
47
+ let clientEnv: Record<string, string> = {};
47
48
  if (Array.isArray(body)) {
48
49
  args = body;
49
50
  const clientAddr = `${req.headers.get("x-forwarded-for") || server.requestIP(req)?.address || "unknown"}`;
@@ -66,6 +67,12 @@ export async function serve() {
66
67
  clientName = clientAddr;
67
68
  log(`session from client IP fallback: ${clientAddr} -> ${sessionId}`);
68
69
  }
70
+ // Extract allowlisted env vars from client (client overrides server)
71
+ if (body.env && typeof body.env === "object") {
72
+ for (const key of PASSTHROUGH_ENV_KEYS) {
73
+ if (typeof body.env[key] === "string") clientEnv[key] = body.env[key];
74
+ }
75
+ }
69
76
  }
70
77
 
71
78
  let clientSession = "";
@@ -117,6 +124,13 @@ export async function serve() {
117
124
  }
118
125
  }
119
126
 
127
+ // Merge passthrough env: server .env.local defaults, then client overrides
128
+ const passthroughEnv: Record<string, string | undefined> = {};
129
+ for (const key of PASSTHROUGH_ENV_KEYS) {
130
+ if (process.env[key]) passthroughEnv[key] = process.env[key];
131
+ }
132
+ Object.assign(passthroughEnv, clientEnv);
133
+
120
134
  const childEnv: Record<string, string | undefined> = {
121
135
  PATH: process.env.PATH,
122
136
  HOME: process.env.HOME,
@@ -124,6 +138,7 @@ export async function serve() {
124
138
  DISPLAY: process.env.DISPLAY,
125
139
  XDG_RUNTIME_DIR: process.env.XDG_RUNTIME_DIR,
126
140
  ...(clientName ? { PLAYWRIGHT_MCP_CLIENT_NAME: clientName } : {}),
141
+ ...passthroughEnv,
127
142
  };
128
143
  const proc = Bun.spawn([bin, ...filteredArgs, "--extension", `-s=${namespacedSession}`], {
129
144
  cwd: workDir,
package/serve.ts CHANGED
@@ -2,7 +2,7 @@ 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 } from "./rech.ts";
5
+ import { log, parseUrl, getOrCreateUrl, authCheck, describeImage, RECH_DIR, PASSTHROUGH_ENV_KEYS } from "./rech.ts";
6
6
 
7
7
  export function isUnderDir(base: string, candidate: string): boolean {
8
8
  const absBase = resolve(base) + "/";
@@ -44,6 +44,7 @@ export async function serve() {
44
44
  let args: string[];
45
45
  let sessionId: string;
46
46
  let clientName = "";
47
+ let clientEnv: Record<string, string> = {};
47
48
  if (Array.isArray(body)) {
48
49
  args = body;
49
50
  const clientAddr = `${req.headers.get("x-forwarded-for") || server.requestIP(req)?.address || "unknown"}`;
@@ -66,6 +67,12 @@ export async function serve() {
66
67
  clientName = clientAddr;
67
68
  log(`session from client IP fallback: ${clientAddr} -> ${sessionId}`);
68
69
  }
70
+ // Extract allowlisted env vars from client (client overrides server)
71
+ if (body.env && typeof body.env === "object") {
72
+ for (const key of PASSTHROUGH_ENV_KEYS) {
73
+ if (typeof body.env[key] === "string") clientEnv[key] = body.env[key];
74
+ }
75
+ }
69
76
  }
70
77
 
71
78
  let clientSession = "";
@@ -117,6 +124,13 @@ export async function serve() {
117
124
  }
118
125
  }
119
126
 
127
+ // Merge passthrough env: server .env.local defaults, then client overrides
128
+ const passthroughEnv: Record<string, string | undefined> = {};
129
+ for (const key of PASSTHROUGH_ENV_KEYS) {
130
+ if (process.env[key]) passthroughEnv[key] = process.env[key];
131
+ }
132
+ Object.assign(passthroughEnv, clientEnv);
133
+
120
134
  const childEnv: Record<string, string | undefined> = {
121
135
  PATH: process.env.PATH,
122
136
  HOME: process.env.HOME,
@@ -124,6 +138,7 @@ export async function serve() {
124
138
  DISPLAY: process.env.DISPLAY,
125
139
  XDG_RUNTIME_DIR: process.env.XDG_RUNTIME_DIR,
126
140
  ...(clientName ? { PLAYWRIGHT_MCP_CLIENT_NAME: clientName } : {}),
141
+ ...passthroughEnv,
127
142
  };
128
143
  const proc = Bun.spawn([bin, ...filteredArgs, "--extension", `-s=${namespacedSession}`], {
129
144
  cwd: workDir,