aboutsummaryrefslogtreecommitdiff
path: root/src/setup.ts
blob: f99e9d9ece7dc1f4bc9dd852427b8f666a368a09 (plain)
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
/**
 * 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 { BrowserDaemon } from "./daemon";

export function registerSetupCommand(pi: ExtensionAPI, daemon: BrowserDaemon): void {
  pi.registerCommand("browser-setup", {
    description: "Connect pi to your Chrome browser",
    handler: async (_args, ctx) => {
      await runSetup(ctx, daemon);
    },
  });
}

async function runSetup(ctx: ExtensionContext, daemon: BrowserDaemon): Promise<void> {
  ctx.ui.notify("browser setup: checking Chrome...", "info");

  // Step 1: Check Chrome is running
  const chromeRunning = checkChromeRunning();
  if (!chromeRunning) {
    ctx.ui.notify(
      "Chrome/Chromium/Edge not detected. Please start your browser and retry /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");
  try {
    await daemon.start();
    ctx.ui.notify("Connected to Chrome ✓", "info");
  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err);
    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;
  }

  // Step 3: Verify with test navigation
  ctx.ui.notify("Testing browser control...", "info");
  try {
    await daemon.newTab("https://github.com");
    const info = await daemon.getPageInfo();
    if ("dialog" in info) {
      await daemon.cdp("Page.handleJavaScriptDialog", { accept: true });
    }
    ctx.ui.notify(
      `Browser control verified ✓\nNavigated to: ${"url" in info ? info.url : "github.com"}`,
      "info",
    );
  } catch (err) {
    ctx.ui.notify(
      `Browser connected but test navigation failed: ${err instanceof Error ? err.message : String(err)}`,
      "warning",
    );
  }
}

function checkChromeRunning(): boolean {
  try {
    if (process.platform === "darwin" || process.platform === "linux") {
      const out = execSync("ps -A -o comm=", { timeout: 5000 }).toString().toLowerCase();
      return ["google chrome", "chrome", "chromium", "microsoft edge", "msedge"].some(
        (n) => out.includes(n),
      );
    } 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;
}