rechrome 1.12.3 → 1.13.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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/rech.js +38 -10
  3. package/rech.ts +38 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rechrome",
3
- "version": "1.12.3",
3
+ "version": "1.13.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/snomiao/rechrome.git"
package/rech.js CHANGED
@@ -597,7 +597,7 @@ async function setup(opts: { profile?: string } = {}): Promise<void> {
597
597
  };
598
598
 
599
599
  // [1/4] Daemon
600
- console.log("\n[1/4] Setting up serve daemon...");
600
+ console.log("\n[1/4] Checking serve daemon...");
601
601
 
602
602
  // Bind address (persists to ~/.env.local as RECH_HOST).
603
603
  // Read the persisted value from ~/.env.local directly — process.env may be shadowed by nearer .env files.
@@ -630,15 +630,24 @@ async function setup(opts: { profile?: string } = {}): Promise<void> {
630
630
  const liveBindUnknown = !!authPing?.ok && !liveBind;
631
631
  const currentBind = liveBind || persistedBind;
632
632
 
633
+ // A healthy daemon already answering on our key needs no reinstall — don't re-prompt for it.
634
+ const daemonHealthy = !!(anonPing && authPing?.ok && !liveBindUnknown);
635
+ // An explicit RECH_HOST override that differs from the live bind is a deliberate rebind request.
636
+ const explicitRebind = !!process.env.RECH_HOST && process.env.RECH_HOST !== currentBind;
637
+
633
638
  // Non-TTY honors explicit process.env.RECH_HOST (shell or merged env stack) — matches the documented `RECH_HOST=0.0.0.0 rech setup` flow.
634
639
  let desiredBind = process.env.RECH_HOST || currentBind;
635
- if (isTTY) {
640
+ // Only prompt to (re)configure the bind when we actually need to set up the daemon. A running
641
+ // daemon is left alone unless the user explicitly asks for a different bind via RECH_HOST.
642
+ if (isTTY && (!daemonHealthy || explicitRebind)) {
636
643
  console.log(`\n Bind address (current: ${currentBind}):`);
637
644
  console.log(` 1. 127.0.0.1 (localhost only)`);
638
645
  console.log(` 2. 0.0.0.0 (all interfaces — HTTP plaintext, trust your network)`);
639
646
  const defaultBindChoice = currentBind === "0.0.0.0" ? "2" : "1";
640
647
  const bindAns = (await ask(` Choice [${defaultBindChoice}]: `, defaultBindChoice)).trim();
641
648
  desiredBind = bindAns === "2" || bindAns === "0.0.0.0" ? "0.0.0.0" : "127.0.0.1";
649
+ } else if (daemonHealthy) {
650
+ console.log(` Daemon already running at ${protocol}://${host}:${port} (bind: ${currentBind}) — skipping daemon setup`);
642
651
  }
643
652
  const bindChanged = desiredBind !== currentBind;
644
653
  const persistedChanged = desiredBind !== persistedBind;
@@ -774,11 +783,26 @@ async function setup(opts: { profile?: string } = {}): Promise<void> {
774
783
  console.log(` [agent] Find PLAYWRIGHT_MCP_EXTENSION_TOKEN=... on that page`);
775
784
  console.log(` [agent] Provide the token value on next stdin line:\n`);
776
785
  }
777
- const tokenInput = (await ask(" Paste token: ")).trim();
778
- const token = tokenInput.replace(/^.*?=/, "").trim();
779
- if (!token || token.length < 20) { console.error(" Invalid token (too short)"); return null; }
780
- console.log(" Token accepted");
781
- return { extId, token };
786
+ // Retry on empty/too-short paste — a truncated copy or a stale token shouldn't
787
+ // abort the whole setup. Bounded so a non-TTY agent with exhausted stdin can't spin.
788
+ const maxTries = isTTY ? 5 : 3;
789
+ for (let attempt = 1; attempt <= maxTries; attempt++) {
790
+ const tokenInput = (await ask(" Paste token: ")).trim();
791
+ const token = tokenInput.replace(/^.*?=/, "").trim();
792
+ const retriesLeft = maxTries - attempt;
793
+ if (!token) {
794
+ console.error(` No token entered.${retriesLeft ? " Copy the full PLAYWRIGHT_MCP_EXTENSION_TOKEN value and try again." : ""}`);
795
+ } else if (token.length < 20) {
796
+ console.error(` Token too short (${token.length} chars) — likely truncated when copying.${retriesLeft ? " Re-copy the full value and try again." : ""}`);
797
+ } else {
798
+ console.log(" Token accepted");
799
+ return { extId, token };
800
+ }
801
+ // Non-TTY with no input left: ask() won't block, so stop instead of burning retries on empty reads.
802
+ if (!isTTY && !tokenInput) break;
803
+ }
804
+ console.error(" No valid token provided — aborting");
805
+ return null;
782
806
  }
783
807
 
784
808
  // [2/4] Primary profile
@@ -809,12 +833,16 @@ async function setup(opts: { profile?: string } = {}): Promise<void> {
809
833
  const pwdEnvPath = join(process.cwd(), ".env.local");
810
834
  const pwdRechPath = join(process.cwd(), ".rechrome", ".env.local");
811
835
  const homeEnvPath = join(process.env.HOME!, ".env.local");
836
+ // Show whether each target already exists so it's clear we'll update (merge) vs create.
837
+ const tag = async (p: string) => (await file(p).exists()) ? "exists → will update" : "new file";
838
+ const [pwdTag, pwdRechTag, homeTag] = await Promise.all([tag(pwdEnvPath), tag(pwdRechPath), tag(homeEnvPath)]);
812
839
  const saveChoice = (await ask(
813
- `Save to:\n 1. ${pwdEnvPath} (current dir) [default]\n 2. ${pwdRechPath} (current dir, rechrome-only)\n 3. ${homeEnvPath} (user home)\n 4. Skip (already copied)\n\n Choice [1]: `
840
+ `Save to:\n 1. ${pwdEnvPath} (current dir) [${pwdTag}] [default]\n 2. ${pwdRechPath} (current dir, rechrome-only) [${pwdRechTag}]\n 3. ${homeEnvPath} (user home) [${homeTag}]\n 4. Skip (already copied)\n\n Choice [1]: `
814
841
  )).trim();
815
842
  if (saveChoice !== "4") {
816
843
  const globalEnvPath = saveChoice === "3" ? homeEnvPath : saveChoice === "2" ? pwdRechPath : pwdEnvPath;
817
844
  if (saveChoice === "2") mkdirSync(join(process.cwd(), ".rechrome"), { recursive: true });
845
+ const existedBefore = await file(globalEnvPath).exists();
818
846
  const existing = await file(globalEnvPath).text().catch(() => "");
819
847
  const keysToRemove = ["PLAYWRIGHT_MCP_USER_DATA_DIR", "PLAYWRIGHT_MCP_EXTENSION_ID", "PLAYWRIGHT_MCP_EXTENSION_TOKEN", "PLAYWRIGHT_MCP_PROFILE_DIRECTORY"];
820
848
  let lines = existing.trimEnd().split("\n").filter(l => !keysToRemove.some(k => l.startsWith(`${k}=`)));
@@ -822,7 +850,7 @@ async function setup(opts: { profile?: string } = {}): Promise<void> {
822
850
  if (rechIdx >= 0) lines[rechIdx] = newLine;
823
851
  else lines.push(newLine);
824
852
  await Bun.write(globalEnvPath, lines.join("\n").trim() + "\n");
825
- console.log(`\nSaved to ${globalEnvPath}`);
853
+ console.log(`\n${existedBefore ? "Updated" : "Created"} ${globalEnvPath}`);
826
854
  }
827
855
 
828
856
  // Save primary to token registry
@@ -850,7 +878,7 @@ async function setup(opts: { profile?: string } = {}): Promise<void> {
850
878
  }
851
879
  rl?.close();
852
880
  envWatcher?.close();
853
- console.log(`\nDone! Test with:\n rech eval "() => document.title"`);
881
+ console.log(`\nDone! Test with:\n rech open github.com/snomiao`);
854
882
  }
855
883
 
856
884
  async function status(): Promise<void> {
package/rech.ts CHANGED
@@ -597,7 +597,7 @@ async function setup(opts: { profile?: string } = {}): Promise<void> {
597
597
  };
598
598
 
599
599
  // [1/4] Daemon
600
- console.log("\n[1/4] Setting up serve daemon...");
600
+ console.log("\n[1/4] Checking serve daemon...");
601
601
 
602
602
  // Bind address (persists to ~/.env.local as RECH_HOST).
603
603
  // Read the persisted value from ~/.env.local directly — process.env may be shadowed by nearer .env files.
@@ -630,15 +630,24 @@ async function setup(opts: { profile?: string } = {}): Promise<void> {
630
630
  const liveBindUnknown = !!authPing?.ok && !liveBind;
631
631
  const currentBind = liveBind || persistedBind;
632
632
 
633
+ // A healthy daemon already answering on our key needs no reinstall — don't re-prompt for it.
634
+ const daemonHealthy = !!(anonPing && authPing?.ok && !liveBindUnknown);
635
+ // An explicit RECH_HOST override that differs from the live bind is a deliberate rebind request.
636
+ const explicitRebind = !!process.env.RECH_HOST && process.env.RECH_HOST !== currentBind;
637
+
633
638
  // Non-TTY honors explicit process.env.RECH_HOST (shell or merged env stack) — matches the documented `RECH_HOST=0.0.0.0 rech setup` flow.
634
639
  let desiredBind = process.env.RECH_HOST || currentBind;
635
- if (isTTY) {
640
+ // Only prompt to (re)configure the bind when we actually need to set up the daemon. A running
641
+ // daemon is left alone unless the user explicitly asks for a different bind via RECH_HOST.
642
+ if (isTTY && (!daemonHealthy || explicitRebind)) {
636
643
  console.log(`\n Bind address (current: ${currentBind}):`);
637
644
  console.log(` 1. 127.0.0.1 (localhost only)`);
638
645
  console.log(` 2. 0.0.0.0 (all interfaces — HTTP plaintext, trust your network)`);
639
646
  const defaultBindChoice = currentBind === "0.0.0.0" ? "2" : "1";
640
647
  const bindAns = (await ask(` Choice [${defaultBindChoice}]: `, defaultBindChoice)).trim();
641
648
  desiredBind = bindAns === "2" || bindAns === "0.0.0.0" ? "0.0.0.0" : "127.0.0.1";
649
+ } else if (daemonHealthy) {
650
+ console.log(` Daemon already running at ${protocol}://${host}:${port} (bind: ${currentBind}) — skipping daemon setup`);
642
651
  }
643
652
  const bindChanged = desiredBind !== currentBind;
644
653
  const persistedChanged = desiredBind !== persistedBind;
@@ -774,11 +783,26 @@ async function setup(opts: { profile?: string } = {}): Promise<void> {
774
783
  console.log(` [agent] Find PLAYWRIGHT_MCP_EXTENSION_TOKEN=... on that page`);
775
784
  console.log(` [agent] Provide the token value on next stdin line:\n`);
776
785
  }
777
- const tokenInput = (await ask(" Paste token: ")).trim();
778
- const token = tokenInput.replace(/^.*?=/, "").trim();
779
- if (!token || token.length < 20) { console.error(" Invalid token (too short)"); return null; }
780
- console.log(" Token accepted");
781
- return { extId, token };
786
+ // Retry on empty/too-short paste — a truncated copy or a stale token shouldn't
787
+ // abort the whole setup. Bounded so a non-TTY agent with exhausted stdin can't spin.
788
+ const maxTries = isTTY ? 5 : 3;
789
+ for (let attempt = 1; attempt <= maxTries; attempt++) {
790
+ const tokenInput = (await ask(" Paste token: ")).trim();
791
+ const token = tokenInput.replace(/^.*?=/, "").trim();
792
+ const retriesLeft = maxTries - attempt;
793
+ if (!token) {
794
+ console.error(` No token entered.${retriesLeft ? " Copy the full PLAYWRIGHT_MCP_EXTENSION_TOKEN value and try again." : ""}`);
795
+ } else if (token.length < 20) {
796
+ console.error(` Token too short (${token.length} chars) — likely truncated when copying.${retriesLeft ? " Re-copy the full value and try again." : ""}`);
797
+ } else {
798
+ console.log(" Token accepted");
799
+ return { extId, token };
800
+ }
801
+ // Non-TTY with no input left: ask() won't block, so stop instead of burning retries on empty reads.
802
+ if (!isTTY && !tokenInput) break;
803
+ }
804
+ console.error(" No valid token provided — aborting");
805
+ return null;
782
806
  }
783
807
 
784
808
  // [2/4] Primary profile
@@ -809,12 +833,16 @@ async function setup(opts: { profile?: string } = {}): Promise<void> {
809
833
  const pwdEnvPath = join(process.cwd(), ".env.local");
810
834
  const pwdRechPath = join(process.cwd(), ".rechrome", ".env.local");
811
835
  const homeEnvPath = join(process.env.HOME!, ".env.local");
836
+ // Show whether each target already exists so it's clear we'll update (merge) vs create.
837
+ const tag = async (p: string) => (await file(p).exists()) ? "exists → will update" : "new file";
838
+ const [pwdTag, pwdRechTag, homeTag] = await Promise.all([tag(pwdEnvPath), tag(pwdRechPath), tag(homeEnvPath)]);
812
839
  const saveChoice = (await ask(
813
- `Save to:\n 1. ${pwdEnvPath} (current dir) [default]\n 2. ${pwdRechPath} (current dir, rechrome-only)\n 3. ${homeEnvPath} (user home)\n 4. Skip (already copied)\n\n Choice [1]: `
840
+ `Save to:\n 1. ${pwdEnvPath} (current dir) [${pwdTag}] [default]\n 2. ${pwdRechPath} (current dir, rechrome-only) [${pwdRechTag}]\n 3. ${homeEnvPath} (user home) [${homeTag}]\n 4. Skip (already copied)\n\n Choice [1]: `
814
841
  )).trim();
815
842
  if (saveChoice !== "4") {
816
843
  const globalEnvPath = saveChoice === "3" ? homeEnvPath : saveChoice === "2" ? pwdRechPath : pwdEnvPath;
817
844
  if (saveChoice === "2") mkdirSync(join(process.cwd(), ".rechrome"), { recursive: true });
845
+ const existedBefore = await file(globalEnvPath).exists();
818
846
  const existing = await file(globalEnvPath).text().catch(() => "");
819
847
  const keysToRemove = ["PLAYWRIGHT_MCP_USER_DATA_DIR", "PLAYWRIGHT_MCP_EXTENSION_ID", "PLAYWRIGHT_MCP_EXTENSION_TOKEN", "PLAYWRIGHT_MCP_PROFILE_DIRECTORY"];
820
848
  let lines = existing.trimEnd().split("\n").filter(l => !keysToRemove.some(k => l.startsWith(`${k}=`)));
@@ -822,7 +850,7 @@ async function setup(opts: { profile?: string } = {}): Promise<void> {
822
850
  if (rechIdx >= 0) lines[rechIdx] = newLine;
823
851
  else lines.push(newLine);
824
852
  await Bun.write(globalEnvPath, lines.join("\n").trim() + "\n");
825
- console.log(`\nSaved to ${globalEnvPath}`);
853
+ console.log(`\n${existedBefore ? "Updated" : "Created"} ${globalEnvPath}`);
826
854
  }
827
855
 
828
856
  // Save primary to token registry
@@ -850,7 +878,7 @@ async function setup(opts: { profile?: string } = {}): Promise<void> {
850
878
  }
851
879
  rl?.close();
852
880
  envWatcher?.close();
853
- console.log(`\nDone! Test with:\n rech eval "() => document.title"`);
881
+ console.log(`\nDone! Test with:\n rech open github.com/snomiao`);
854
882
  }
855
883
 
856
884
  async function status(): Promise<void> {