aboutsummaryrefslogtreecommitdiff
path: root/src/index.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/index.ts')
-rw-r--r--src/index.ts83
1 files changed, 81 insertions, 2 deletions
diff --git a/src/index.ts b/src/index.ts
index 2b95697..152c94d 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -11,7 +11,9 @@ import { restoreDownloadBehavior } from "./domains/files";
import { createDaemonTransport } from "./daemon/transport";
import { setDebugClicks } from "./util/debug";
import { asBoolean, asString } from "./util/guards";
-import { describeCdpTarget, resolveCdpTarget } from "./cdp/endpoint";
+import { describeCdpTarget, isValidTargetValue, localAliases, resolveCdpTarget } from "./cdp/endpoint";
+import { clearTarget, readTarget, writeTarget } from "./cdp/target-store";
+import { ensureDaemon, stopDaemon } from "./daemon/spawn";
import { queryLiveWsUrl } from "./cdp/discovery";
export default function browserHarnessExtension(pi: ExtensionAPI): void {
@@ -47,7 +49,7 @@ export default function browserHarnessExtension(pi: ExtensionAPI): void {
// first browser_* tool runs. A plain "Disconnected" reads as a fault
// when it is really just "not needed yet", so say which it is, and show
// what we would connect to.
- const target = resolveCdpTarget();
+ const target = resolveCdpTarget(process.env, await readTarget());
const reachable =
target.kind === "remote" ? (await queryLiveWsUrl(target.port, 2_000, target.host)) !== null : null;
@@ -91,6 +93,83 @@ export default function browserHarnessExtension(pi: ExtensionAPI): void {
},
});
+ // FORK: switch which browser the harness drives, without leaving pi.
+ //
+ // The target is resolved per daemon process and the daemon holds the browser
+ // connection, so a switch has to replace that process — writing the store
+ // alone would leave tool calls going to the old browser while /browser-status
+ // reported the new one.
+ pi.registerCommand("browser-target", {
+ description: "Show or change which browser to drive (local, default, or host:port)",
+ handler: async (args, ctx) => {
+ const wanted = (typeof args === "string" ? args : "").trim();
+
+ if (wanted === "") {
+ const current = resolveCdpTarget(process.env, await readTarget());
+ ctx.ui.notify(
+ `Browser target: ${describeCdpTarget(current)}\n` +
+ `Change with: /browser-target local | default | host:port`,
+ "info",
+ );
+ return;
+ }
+
+ const clearing = wanted.toLowerCase() === "default";
+ if (!clearing && !isValidTargetValue(wanted)) {
+ ctx.ui.notify(
+ `Not a usable target: "${wanted}"\n` +
+ `Expected "host:port", "default", or one of ${localAliases().join(", ")}.`,
+ "error",
+ );
+ return;
+ }
+
+ const saved = clearing ? await clearTarget() : await writeTarget(wanted);
+ if (!saved.success) {
+ ctx.ui.notify(saved.error, "error");
+ return;
+ }
+
+ const resolved = resolveCdpTarget(process.env, await readTarget());
+
+ // Warn rather than silently disobey: the environment deliberately wins,
+ // so a stored choice made now would not take effect this session.
+ const envOverride = (process.env["BU_CDP_HTTP"] ?? "").trim();
+ if (envOverride !== "") {
+ ctx.ui.notify(
+ `Saved, but BU_CDP_HTTP=${envOverride} is set in this environment and takes precedence.\n` +
+ `Still using: ${describeCdpTarget(resolved)}`,
+ "warning",
+ );
+ return;
+ }
+
+ ctx.ui.notify(`Switching to ${describeCdpTarget(resolved)}...`, "info");
+
+ if (client) await client.stop();
+ if (!(await stopDaemon())) {
+ ctx.ui.notify("Saved, but the browser daemon did not stop — run /browser-setup to pick it up.", "warning");
+ return;
+ }
+ if (!(await ensureDaemon())) {
+ ctx.ui.notify("Saved, but the browser daemon did not come back. Run /browser-setup.", "error");
+ return;
+ }
+
+ if (!client) {
+ ctx.ui.notify(`Browser target set to ${describeCdpTarget(resolved)} ✓ — run /browser-setup to connect.`, "info");
+ return;
+ }
+ const started = await client.start();
+ ctx.ui.notify(
+ started.success
+ ? `Browser target set to ${describeCdpTarget(resolved)} ✓`
+ : `Target saved (${describeCdpTarget(resolved)}) but connecting failed: ${started.error.message}`,
+ started.success ? "info" : "error",
+ );
+ },
+ });
+
pi.registerCommand("browser-reload-daemon", {
description: "Restart the browser client",
handler: async (_args, ctx) => {