rechrome 1.31.1 → 1.31.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rechrome",
3
- "version": "1.31.1",
3
+ "version": "1.31.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/snomiao/rechrome.git"
package/rechrome.js CHANGED
@@ -1922,14 +1922,31 @@ async function ensureDaemonManager(ask: (q: string, def?: string) => Promise<str
1922
1922
  daemonManager();
1923
1923
  }
1924
1924
 
1925
+ /**
1926
+ * Rewriting RECHROME_URL for a new daemon key must not drop the saved `?profile=`: without it
1927
+ * every client loses its Chrome profile (and a setup aborted before its final save step used
1928
+ * to leave it that way).
1929
+ */
1930
+ export function keepProfileParam(next: string, previous?: string): string {
1931
+ try {
1932
+ const url = new URL(next);
1933
+ // A .env.local value may be quoted: RECHROME_URL="http://…/?profile=work".
1934
+ const raw = previous?.trim().replace(/^(['"])(.*)\1$/, "$2");
1935
+ const profile = raw ? new URL(raw).searchParams.get("profile") : null;
1936
+ if (profile && !url.searchParams.has("profile")) url.searchParams.set("profile", profile);
1937
+ return url.toString();
1938
+ } catch { return next; }
1939
+ }
1940
+
1925
1941
  export async function daemonInstall(serveUrl: string): Promise<void> {
1926
1942
  // Resolve the manager first so a missing dependency fails before config is mutated.
1927
1943
  const mgr = daemonManager();
1928
1944
  // Persist the URL for future clients without an explicit environment override.
1929
1945
  // The daemon's explicit environment takes precedence over this saved default.
1930
1946
  const envRaw = await file(globalEnvFile).text().catch(() => "");
1931
- const filtered = envRaw.trimEnd().split("\n").filter(l => !l.startsWith(`${ENV_KEY}=`));
1932
- await Bun.write(globalEnvFile, [...filtered, `${ENV_KEY}=${serveUrl}`, ""].join("\n"));
1947
+ const lines = envRaw.trimEnd().split("\n");
1948
+ const filtered = lines.filter(l => !l.startsWith(`${ENV_KEY}=`));
1949
+ await Bun.write(globalEnvFile, [...filtered, `${ENV_KEY}=${keepProfileParam(serveUrl, lines.find(l => l.startsWith(`${ENV_KEY}=`))?.slice(ENV_KEY.length + 1))}`, ""].join("\n"));
1933
1950
 
1934
1951
  const home = HOME;
1935
1952
  const bunBin = Bun.which("bun") ?? process.execPath;
package/rechrome.ts CHANGED
@@ -1922,14 +1922,31 @@ async function ensureDaemonManager(ask: (q: string, def?: string) => Promise<str
1922
1922
  daemonManager();
1923
1923
  }
1924
1924
 
1925
+ /**
1926
+ * Rewriting RECHROME_URL for a new daemon key must not drop the saved `?profile=`: without it
1927
+ * every client loses its Chrome profile (and a setup aborted before its final save step used
1928
+ * to leave it that way).
1929
+ */
1930
+ export function keepProfileParam(next: string, previous?: string): string {
1931
+ try {
1932
+ const url = new URL(next);
1933
+ // A .env.local value may be quoted: RECHROME_URL="http://…/?profile=work".
1934
+ const raw = previous?.trim().replace(/^(['"])(.*)\1$/, "$2");
1935
+ const profile = raw ? new URL(raw).searchParams.get("profile") : null;
1936
+ if (profile && !url.searchParams.has("profile")) url.searchParams.set("profile", profile);
1937
+ return url.toString();
1938
+ } catch { return next; }
1939
+ }
1940
+
1925
1941
  export async function daemonInstall(serveUrl: string): Promise<void> {
1926
1942
  // Resolve the manager first so a missing dependency fails before config is mutated.
1927
1943
  const mgr = daemonManager();
1928
1944
  // Persist the URL for future clients without an explicit environment override.
1929
1945
  // The daemon's explicit environment takes precedence over this saved default.
1930
1946
  const envRaw = await file(globalEnvFile).text().catch(() => "");
1931
- const filtered = envRaw.trimEnd().split("\n").filter(l => !l.startsWith(`${ENV_KEY}=`));
1932
- await Bun.write(globalEnvFile, [...filtered, `${ENV_KEY}=${serveUrl}`, ""].join("\n"));
1947
+ const lines = envRaw.trimEnd().split("\n");
1948
+ const filtered = lines.filter(l => !l.startsWith(`${ENV_KEY}=`));
1949
+ await Bun.write(globalEnvFile, [...filtered, `${ENV_KEY}=${keepProfileParam(serveUrl, lines.find(l => l.startsWith(`${ENV_KEY}=`))?.slice(ENV_KEY.length + 1))}`, ""].join("\n"));
1933
1950
 
1934
1951
  const home = HOME;
1935
1952
  const bunBin = Bun.which("bun") ?? process.execPath;
package/serve.js CHANGED
@@ -691,6 +691,39 @@ export async function serve() {
691
691
  const isOpenNoUrl = isOpenCmd && filteredArgs.length === 1;
692
692
  if (isOpenNoUrl) filteredArgs.push("about:blank");
693
693
 
694
+ // Merge passthrough env: server .env.local defaults, then client overrides
695
+ const passthroughEnv: Record<string, string | undefined> = {};
696
+ for (const key of PASSTHROUGH_ENV_KEYS) {
697
+ if (process.env[key]) passthroughEnv[key] = process.env[key];
698
+ }
699
+ Object.assign(passthroughEnv, clientEnv, profileEnv);
700
+
701
+ // Resolve profile name/email → directory name
702
+ if (!scoped && passthroughEnv.PLAYWRIGHT_MCP_PROFILE_DIRECTORY) {
703
+ const resolved = await resolveProfileDirectory(passthroughEnv.PLAYWRIGHT_MCP_PROFILE_DIRECTORY);
704
+ if (resolved !== passthroughEnv.PLAYWRIGHT_MCP_PROFILE_DIRECTORY)
705
+ log(`profile resolved: "${passthroughEnv.PLAYWRIGHT_MCP_PROFILE_DIRECTORY}" → "${resolved}"`);
706
+ passthroughEnv.PLAYWRIGHT_MCP_PROFILE_DIRECTORY = resolved;
707
+ }
708
+
709
+ // Never fall back silently: without extension credentials (a registered profile) or a managed
710
+ // user-data dir, `open` would launch a separate headless browser with a throwaway profile —
711
+ // the command "succeeds" and nothing shows up in the user's Chrome.
712
+ const extensionMode = !!(passthroughEnv.PLAYWRIGHT_MCP_EXTENSION_ID && passthroughEnv.PLAYWRIGHT_MCP_EXTENSION_TOKEN);
713
+ // Checked before the existing-session shortcuts below: reusing a leftover headless session
714
+ // (bare `open` answered with its tab list, or `open <url>` rewritten to goto) is the same
715
+ // silent fallback.
716
+ if (isOpenCmd && !extensionMode && !passthroughEnv.PLAYWRIGHT_MCP_USER_DATA_DIR) {
717
+ const requested = passthroughEnv.PLAYWRIGHT_MCP_PROFILE_DIRECTORY;
718
+ const registered = Object.keys(await readTokenRegistry().catch(() => ({})));
719
+ const known = registered.length ? `Registered here: ${registered.join(", ")}` : "No profiles are registered here yet: rech setup";
720
+ const stderr = requested
721
+ ? `[rech] profile "${requested}" has no extension token on this machine, so rech would open a separate headless browser instead of your Chrome. Refusing.\n Register it: rech setup --profile ${JSON.stringify(requested)}\n ${known}\n`
722
+ : `[rech] no Chrome profile selected, so rech would open a separate headless browser instead of your Chrome. Refusing.\n Pick one: rech --profile <email> open <url>, or add ?profile=<email> to RECHROME_URL\n ${known}\n`;
723
+ log(`refused open without a profile (session=${namespacedSession}, requested=${requested ?? "-"})`);
724
+ return Response.json({ status: 1, stdout: "", stderr, files: [] });
725
+ }
726
+
694
727
  // open against an existing session: bare `open` returns a tab-list hint; `open <url>`
695
728
  // converts to `goto` to reuse the live browser. (Guarding on filteredArgs.length===1
696
729
  // was dead — about:blank/<url> is already appended above, so length is always >=2.)
@@ -730,21 +763,6 @@ export async function serve() {
730
763
  }
731
764
  }
732
765
 
733
- // Merge passthrough env: server .env.local defaults, then client overrides
734
- const passthroughEnv: Record<string, string | undefined> = {};
735
- for (const key of PASSTHROUGH_ENV_KEYS) {
736
- if (process.env[key]) passthroughEnv[key] = process.env[key];
737
- }
738
- Object.assign(passthroughEnv, clientEnv, profileEnv);
739
-
740
- // Resolve profile name/email → directory name
741
- if (!scoped && passthroughEnv.PLAYWRIGHT_MCP_PROFILE_DIRECTORY) {
742
- const resolved = await resolveProfileDirectory(passthroughEnv.PLAYWRIGHT_MCP_PROFILE_DIRECTORY);
743
- if (resolved !== passthroughEnv.PLAYWRIGHT_MCP_PROFILE_DIRECTORY)
744
- log(`profile resolved: "${passthroughEnv.PLAYWRIGHT_MCP_PROFILE_DIRECTORY}" → "${resolved}"`);
745
- passthroughEnv.PLAYWRIGHT_MCP_PROFILE_DIRECTORY = resolved;
746
- }
747
-
748
766
  const childEnv: Record<string, string | undefined> = {
749
767
  PATH: process.env.PATH,
750
768
  HOME: HOME,
package/serve.ts CHANGED
@@ -691,6 +691,39 @@ export async function serve() {
691
691
  const isOpenNoUrl = isOpenCmd && filteredArgs.length === 1;
692
692
  if (isOpenNoUrl) filteredArgs.push("about:blank");
693
693
 
694
+ // Merge passthrough env: server .env.local defaults, then client overrides
695
+ const passthroughEnv: Record<string, string | undefined> = {};
696
+ for (const key of PASSTHROUGH_ENV_KEYS) {
697
+ if (process.env[key]) passthroughEnv[key] = process.env[key];
698
+ }
699
+ Object.assign(passthroughEnv, clientEnv, profileEnv);
700
+
701
+ // Resolve profile name/email → directory name
702
+ if (!scoped && passthroughEnv.PLAYWRIGHT_MCP_PROFILE_DIRECTORY) {
703
+ const resolved = await resolveProfileDirectory(passthroughEnv.PLAYWRIGHT_MCP_PROFILE_DIRECTORY);
704
+ if (resolved !== passthroughEnv.PLAYWRIGHT_MCP_PROFILE_DIRECTORY)
705
+ log(`profile resolved: "${passthroughEnv.PLAYWRIGHT_MCP_PROFILE_DIRECTORY}" → "${resolved}"`);
706
+ passthroughEnv.PLAYWRIGHT_MCP_PROFILE_DIRECTORY = resolved;
707
+ }
708
+
709
+ // Never fall back silently: without extension credentials (a registered profile) or a managed
710
+ // user-data dir, `open` would launch a separate headless browser with a throwaway profile —
711
+ // the command "succeeds" and nothing shows up in the user's Chrome.
712
+ const extensionMode = !!(passthroughEnv.PLAYWRIGHT_MCP_EXTENSION_ID && passthroughEnv.PLAYWRIGHT_MCP_EXTENSION_TOKEN);
713
+ // Checked before the existing-session shortcuts below: reusing a leftover headless session
714
+ // (bare `open` answered with its tab list, or `open <url>` rewritten to goto) is the same
715
+ // silent fallback.
716
+ if (isOpenCmd && !extensionMode && !passthroughEnv.PLAYWRIGHT_MCP_USER_DATA_DIR) {
717
+ const requested = passthroughEnv.PLAYWRIGHT_MCP_PROFILE_DIRECTORY;
718
+ const registered = Object.keys(await readTokenRegistry().catch(() => ({})));
719
+ const known = registered.length ? `Registered here: ${registered.join(", ")}` : "No profiles are registered here yet: rech setup";
720
+ const stderr = requested
721
+ ? `[rech] profile "${requested}" has no extension token on this machine, so rech would open a separate headless browser instead of your Chrome. Refusing.\n Register it: rech setup --profile ${JSON.stringify(requested)}\n ${known}\n`
722
+ : `[rech] no Chrome profile selected, so rech would open a separate headless browser instead of your Chrome. Refusing.\n Pick one: rech --profile <email> open <url>, or add ?profile=<email> to RECHROME_URL\n ${known}\n`;
723
+ log(`refused open without a profile (session=${namespacedSession}, requested=${requested ?? "-"})`);
724
+ return Response.json({ status: 1, stdout: "", stderr, files: [] });
725
+ }
726
+
694
727
  // open against an existing session: bare `open` returns a tab-list hint; `open <url>`
695
728
  // converts to `goto` to reuse the live browser. (Guarding on filteredArgs.length===1
696
729
  // was dead — about:blank/<url> is already appended above, so length is always >=2.)
@@ -730,21 +763,6 @@ export async function serve() {
730
763
  }
731
764
  }
732
765
 
733
- // Merge passthrough env: server .env.local defaults, then client overrides
734
- const passthroughEnv: Record<string, string | undefined> = {};
735
- for (const key of PASSTHROUGH_ENV_KEYS) {
736
- if (process.env[key]) passthroughEnv[key] = process.env[key];
737
- }
738
- Object.assign(passthroughEnv, clientEnv, profileEnv);
739
-
740
- // Resolve profile name/email → directory name
741
- if (!scoped && passthroughEnv.PLAYWRIGHT_MCP_PROFILE_DIRECTORY) {
742
- const resolved = await resolveProfileDirectory(passthroughEnv.PLAYWRIGHT_MCP_PROFILE_DIRECTORY);
743
- if (resolved !== passthroughEnv.PLAYWRIGHT_MCP_PROFILE_DIRECTORY)
744
- log(`profile resolved: "${passthroughEnv.PLAYWRIGHT_MCP_PROFILE_DIRECTORY}" → "${resolved}"`);
745
- passthroughEnv.PLAYWRIGHT_MCP_PROFILE_DIRECTORY = resolved;
746
- }
747
-
748
766
  const childEnv: Record<string, string | undefined> = {
749
767
  PATH: process.env.PATH,
750
768
  HOME: HOME,