src/setup.ts (view raw)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
/**
* Setup command — verifies Chrome is running with remote debugging
* and connects the pi agent to it.
*/
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
import { execSync } from "node:child_process";
import type { BrowserClient } from "./client";
export function registerSetupCommand(pi: ExtensionAPI, client: BrowserClient): void {
pi.registerCommand("browser-setup", {
description: "Connect pi to your Chrome browser",
handler: async (_args, ctx) => {
await runSetup(ctx, client);
},
});
}
async function runSetup(ctx: ExtensionContext, client: BrowserClient): Promise<void> {
ctx.ui.notify("browser setup: checking Chrome...", "info");
// Step 1: Check Chrome is running
const chromeRunning = checkChromeRunning();
if (!chromeRunning) {
ctx.ui.notify(
"No browser instance running. Please open your browser and then run /browser-setup.",
"error",
);
return;
}
ctx.ui.notify("Chrome is running ✓", "info");
// Step 2: Try to connect to Chrome DevTools
ctx.ui.notify("Connecting to Chrome DevTools...", "info");
const startResult = await client.start();
if (!startResult.success) {
const msg = startResult.error.message;
const lower = msg.toLowerCase();
if (
lower.includes("devtoolsactiveport") ||
lower.includes("remote debugging") ||
lower.includes("econnrefused") ||
lower.includes("cannot reach chrome devtools")
) {
ctx.ui.notify(
"Chrome remote debugging needs to be enabled.\n\n" +
"Open chrome://inspect/#remote-debugging in your browser, tick the\n" +
"\"Discover network targets\" / Allow checkbox, then run /browser-setup again.\n\n" +
"Or set BU_CDP_WS to a remote browser WebSocket URL.",
"warning",
);
return;
}
ctx.ui.notify(`Connection failed: ${msg}`, "error");
return;
}
ctx.ui.notify("Connected to Chrome ✓", "info");
// Step 3: Verify with test navigation
ctx.ui.notify("Testing browser control...", "info");
const tabResult = await client.newTab("https://github.com");
if (!tabResult.success) {
ctx.ui.notify(
`Browser connected but test navigation failed: ${tabResult.error.message}`,
"warning",
);
return;
}
const info = await client.pageInfo();
if (info.success && "dialog" in info.data) {
await client.session().call("Page.handleJavaScriptDialog", { accept: true });
}
const pageUrl = info.success && !("dialog" in info.data) ? info.data.url : "github.com";
ctx.ui.notify(`Browser control verified ✓\nNavigated to: ${pageUrl}`, "info");
}
function checkChromeRunning(): boolean {
try {
if (process.platform === "darwin") {
// Exact line matching — excludes "Google Chrome Helper" etc.
// that linger after the user quits the browser.
const out = execSync("ps -A -o comm=", { timeout: 5000 }).toString().toLowerCase();
const lines = out.split("\n").map((l) => l.trim()).filter(Boolean);
const browserProcesses = ["google chrome", "chromium", "microsoft edge"];
return lines.some((l) => {
if (l.includes("helper") || l.includes("renderer")) return false;
return browserProcesses.includes(l);
});
} else if (process.platform === "linux") {
// Use args to distinguish the main browser process from sub-processes
// (GPU, renderer, utility, etc.) which carry --type= flags.
const out = execSync("ps -A -o comm=,args=", { timeout: 5000 }).toString().toLowerCase();
const lines = out.split("\n").map((l) => l.trim()).filter(Boolean);
const browserComms = ["chrome", "chromium", "chromium-browser", "msedge", "microsoft-edge", "google-chrome"];
return lines.some((line) => {
const parts = line.split(/\s+/);
const comm = parts[0] ?? "";
if (!comm || !browserComms.includes(comm)) return false;
const isSubprocess = line.includes("--type=") && !line.includes("--type=browser");
return !isSubprocess;
});
} else if (process.platform === "win32") {
const out = execSync("tasklist", { timeout: 5000 }).toString().toLowerCase();
return ["chrome.exe", "msedge.exe"].some((n) => out.includes(n));
}
} catch {
// best-effort
}
return false;
}
|