@browserbasehq/browse-cli 0.3.0-alpha-e81dde715786d70e65524c8b7ce10e00a909efd9 → 0.4.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.
- package/README.md +52 -6
- package/dist/index.js +386 -74
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -173,11 +173,47 @@ browse env
|
|
|
173
173
|
# Switch current session to Browserbase (restarts daemon if needed)
|
|
174
174
|
browse env remote
|
|
175
175
|
|
|
176
|
-
# Switch back to local Chrome
|
|
176
|
+
# Switch back to local Chrome (auto-discovers existing Chrome, falls back to isolated)
|
|
177
177
|
browse env local
|
|
178
178
|
```
|
|
179
179
|
|
|
180
|
-
|
|
180
|
+
#### Local Browser Strategies
|
|
181
|
+
|
|
182
|
+
By default, `browse env local` auto-discovers an already-running Chrome with remote
|
|
183
|
+
debugging enabled. This lets agents use your existing cookies, logins, and browser state.
|
|
184
|
+
If no debuggable Chrome is found, it falls back to launching an isolated browser.
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
# Auto-discover local Chrome, fallback to isolated (default)
|
|
188
|
+
browse env local
|
|
189
|
+
|
|
190
|
+
# Force a clean isolated browser (no auto-discovery)
|
|
191
|
+
browse env local --isolated
|
|
192
|
+
|
|
193
|
+
# Attach to a specific CDP target (port or URL)
|
|
194
|
+
browse env local 9222
|
|
195
|
+
browse env local ws://localhost:9222/devtools/browser/...
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Auto-discovery checks:
|
|
199
|
+
1. `DevToolsActivePort` files in well-known Chrome/Chromium/Brave user-data directories
|
|
200
|
+
2. Common debugging ports (9222, 9229)
|
|
201
|
+
|
|
202
|
+
To make your Chrome discoverable:
|
|
203
|
+
|
|
204
|
+
1. Open `chrome://inspect/#remote-debugging`
|
|
205
|
+
2. Check the box **"Allow remote debugging for this browser instance"**
|
|
206
|
+
|
|
207
|
+
For more information, see the [Chrome DevTools docs](https://developer.chrome.com/blog/chrome-devtools-mcp-debug-your-browser-session).
|
|
208
|
+
|
|
209
|
+
Use `browse status` to see which strategy was resolved:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
browse status
|
|
213
|
+
# {"running":true,"session":"default","mode":"local","localStrategy":"auto","localSource":"attached-existing","resolvedCdpUrl":"ws://..."}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
#### General Behavior
|
|
181
217
|
|
|
182
218
|
- Environment is scoped per `--session`
|
|
183
219
|
- `browse env <target>` persists an override and restarts the daemon
|
|
@@ -193,7 +229,7 @@ Behavior details:
|
|
|
193
229
|
| `--session <name>` | Session name for multiple browsers (default: "default") |
|
|
194
230
|
| `--headless` | Run Chrome in headless mode |
|
|
195
231
|
| `--headed` | Run Chrome with visible window (default) |
|
|
196
|
-
| `--ws <url>` |
|
|
232
|
+
| `--ws <url\|port>` | One-shot CDP connection (bypasses daemon) |
|
|
197
233
|
| `--json` | Output as JSON |
|
|
198
234
|
|
|
199
235
|
## Environment Variables
|
|
@@ -249,11 +285,21 @@ browse --session personal open https://twitter.com
|
|
|
249
285
|
|
|
250
286
|
Connect to an existing Chrome instance:
|
|
251
287
|
|
|
288
|
+
To make your Chrome discoverable:
|
|
289
|
+
|
|
290
|
+
1. Open `chrome://inspect/#remote-debugging`
|
|
291
|
+
2. Check the box **"Allow remote debugging for this browser instance"**
|
|
292
|
+
3. Re-run the CLI and it will auto-connect!
|
|
293
|
+
|
|
294
|
+
For more information, see the [Chrome DevTools docs](https://developer.chrome.com/blog/chrome-devtools-mcp-debug-your-browser-session).
|
|
295
|
+
|
|
252
296
|
```bash
|
|
253
|
-
#
|
|
254
|
-
|
|
297
|
+
# Auto-discover Chrome with remote debugging enabled
|
|
298
|
+
browse env local
|
|
299
|
+
browse open https://example.com
|
|
255
300
|
|
|
256
|
-
#
|
|
301
|
+
# Or target a specific port / WebSocket URL
|
|
302
|
+
browse env local 9222
|
|
257
303
|
browse --ws ws://localhost:9222/devtools/browser/... open https://example.com
|
|
258
304
|
```
|
|
259
305
|
|
package/dist/index.js
CHANGED
|
@@ -34,9 +34,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
34
34
|
));
|
|
35
35
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
36
36
|
|
|
37
|
-
// ../../node_modules/.pnpm/tsup@8.5.1_jiti@
|
|
37
|
+
// ../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_tsx@4.19.4_typescript@5.8.3_yaml@2.8.3/node_modules/tsup/assets/cjs_shims.js
|
|
38
38
|
var init_cjs_shims = __esm({
|
|
39
|
-
"../../node_modules/.pnpm/tsup@8.5.1_jiti@
|
|
39
|
+
"../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_tsx@4.19.4_typescript@5.8.3_yaml@2.8.3/node_modules/tsup/assets/cjs_shims.js"() {
|
|
40
40
|
"use strict";
|
|
41
41
|
}
|
|
42
42
|
});
|
|
@@ -94180,7 +94180,7 @@ var init_page2 = __esm({
|
|
|
94180
94180
|
});
|
|
94181
94181
|
try {
|
|
94182
94182
|
if (this.apiClient) {
|
|
94183
|
-
const result = await this.apiClient.goto(url2, { waitUntil: options?.waitUntil }, this.mainFrameId());
|
|
94183
|
+
const result = await this.apiClient.goto(url2, { waitUntil: options?.waitUntil, timeout: options?.timeoutMs }, this.mainFrameId());
|
|
94184
94184
|
this._currentUrl = url2;
|
|
94185
94185
|
if (isSerializableResponse(result)) {
|
|
94186
94186
|
return Response2.fromSerializable(result, {
|
|
@@ -114574,7 +114574,7 @@ var OpenAICUAClient = class extends AgentClient {
|
|
|
114574
114574
|
const messageList = [];
|
|
114575
114575
|
let finalMessage = "";
|
|
114576
114576
|
this.reasoningItems.clear();
|
|
114577
|
-
let inputItems = this.createInitialInputItems(instruction);
|
|
114577
|
+
let inputItems = await this.createInitialInputItems(instruction);
|
|
114578
114578
|
let previousResponseId = void 0;
|
|
114579
114579
|
let totalInputTokens = 0;
|
|
114580
114580
|
let totalOutputTokens = 0;
|
|
@@ -114781,17 +114781,37 @@ var OpenAICUAClient = class extends AgentClient {
|
|
|
114781
114781
|
isFunctionCallItem(item) {
|
|
114782
114782
|
return item.type === "function_call" && "call_id" in item && "name" in item && "arguments" in item;
|
|
114783
114783
|
}
|
|
114784
|
-
createInitialInputItems(instruction) {
|
|
114785
|
-
|
|
114786
|
-
|
|
114784
|
+
async createInitialInputItems(instruction) {
|
|
114785
|
+
const inputItems = [];
|
|
114786
|
+
if (this.userProvidedInstructions) {
|
|
114787
|
+
const systemMessage = {
|
|
114787
114788
|
role: "system",
|
|
114788
114789
|
content: this.userProvidedInstructions
|
|
114789
|
-
}
|
|
114790
|
-
|
|
114791
|
-
|
|
114792
|
-
|
|
114793
|
-
|
|
114790
|
+
};
|
|
114791
|
+
inputItems.push(systemMessage);
|
|
114792
|
+
}
|
|
114793
|
+
const textInput = {
|
|
114794
|
+
type: "input_text",
|
|
114795
|
+
text: instruction
|
|
114796
|
+
};
|
|
114797
|
+
const userContent = [
|
|
114798
|
+
textInput
|
|
114794
114799
|
];
|
|
114800
|
+
const initialScreenshot = await this.captureInitialScreenshot();
|
|
114801
|
+
if (initialScreenshot) {
|
|
114802
|
+
const screenshotInput = {
|
|
114803
|
+
type: "input_image",
|
|
114804
|
+
image_url: initialScreenshot,
|
|
114805
|
+
detail: "high"
|
|
114806
|
+
};
|
|
114807
|
+
userContent.push(screenshotInput);
|
|
114808
|
+
}
|
|
114809
|
+
const userMessage = {
|
|
114810
|
+
role: "user",
|
|
114811
|
+
content: userContent
|
|
114812
|
+
};
|
|
114813
|
+
inputItems.push(userMessage);
|
|
114814
|
+
return inputItems;
|
|
114795
114815
|
}
|
|
114796
114816
|
async getAction(inputItems, previousResponseId) {
|
|
114797
114817
|
try {
|
|
@@ -115050,6 +115070,16 @@ var OpenAICUAClient = class extends AgentClient {
|
|
|
115050
115070
|
this.pendingContextNotes = [];
|
|
115051
115071
|
return notes;
|
|
115052
115072
|
}
|
|
115073
|
+
async captureInitialScreenshot() {
|
|
115074
|
+
if (!this.screenshotProvider) {
|
|
115075
|
+
return void 0;
|
|
115076
|
+
}
|
|
115077
|
+
try {
|
|
115078
|
+
return await this.captureScreenshot();
|
|
115079
|
+
} catch {
|
|
115080
|
+
return void 0;
|
|
115081
|
+
}
|
|
115082
|
+
}
|
|
115053
115083
|
convertFunctionCallToAction(call) {
|
|
115054
115084
|
try {
|
|
115055
115085
|
const args = JSON.parse(call.arguments);
|
|
@@ -164590,7 +164620,29 @@ var import_child_process4 = require("child_process");
|
|
|
164590
164620
|
var readline = __toESM(require("readline"));
|
|
164591
164621
|
|
|
164592
164622
|
// package.json
|
|
164593
|
-
var version3 = "0.
|
|
164623
|
+
var version3 = "0.4.0";
|
|
164624
|
+
|
|
164625
|
+
// src/resolve-ws.ts
|
|
164626
|
+
init_cjs_shims();
|
|
164627
|
+
async function resolveWsTarget(input) {
|
|
164628
|
+
if (/^\d+$/.test(input)) {
|
|
164629
|
+
const port = input;
|
|
164630
|
+
const url2 = `http://127.0.0.1:${port}/json/version`;
|
|
164631
|
+
try {
|
|
164632
|
+
const res = await fetch(url2);
|
|
164633
|
+
if (!res.ok) {
|
|
164634
|
+
throw new Error(`HTTP ${res.status} from ${url2}`);
|
|
164635
|
+
}
|
|
164636
|
+
const json2 = await res.json();
|
|
164637
|
+
if (json2.webSocketDebuggerUrl) {
|
|
164638
|
+
return json2.webSocketDebuggerUrl;
|
|
164639
|
+
}
|
|
164640
|
+
} catch {
|
|
164641
|
+
}
|
|
164642
|
+
return `ws://127.0.0.1:${port}/devtools/browser`;
|
|
164643
|
+
}
|
|
164644
|
+
return input;
|
|
164645
|
+
}
|
|
164594
164646
|
|
|
164595
164647
|
// src/index.ts
|
|
164596
164648
|
var program = new import_commander.Command();
|
|
@@ -164687,6 +164739,34 @@ function getContextPath(session) {
|
|
|
164687
164739
|
function getConnectPath(session) {
|
|
164688
164740
|
return path11.join(SOCKET_DIR, `browse-${session}.connect`);
|
|
164689
164741
|
}
|
|
164742
|
+
function getLocalConfigPath(session) {
|
|
164743
|
+
return path11.join(SOCKET_DIR, `browse-${session}.local-config`);
|
|
164744
|
+
}
|
|
164745
|
+
function getLocalInfoPath(session) {
|
|
164746
|
+
return path11.join(SOCKET_DIR, `browse-${session}.local-info`);
|
|
164747
|
+
}
|
|
164748
|
+
async function readLocalConfig(session) {
|
|
164749
|
+
try {
|
|
164750
|
+
const raw = await import_fs11.promises.readFile(getLocalConfigPath(session), "utf-8");
|
|
164751
|
+
return JSON.parse(raw);
|
|
164752
|
+
} catch {
|
|
164753
|
+
return { strategy: "auto" };
|
|
164754
|
+
}
|
|
164755
|
+
}
|
|
164756
|
+
async function writeLocalConfig(session, config3) {
|
|
164757
|
+
await import_fs11.promises.writeFile(getLocalConfigPath(session), JSON.stringify(config3));
|
|
164758
|
+
}
|
|
164759
|
+
async function writeLocalInfo(session, info) {
|
|
164760
|
+
await import_fs11.promises.writeFile(getLocalInfoPath(session), JSON.stringify(info));
|
|
164761
|
+
}
|
|
164762
|
+
async function readLocalInfo(session) {
|
|
164763
|
+
try {
|
|
164764
|
+
const raw = await import_fs11.promises.readFile(getLocalInfoPath(session), "utf-8");
|
|
164765
|
+
return JSON.parse(raw);
|
|
164766
|
+
} catch {
|
|
164767
|
+
return null;
|
|
164768
|
+
}
|
|
164769
|
+
}
|
|
164690
164770
|
function hasBrowserbaseCredentials() {
|
|
164691
164771
|
return Boolean(process.env.BROWSERBASE_API_KEY);
|
|
164692
164772
|
}
|
|
@@ -164718,6 +164798,165 @@ async function getDesiredMode(session) {
|
|
|
164718
164798
|
}
|
|
164719
164799
|
return hasBrowserbaseCredentials() ? "browserbase" : "local";
|
|
164720
164800
|
}
|
|
164801
|
+
function getChromeUserDataDirs() {
|
|
164802
|
+
const home = os3.homedir();
|
|
164803
|
+
const dirs = [];
|
|
164804
|
+
if (process.platform === "darwin") {
|
|
164805
|
+
const base = path11.join(home, "Library", "Application Support");
|
|
164806
|
+
for (const name18 of [
|
|
164807
|
+
"Google/Chrome",
|
|
164808
|
+
"Google/Chrome Canary",
|
|
164809
|
+
"Chromium",
|
|
164810
|
+
"BraveSoftware/Brave-Browser"
|
|
164811
|
+
]) {
|
|
164812
|
+
dirs.push(path11.join(base, name18));
|
|
164813
|
+
}
|
|
164814
|
+
} else if (process.platform === "linux") {
|
|
164815
|
+
const config3 = path11.join(home, ".config");
|
|
164816
|
+
for (const name18 of [
|
|
164817
|
+
"google-chrome",
|
|
164818
|
+
"google-chrome-unstable",
|
|
164819
|
+
"chromium",
|
|
164820
|
+
"BraveSoftware/Brave-Browser"
|
|
164821
|
+
]) {
|
|
164822
|
+
dirs.push(path11.join(config3, name18));
|
|
164823
|
+
}
|
|
164824
|
+
}
|
|
164825
|
+
return dirs;
|
|
164826
|
+
}
|
|
164827
|
+
async function readDevToolsActivePort(userDataDir) {
|
|
164828
|
+
try {
|
|
164829
|
+
const content = await import_fs11.promises.readFile(
|
|
164830
|
+
path11.join(userDataDir, "DevToolsActivePort"),
|
|
164831
|
+
"utf-8"
|
|
164832
|
+
);
|
|
164833
|
+
const lines = content.trim().split("\n");
|
|
164834
|
+
const port = parseInt(lines[0]?.trim(), 10);
|
|
164835
|
+
if (isNaN(port) || port <= 0 || port > 65535) return null;
|
|
164836
|
+
const wsPath = lines[1]?.trim() || "/devtools/browser";
|
|
164837
|
+
return { port, wsPath };
|
|
164838
|
+
} catch {
|
|
164839
|
+
return null;
|
|
164840
|
+
}
|
|
164841
|
+
}
|
|
164842
|
+
function isPortReachable(port, timeoutMs = 500) {
|
|
164843
|
+
return new Promise((resolve4) => {
|
|
164844
|
+
const sock = net2.createConnection({ host: "127.0.0.1", port });
|
|
164845
|
+
const timer = setTimeout(() => {
|
|
164846
|
+
sock.destroy();
|
|
164847
|
+
resolve4(false);
|
|
164848
|
+
}, timeoutMs);
|
|
164849
|
+
sock.on("connect", () => {
|
|
164850
|
+
clearTimeout(timer);
|
|
164851
|
+
sock.destroy();
|
|
164852
|
+
resolve4(true);
|
|
164853
|
+
});
|
|
164854
|
+
sock.on("error", () => {
|
|
164855
|
+
clearTimeout(timer);
|
|
164856
|
+
resolve4(false);
|
|
164857
|
+
});
|
|
164858
|
+
});
|
|
164859
|
+
}
|
|
164860
|
+
async function probeCdpEndpoint(port) {
|
|
164861
|
+
try {
|
|
164862
|
+
const controller = new AbortController();
|
|
164863
|
+
const timer = setTimeout(() => controller.abort(), 2e3);
|
|
164864
|
+
const res = await fetch(`http://127.0.0.1:${port}/json/version`, {
|
|
164865
|
+
signal: controller.signal
|
|
164866
|
+
});
|
|
164867
|
+
clearTimeout(timer);
|
|
164868
|
+
if (res.ok) {
|
|
164869
|
+
const json2 = await res.json();
|
|
164870
|
+
if (json2.webSocketDebuggerUrl) {
|
|
164871
|
+
return json2.webSocketDebuggerUrl;
|
|
164872
|
+
}
|
|
164873
|
+
}
|
|
164874
|
+
} catch {
|
|
164875
|
+
}
|
|
164876
|
+
const wsUrl = `ws://127.0.0.1:${port}/devtools/browser`;
|
|
164877
|
+
try {
|
|
164878
|
+
const verified = await verifyCdpWebSocket(wsUrl);
|
|
164879
|
+
if (verified) return wsUrl;
|
|
164880
|
+
} catch {
|
|
164881
|
+
}
|
|
164882
|
+
return null;
|
|
164883
|
+
}
|
|
164884
|
+
function verifyCdpWebSocket(wsUrl) {
|
|
164885
|
+
return new Promise((resolve4) => {
|
|
164886
|
+
const url2 = new URL(wsUrl);
|
|
164887
|
+
const port = parseInt(url2.port) || 80;
|
|
164888
|
+
const wsKey = Buffer.from(
|
|
164889
|
+
Array.from({ length: 16 }, () => Math.floor(Math.random() * 256))
|
|
164890
|
+
).toString("base64");
|
|
164891
|
+
const sock = net2.createConnection({ host: url2.hostname, port });
|
|
164892
|
+
let response = "";
|
|
164893
|
+
const timer = setTimeout(() => {
|
|
164894
|
+
sock.destroy();
|
|
164895
|
+
resolve4(false);
|
|
164896
|
+
}, 2e3);
|
|
164897
|
+
sock.on("connect", () => {
|
|
164898
|
+
sock.write(
|
|
164899
|
+
`GET ${url2.pathname} HTTP/1.1\r
|
|
164900
|
+
Host: ${url2.hostname}:${port}\r
|
|
164901
|
+
Upgrade: websocket\r
|
|
164902
|
+
Connection: Upgrade\r
|
|
164903
|
+
Sec-WebSocket-Key: ${wsKey}\r
|
|
164904
|
+
Sec-WebSocket-Version: 13\r
|
|
164905
|
+
\r
|
|
164906
|
+
`
|
|
164907
|
+
);
|
|
164908
|
+
});
|
|
164909
|
+
sock.on("data", (data) => {
|
|
164910
|
+
response += data.toString();
|
|
164911
|
+
if (/^HTTP\/1\.[01] 101(?:\s|$)/.test(response)) {
|
|
164912
|
+
clearTimeout(timer);
|
|
164913
|
+
sock.destroy();
|
|
164914
|
+
resolve4(true);
|
|
164915
|
+
} else if (response.includes("\r\n\r\n")) {
|
|
164916
|
+
clearTimeout(timer);
|
|
164917
|
+
sock.destroy();
|
|
164918
|
+
resolve4(false);
|
|
164919
|
+
}
|
|
164920
|
+
});
|
|
164921
|
+
sock.on("error", () => {
|
|
164922
|
+
clearTimeout(timer);
|
|
164923
|
+
resolve4(false);
|
|
164924
|
+
});
|
|
164925
|
+
});
|
|
164926
|
+
}
|
|
164927
|
+
async function discoverLocalCdp() {
|
|
164928
|
+
const candidates = [];
|
|
164929
|
+
const userDataDirs = getChromeUserDataDirs();
|
|
164930
|
+
for (const dir of userDataDirs) {
|
|
164931
|
+
const info = await readDevToolsActivePort(dir);
|
|
164932
|
+
if (!info) continue;
|
|
164933
|
+
if (!await isPortReachable(info.port)) {
|
|
164934
|
+
try {
|
|
164935
|
+
await import_fs11.promises.unlink(path11.join(dir, "DevToolsActivePort"));
|
|
164936
|
+
} catch {
|
|
164937
|
+
}
|
|
164938
|
+
continue;
|
|
164939
|
+
}
|
|
164940
|
+
const wsUrl = await probeCdpEndpoint(info.port);
|
|
164941
|
+
if (wsUrl) {
|
|
164942
|
+
const name18 = path11.basename(dir);
|
|
164943
|
+
candidates.push({ wsUrl, source: `DevToolsActivePort (${name18})` });
|
|
164944
|
+
}
|
|
164945
|
+
}
|
|
164946
|
+
if (candidates.length === 0) {
|
|
164947
|
+
for (const port of [9222, 9229]) {
|
|
164948
|
+
if (!await isPortReachable(port)) continue;
|
|
164949
|
+
const wsUrl = await probeCdpEndpoint(port);
|
|
164950
|
+
if (wsUrl) {
|
|
164951
|
+
candidates.push({ wsUrl, source: `port ${port}` });
|
|
164952
|
+
}
|
|
164953
|
+
}
|
|
164954
|
+
}
|
|
164955
|
+
if (candidates.length > 1) {
|
|
164956
|
+
return null;
|
|
164957
|
+
}
|
|
164958
|
+
return candidates[0] ?? null;
|
|
164959
|
+
}
|
|
164721
164960
|
async function isDaemonRunning(session) {
|
|
164722
164961
|
try {
|
|
164723
164962
|
const pidFile = getPidPath(session);
|
|
@@ -164736,14 +164975,16 @@ var DAEMON_STATE_FILES = (session) => [
|
|
|
164736
164975
|
getWsPath(session),
|
|
164737
164976
|
getChromePidPath(session),
|
|
164738
164977
|
getLockPath(session),
|
|
164739
|
-
getModePath(session)
|
|
164978
|
+
getModePath(session),
|
|
164979
|
+
getLocalInfoPath(session)
|
|
164740
164980
|
];
|
|
164741
164981
|
async function cleanupStaleFiles(session) {
|
|
164742
164982
|
const files = [
|
|
164743
164983
|
...DAEMON_STATE_FILES(session),
|
|
164744
164984
|
// Client-written config, only cleaned on full shutdown
|
|
164745
164985
|
getContextPath(session),
|
|
164746
|
-
getConnectPath(session)
|
|
164986
|
+
getConnectPath(session),
|
|
164987
|
+
getLocalConfigPath(session)
|
|
164747
164988
|
];
|
|
164748
164989
|
for (const file2 of files) {
|
|
164749
164990
|
try {
|
|
@@ -164820,6 +165061,37 @@ async function runDaemon(session, headless) {
|
|
|
164820
165061
|
connectSessionId = (await import_fs11.promises.readFile(getConnectPath(session), "utf-8")).trim();
|
|
164821
165062
|
} catch {
|
|
164822
165063
|
}
|
|
165064
|
+
let localLaunchOptions;
|
|
165065
|
+
let localInfo;
|
|
165066
|
+
if (!useBrowserbase) {
|
|
165067
|
+
const localConfig = await readLocalConfig(session);
|
|
165068
|
+
if (localConfig.strategy === "isolated") {
|
|
165069
|
+
localLaunchOptions = { headless, viewport: DEFAULT_VIEWPORT2 };
|
|
165070
|
+
localInfo = { localSource: "isolated" };
|
|
165071
|
+
} else if (localConfig.strategy === "cdp") {
|
|
165072
|
+
const cdpUrl = await resolveWsTarget(localConfig.cdpTarget);
|
|
165073
|
+
localLaunchOptions = { cdpUrl };
|
|
165074
|
+
localInfo = {
|
|
165075
|
+
localSource: "attached-explicit",
|
|
165076
|
+
resolvedCdpUrl: cdpUrl
|
|
165077
|
+
};
|
|
165078
|
+
} else {
|
|
165079
|
+
const discovered = await discoverLocalCdp();
|
|
165080
|
+
if (discovered) {
|
|
165081
|
+
localLaunchOptions = { cdpUrl: discovered.wsUrl };
|
|
165082
|
+
localInfo = {
|
|
165083
|
+
localSource: "attached-existing",
|
|
165084
|
+
resolvedCdpUrl: discovered.wsUrl
|
|
165085
|
+
};
|
|
165086
|
+
} else {
|
|
165087
|
+
localLaunchOptions = { headless, viewport: DEFAULT_VIEWPORT2 };
|
|
165088
|
+
localInfo = {
|
|
165089
|
+
localSource: "isolated-fallback",
|
|
165090
|
+
fallbackReason: "no debuggable local browser found"
|
|
165091
|
+
};
|
|
165092
|
+
}
|
|
165093
|
+
}
|
|
165094
|
+
}
|
|
164823
165095
|
stagehand = new V3({
|
|
164824
165096
|
env: useBrowserbase ? "BROWSERBASE" : "LOCAL",
|
|
164825
165097
|
verbose: 0,
|
|
@@ -164841,13 +165113,13 @@ async function runDaemon(session, headless) {
|
|
|
164841
165113
|
}
|
|
164842
165114
|
} : {}
|
|
164843
165115
|
} : {
|
|
164844
|
-
localBrowserLaunchOptions:
|
|
164845
|
-
headless,
|
|
164846
|
-
viewport: DEFAULT_VIEWPORT2
|
|
164847
|
-
}
|
|
165116
|
+
localBrowserLaunchOptions: localLaunchOptions
|
|
164848
165117
|
}
|
|
164849
165118
|
});
|
|
164850
165119
|
await import_fs11.promises.writeFile(getModePath(session), desiredMode);
|
|
165120
|
+
if (localInfo) {
|
|
165121
|
+
await writeLocalInfo(session, localInfo);
|
|
165122
|
+
}
|
|
164851
165123
|
await stagehand.init();
|
|
164852
165124
|
context = stagehand.context;
|
|
164853
165125
|
context.conn.onTransportClosed(() => {
|
|
@@ -164905,7 +165177,7 @@ async function runDaemon(session, headless) {
|
|
|
164905
165177
|
}
|
|
164906
165178
|
} catch {
|
|
164907
165179
|
}
|
|
164908
|
-
await
|
|
165180
|
+
await cleanupDaemonStateFiles(session);
|
|
164909
165181
|
process.exit(0);
|
|
164910
165182
|
};
|
|
164911
165183
|
process.on("SIGTERM", () => shutdown());
|
|
@@ -165572,7 +165844,7 @@ async function stopDaemonAndCleanup(session) {
|
|
|
165572
165844
|
} catch {
|
|
165573
165845
|
}
|
|
165574
165846
|
await new Promise((r3) => setTimeout(r3, 500));
|
|
165575
|
-
await
|
|
165847
|
+
await cleanupDaemonStateFiles(session);
|
|
165576
165848
|
}
|
|
165577
165849
|
async function ensureDaemon(session, headless) {
|
|
165578
165850
|
const wantMode = await getDesiredMode(session);
|
|
@@ -165659,12 +165931,13 @@ async function runCommand(command, args) {
|
|
|
165659
165931
|
const session = getSession(opts);
|
|
165660
165932
|
const headless = isHeadless(opts);
|
|
165661
165933
|
if (opts.ws) {
|
|
165934
|
+
const cdpUrl = await resolveWsTarget(opts.ws);
|
|
165662
165935
|
const stagehand = new V3({
|
|
165663
165936
|
env: "LOCAL",
|
|
165664
165937
|
verbose: 0,
|
|
165665
165938
|
disablePino: true,
|
|
165666
165939
|
localBrowserLaunchOptions: {
|
|
165667
|
-
cdpUrl
|
|
165940
|
+
cdpUrl
|
|
165668
165941
|
}
|
|
165669
165942
|
});
|
|
165670
165943
|
await stagehand.init();
|
|
@@ -165702,8 +165975,8 @@ async function runCommand(command, args) {
|
|
|
165702
165975
|
return sendCommand(session, command, args, headless);
|
|
165703
165976
|
}
|
|
165704
165977
|
program.name("browse").description("Browser automation CLI for AI agents").version(version3).option(
|
|
165705
|
-
"--ws <url>",
|
|
165706
|
-
"CDP WebSocket URL (bypasses daemon, direct connection)"
|
|
165978
|
+
"--ws <url|port>",
|
|
165979
|
+
"CDP WebSocket URL or port number (bypasses daemon, direct connection)"
|
|
165707
165980
|
).option("--headless", "Run Chrome in headless mode").option("--headed", "Run Chrome with visible window (default)").option("--json", "Output as JSON", false).option(
|
|
165708
165981
|
"--session <name>",
|
|
165709
165982
|
"Session name for multiple browsers (or use BROWSE_SESSION env var)"
|
|
@@ -165748,6 +166021,7 @@ program.command("status").description("Check daemon status").action(async () =>
|
|
|
165748
166021
|
let wsUrl = null;
|
|
165749
166022
|
let mode = null;
|
|
165750
166023
|
let browserbaseSessionId = null;
|
|
166024
|
+
let localDetails = {};
|
|
165751
166025
|
if (running) {
|
|
165752
166026
|
try {
|
|
165753
166027
|
wsUrl = await import_fs11.promises.readFile(getWsPath(session), "utf-8");
|
|
@@ -165758,68 +166032,106 @@ program.command("status").description("Check daemon status").action(async () =>
|
|
|
165758
166032
|
browserbaseSessionId = (await import_fs11.promises.readFile(getConnectPath(session), "utf-8")).trim();
|
|
165759
166033
|
} catch {
|
|
165760
166034
|
}
|
|
166035
|
+
if (mode === "local") {
|
|
166036
|
+
const localConfig = await readLocalConfig(session);
|
|
166037
|
+
const localInfo = await readLocalInfo(session);
|
|
166038
|
+
localDetails = {
|
|
166039
|
+
localStrategy: localConfig.strategy,
|
|
166040
|
+
...localInfo ?? {}
|
|
166041
|
+
};
|
|
166042
|
+
}
|
|
165761
166043
|
}
|
|
165762
166044
|
console.log(
|
|
165763
|
-
JSON.stringify({
|
|
166045
|
+
JSON.stringify({
|
|
166046
|
+
running,
|
|
166047
|
+
session,
|
|
166048
|
+
wsUrl,
|
|
166049
|
+
mode,
|
|
166050
|
+
browserbaseSessionId,
|
|
166051
|
+
...localDetails
|
|
166052
|
+
})
|
|
165764
166053
|
);
|
|
165765
166054
|
});
|
|
165766
|
-
program.command("env [target]").description(
|
|
165767
|
-
|
|
165768
|
-
|
|
165769
|
-
|
|
165770
|
-
|
|
165771
|
-
const
|
|
165772
|
-
if (
|
|
165773
|
-
mode =
|
|
165774
|
-
|
|
165775
|
-
|
|
165776
|
-
|
|
165777
|
-
|
|
165778
|
-
|
|
165779
|
-
|
|
165780
|
-
})
|
|
165781
|
-
);
|
|
165782
|
-
return;
|
|
165783
|
-
}
|
|
165784
|
-
const modeMap = {
|
|
165785
|
-
local: "local",
|
|
165786
|
-
remote: "browserbase"
|
|
165787
|
-
};
|
|
165788
|
-
const mapped = modeMap[target];
|
|
165789
|
-
if (!mapped) {
|
|
165790
|
-
console.error("Usage: browse env [local|remote]");
|
|
165791
|
-
process.exit(1);
|
|
165792
|
-
}
|
|
165793
|
-
try {
|
|
165794
|
-
assertModeSupported(mapped);
|
|
165795
|
-
} catch (err) {
|
|
165796
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
165797
|
-
process.exit(1);
|
|
165798
|
-
}
|
|
165799
|
-
await import_fs11.promises.writeFile(getModeOverridePath(session), mapped);
|
|
165800
|
-
if (await isDaemonRunning(session)) {
|
|
165801
|
-
const currentMode = await readCurrentMode(session) ?? "local";
|
|
165802
|
-
if (currentMode === mapped) {
|
|
166055
|
+
program.command("env [target] [cdpTarget]").description(
|
|
166056
|
+
"Show or switch browser environment (local | remote)\n\n browse env Show current environment\n browse env local Auto-discover local Chrome, fallback to isolated\n browse env local --isolated Force clean isolated browser\n browse env local <port|url> Attach to specific CDP target\n browse env remote Use Browserbase (requires API key)"
|
|
166057
|
+
).option("--isolated", "Force isolated local browser (no auto-discovery)").action(
|
|
166058
|
+
async (target, cdpTarget, cmdOpts) => {
|
|
166059
|
+
const opts = program.opts();
|
|
166060
|
+
const session = getSession(opts);
|
|
166061
|
+
if (!target) {
|
|
166062
|
+
let mode = null;
|
|
166063
|
+
const desiredMode = await getDesiredMode(session);
|
|
166064
|
+
const localConfig2 = await readLocalConfig(session);
|
|
166065
|
+
const localInfo = await readLocalInfo(session);
|
|
166066
|
+
if (await isDaemonRunning(session)) {
|
|
166067
|
+
mode = toModeTarget(await readCurrentMode(session) ?? desiredMode);
|
|
166068
|
+
}
|
|
165803
166069
|
console.log(
|
|
165804
166070
|
JSON.stringify({
|
|
165805
|
-
mode:
|
|
166071
|
+
mode: mode ?? "not running",
|
|
166072
|
+
desired: toModeTarget(desiredMode),
|
|
165806
166073
|
session,
|
|
165807
|
-
|
|
166074
|
+
...desiredMode === "local" ? {
|
|
166075
|
+
localStrategy: localConfig2.strategy,
|
|
166076
|
+
...localInfo ?? {}
|
|
166077
|
+
} : {}
|
|
165808
166078
|
})
|
|
165809
166079
|
);
|
|
165810
166080
|
return;
|
|
165811
166081
|
}
|
|
165812
|
-
|
|
166082
|
+
const modeMap = {
|
|
166083
|
+
local: "local",
|
|
166084
|
+
remote: "browserbase"
|
|
166085
|
+
};
|
|
166086
|
+
const mapped = modeMap[target];
|
|
166087
|
+
if (!mapped) {
|
|
166088
|
+
console.error(
|
|
166089
|
+
"Usage: browse env [local|remote]\n browse env local [--isolated] [<port|url>]"
|
|
166090
|
+
);
|
|
166091
|
+
process.exit(1);
|
|
166092
|
+
}
|
|
166093
|
+
try {
|
|
166094
|
+
assertModeSupported(mapped);
|
|
166095
|
+
} catch (err) {
|
|
166096
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
166097
|
+
process.exit(1);
|
|
166098
|
+
}
|
|
166099
|
+
let localConfig = { strategy: "auto" };
|
|
166100
|
+
if (mapped === "local") {
|
|
166101
|
+
if (cmdOpts.isolated) {
|
|
166102
|
+
localConfig = { strategy: "isolated" };
|
|
166103
|
+
} else if (cdpTarget) {
|
|
166104
|
+
localConfig = { strategy: "cdp", cdpTarget };
|
|
166105
|
+
}
|
|
166106
|
+
await writeLocalConfig(session, localConfig);
|
|
166107
|
+
}
|
|
166108
|
+
await import_fs11.promises.writeFile(getModeOverridePath(session), mapped);
|
|
166109
|
+
if (await isDaemonRunning(session)) {
|
|
166110
|
+
const currentMode = await readCurrentMode(session) ?? "local";
|
|
166111
|
+
const needsRestart = currentMode !== mapped || mapped === "local";
|
|
166112
|
+
if (!needsRestart) {
|
|
166113
|
+
console.log(
|
|
166114
|
+
JSON.stringify({
|
|
166115
|
+
mode: toModeTarget(mapped),
|
|
166116
|
+
session,
|
|
166117
|
+
restarted: false
|
|
166118
|
+
})
|
|
166119
|
+
);
|
|
166120
|
+
return;
|
|
166121
|
+
}
|
|
166122
|
+
await stopDaemonAndCleanup(session);
|
|
166123
|
+
}
|
|
166124
|
+
await ensureDaemon(session, isHeadless(opts));
|
|
166125
|
+
console.log(
|
|
166126
|
+
JSON.stringify({
|
|
166127
|
+
mode: toModeTarget(mapped),
|
|
166128
|
+
session,
|
|
166129
|
+
restarted: true,
|
|
166130
|
+
...mapped === "local" ? { localStrategy: localConfig.strategy } : {}
|
|
166131
|
+
})
|
|
166132
|
+
);
|
|
165813
166133
|
}
|
|
165814
|
-
|
|
165815
|
-
console.log(
|
|
165816
|
-
JSON.stringify({
|
|
165817
|
-
mode: toModeTarget(mapped),
|
|
165818
|
-
session,
|
|
165819
|
-
restarted: true
|
|
165820
|
-
})
|
|
165821
|
-
);
|
|
165822
|
-
});
|
|
166134
|
+
);
|
|
165823
166135
|
program.command("refs").description("Show cached ref map from last snapshot").action(async () => {
|
|
165824
166136
|
const opts = program.opts();
|
|
165825
166137
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@browserbasehq/browse-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Browser automation CLI for AI agents, built on Stagehand",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"pino": "^9.6.0",
|
|
48
48
|
"pino-pretty": "^13.0.0",
|
|
49
49
|
"ws": "^8.18.0",
|
|
50
|
-
"@browserbasehq/stagehand": "3.2.1-alpha-
|
|
50
|
+
"@browserbasehq/stagehand": "3.2.1-alpha-be6798aefdf44d2d2d275a4b9cf3d4473609515b"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@types/node": "^20.11.30",
|