opensteer 0.6.5 → 0.6.6

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.
@@ -7529,6 +7529,8 @@ import WebSocket2, { WebSocketServer } from "ws";
7529
7529
  var CDP_DISCOVERY_TIMEOUT_MS = 3e3;
7530
7530
  var LOCAL_PROXY_HOST = "127.0.0.1";
7531
7531
  var INTERNAL_COMMAND_ID_START = 1e9;
7532
+ var CREATE_BLANK_TARGET_COMMAND_ID = 1;
7533
+ var CREATE_BLANK_TARGET_TIMEOUT_MS = 5e3;
7532
7534
  async function discoverTargets(cdpUrl) {
7533
7535
  const baseUrl = resolveHttpDiscoveryBase(cdpUrl);
7534
7536
  const [targetsPayload, versionPayload] = await Promise.all([
@@ -7540,6 +7542,81 @@ async function discoverTargets(cdpUrl) {
7540
7542
  targets: readPageTargets(targetsPayload)
7541
7543
  };
7542
7544
  }
7545
+ function createBlankTarget(browserWsUrl) {
7546
+ return new Promise((resolve, reject) => {
7547
+ const ws = new WebSocket2(browserWsUrl);
7548
+ let settled = false;
7549
+ function settle(handler) {
7550
+ if (settled) {
7551
+ return;
7552
+ }
7553
+ settled = true;
7554
+ clearTimeout(timeout);
7555
+ ws.close();
7556
+ handler();
7557
+ }
7558
+ const timeout = setTimeout(() => {
7559
+ settle(
7560
+ () => reject(new Error("Timed out creating a blank tab via CDP."))
7561
+ );
7562
+ }, CREATE_BLANK_TARGET_TIMEOUT_MS);
7563
+ ws.once("open", () => {
7564
+ ws.send(
7565
+ JSON.stringify({
7566
+ id: CREATE_BLANK_TARGET_COMMAND_ID,
7567
+ method: "Target.createTarget",
7568
+ params: { url: "about:blank" }
7569
+ })
7570
+ );
7571
+ });
7572
+ ws.on("message", (data, isBinary) => {
7573
+ if (isBinary) {
7574
+ return;
7575
+ }
7576
+ const message = parseMessage(data);
7577
+ if (!message || asNumber(message.id) !== CREATE_BLANK_TARGET_COMMAND_ID) {
7578
+ return;
7579
+ }
7580
+ const cdpError = asObject(message.error);
7581
+ if (cdpError) {
7582
+ settle(
7583
+ () => reject(
7584
+ new Error(
7585
+ `Target.createTarget failed: ${asString(cdpError.message) ?? JSON.stringify(cdpError)}`
7586
+ )
7587
+ )
7588
+ );
7589
+ return;
7590
+ }
7591
+ const targetId = asString(asObject(message.result)?.targetId);
7592
+ if (targetId) {
7593
+ settle(() => resolve(targetId));
7594
+ return;
7595
+ }
7596
+ settle(
7597
+ () => reject(
7598
+ new Error(
7599
+ "Target.createTarget succeeded but no targetId was returned."
7600
+ )
7601
+ )
7602
+ );
7603
+ });
7604
+ ws.once("error", (err) => {
7605
+ settle(
7606
+ () => reject(new Error(`Failed to create blank tab: ${errorMessage(err)}`))
7607
+ );
7608
+ });
7609
+ ws.once("close", () => {
7610
+ settle(
7611
+ () => reject(
7612
+ new Error(
7613
+ "CDP browser websocket closed before blank tab creation completed."
7614
+ )
7615
+ )
7616
+ );
7617
+ });
7618
+ });
7619
+ }
7543
7620
  var CDPProxy = class {
7544
7621
  constructor(browserWsUrl, targetId) {
7545
7622
  this.browserWsUrl = browserWsUrl;
@@ -7947,12 +8024,13 @@ var BrowserPool = class {
7947
8024
  let cdpProxy = null;
7948
8025
  try {
7949
8026
  const { browserWsUrl, targets } = await discoverTargets(cdpUrl);
7950
- if (targets.length === 0) {
7951
- throw new Error(
7952
- "No page targets found. Is the browser running with an open window?"
7953
- );
8027
+ let targetId;
8028
+ if (targets.length > 0) {
8029
+ targetId = targets[0].id;
8030
+ } else {
8031
+ targetId = await createBlankTarget(browserWsUrl);
7954
8032
  }
7955
- cdpProxy = new CDPProxy(browserWsUrl, targets[0].id);
8033
+ cdpProxy = new CDPProxy(browserWsUrl, targetId);
7956
8034
  const proxyWsUrl = await cdpProxy.start();
7957
8035
  browser = await chromium.connectOverCDP(proxyWsUrl, {
7958
8036
  timeout: timeout ?? 3e4
@@ -1336,6 +1336,8 @@ var import_ws = __toESM(require("ws"), 1);
1336
1336
  var CDP_DISCOVERY_TIMEOUT_MS = 3e3;
1337
1337
  var LOCAL_PROXY_HOST = "127.0.0.1";
1338
1338
  var INTERNAL_COMMAND_ID_START = 1e9;
1339
+ var CREATE_BLANK_TARGET_COMMAND_ID = 1;
1340
+ var CREATE_BLANK_TARGET_TIMEOUT_MS = 5e3;
1339
1341
  async function discoverTargets(cdpUrl) {
1340
1342
  const baseUrl = resolveHttpDiscoveryBase(cdpUrl);
1341
1343
  const [targetsPayload, versionPayload] = await Promise.all([
@@ -1347,6 +1349,81 @@ async function discoverTargets(cdpUrl) {
1347
1349
  targets: readPageTargets(targetsPayload)
1348
1350
  };
1349
1351
  }
1352
+ function createBlankTarget(browserWsUrl) {
1353
+ return new Promise((resolve, reject) => {
1354
+ const ws = new import_ws.default(browserWsUrl);
1355
+ let settled = false;
1356
+ function settle(handler) {
1357
+ if (settled) {
1358
+ return;
1359
+ }
1360
+ settled = true;
1361
+ clearTimeout(timeout);
1362
+ ws.close();
1363
+ handler();
1364
+ }
1365
+ const timeout = setTimeout(() => {
1366
+ settle(
1367
+ () => reject(new Error("Timed out creating a blank tab via CDP."))
1368
+ );
1369
+ }, CREATE_BLANK_TARGET_TIMEOUT_MS);
1370
+ ws.once("open", () => {
1371
+ ws.send(
1372
+ JSON.stringify({
1373
+ id: CREATE_BLANK_TARGET_COMMAND_ID,
1374
+ method: "Target.createTarget",
1375
+ params: { url: "about:blank" }
1376
+ })
1377
+ );
1378
+ });
1379
+ ws.on("message", (data, isBinary) => {
1380
+ if (isBinary) {
1381
+ return;
1382
+ }
1383
+ const message = parseMessage(data);
1384
+ if (!message || asNumber(message.id) !== CREATE_BLANK_TARGET_COMMAND_ID) {
1385
+ return;
1386
+ }
1387
+ const cdpError = asObject(message.error);
1388
+ if (cdpError) {
1389
+ settle(
1390
+ () => reject(
1391
+ new Error(
1392
+ `Target.createTarget failed: ${asString(cdpError.message) ?? JSON.stringify(cdpError)}`
1393
+ )
1394
+ )
1395
+ );
1396
+ return;
1397
+ }
1398
+ const targetId = asString(asObject(message.result)?.targetId);
1399
+ if (targetId) {
1400
+ settle(() => resolve(targetId));
1401
+ return;
1402
+ }
1403
+ settle(
1404
+ () => reject(
1405
+ new Error(
1406
+ "Target.createTarget succeeded but no targetId was returned."
1407
+ )
1408
+ )
1409
+ );
1410
+ });
1411
+ ws.once("error", (err) => {
1412
+ settle(
1413
+ () => reject(new Error(`Failed to create blank tab: ${errorMessage(err)}`))
1414
+ );
1415
+ });
1416
+ ws.once("close", () => {
1417
+ settle(
1418
+ () => reject(
1419
+ new Error(
1420
+ "CDP browser websocket closed before blank tab creation completed."
1421
+ )
1422
+ )
1423
+ );
1424
+ });
1425
+ });
1426
+ }
1350
1427
  var CDPProxy = class {
1351
1428
  constructor(browserWsUrl, targetId) {
1352
1429
  this.browserWsUrl = browserWsUrl;
@@ -1830,12 +1907,13 @@ var BrowserPool = class {
1830
1907
  let cdpProxy = null;
1831
1908
  try {
1832
1909
  const { browserWsUrl, targets } = await discoverTargets(cdpUrl);
1833
- if (targets.length === 0) {
1834
- throw new Error(
1835
- "No page targets found. Is the browser running with an open window?"
1836
- );
1910
+ let targetId;
1911
+ if (targets.length > 0) {
1912
+ targetId = targets[0].id;
1913
+ } else {
1914
+ targetId = await createBlankTarget(browserWsUrl);
1837
1915
  }
1838
- cdpProxy = new CDPProxy(browserWsUrl, targets[0].id);
1916
+ cdpProxy = new CDPProxy(browserWsUrl, targetId);
1839
1917
  const proxyWsUrl = await cdpProxy.start();
1840
1918
  browser = await import_playwright.chromium.connectOverCDP(proxyWsUrl, {
1841
1919
  timeout: timeout ?? 3e4
@@ -7,7 +7,7 @@ import {
7
7
  } from "../chunk-54KNQTOL.js";
8
8
  import {
9
9
  Opensteer
10
- } from "../chunk-G6V2DJRN.js";
10
+ } from "../chunk-H27BERRF.js";
11
11
  import {
12
12
  resolveConfigWithEnv
13
13
  } from "../chunk-KPPOTU3D.js";
@@ -437,6 +437,8 @@ var import_ws = __toESM(require("ws"), 1);
437
437
  var CDP_DISCOVERY_TIMEOUT_MS = 3e3;
438
438
  var LOCAL_PROXY_HOST = "127.0.0.1";
439
439
  var INTERNAL_COMMAND_ID_START = 1e9;
440
+ var CREATE_BLANK_TARGET_COMMAND_ID = 1;
441
+ var CREATE_BLANK_TARGET_TIMEOUT_MS = 5e3;
440
442
  async function discoverTargets(cdpUrl) {
441
443
  const baseUrl = resolveHttpDiscoveryBase(cdpUrl);
442
444
  const [targetsPayload, versionPayload] = await Promise.all([
@@ -448,6 +450,81 @@ async function discoverTargets(cdpUrl) {
448
450
  targets: readPageTargets(targetsPayload)
449
451
  };
450
452
  }
453
+ function createBlankTarget(browserWsUrl) {
454
+ return new Promise((resolve, reject) => {
455
+ const ws = new import_ws.default(browserWsUrl);
456
+ let settled = false;
457
+ function settle(handler) {
458
+ if (settled) {
459
+ return;
460
+ }
461
+ settled = true;
462
+ clearTimeout(timeout);
463
+ ws.close();
464
+ handler();
465
+ }
466
+ const timeout = setTimeout(() => {
467
+ settle(
468
+ () => reject(new Error("Timed out creating a blank tab via CDP."))
469
+ );
470
+ }, CREATE_BLANK_TARGET_TIMEOUT_MS);
471
+ ws.once("open", () => {
472
+ ws.send(
473
+ JSON.stringify({
474
+ id: CREATE_BLANK_TARGET_COMMAND_ID,
475
+ method: "Target.createTarget",
476
+ params: { url: "about:blank" }
477
+ })
478
+ );
479
+ });
480
+ ws.on("message", (data, isBinary) => {
481
+ if (isBinary) {
482
+ return;
483
+ }
484
+ const message = parseMessage(data);
485
+ if (!message || asNumber(message.id) !== CREATE_BLANK_TARGET_COMMAND_ID) {
486
+ return;
487
+ }
488
+ const cdpError = asObject(message.error);
489
+ if (cdpError) {
490
+ settle(
491
+ () => reject(
492
+ new Error(
493
+ `Target.createTarget failed: ${asString(cdpError.message) ?? JSON.stringify(cdpError)}`
494
+ )
495
+ )
496
+ );
497
+ return;
498
+ }
499
+ const targetId = asString(asObject(message.result)?.targetId);
500
+ if (targetId) {
501
+ settle(() => resolve(targetId));
502
+ return;
503
+ }
504
+ settle(
505
+ () => reject(
506
+ new Error(
507
+ "Target.createTarget succeeded but no targetId was returned."
508
+ )
509
+ )
510
+ );
511
+ });
512
+ ws.once("error", (err) => {
513
+ settle(
514
+ () => reject(new Error(`Failed to create blank tab: ${errorMessage(err)}`))
515
+ );
516
+ });
517
+ ws.once("close", () => {
518
+ settle(
519
+ () => reject(
520
+ new Error(
521
+ "CDP browser websocket closed before blank tab creation completed."
522
+ )
523
+ )
524
+ );
525
+ });
526
+ });
527
+ }
451
528
  var CDPProxy = class {
452
529
  constructor(browserWsUrl, targetId) {
453
530
  this.browserWsUrl = browserWsUrl;
@@ -931,12 +1008,13 @@ var BrowserPool = class {
931
1008
  let cdpProxy = null;
932
1009
  try {
933
1010
  const { browserWsUrl, targets } = await discoverTargets(cdpUrl);
934
- if (targets.length === 0) {
935
- throw new Error(
936
- "No page targets found. Is the browser running with an open window?"
937
- );
1011
+ let targetId;
1012
+ if (targets.length > 0) {
1013
+ targetId = targets[0].id;
1014
+ } else {
1015
+ targetId = await createBlankTarget(browserWsUrl);
938
1016
  }
939
- cdpProxy = new CDPProxy(browserWsUrl, targets[0].id);
1017
+ cdpProxy = new CDPProxy(browserWsUrl, targetId);
940
1018
  const proxyWsUrl = await cdpProxy.start();
941
1019
  browser = await import_playwright.chromium.connectOverCDP(proxyWsUrl, {
942
1020
  timeout: timeout ?? 3e4
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Opensteer
3
- } from "../chunk-G6V2DJRN.js";
3
+ } from "../chunk-H27BERRF.js";
4
4
  import {
5
5
  normalizeError,
6
6
  resolveCloudSelection,
package/dist/index.cjs CHANGED
@@ -528,6 +528,8 @@ var import_ws = __toESM(require("ws"), 1);
528
528
  var CDP_DISCOVERY_TIMEOUT_MS = 3e3;
529
529
  var LOCAL_PROXY_HOST = "127.0.0.1";
530
530
  var INTERNAL_COMMAND_ID_START = 1e9;
531
+ var CREATE_BLANK_TARGET_COMMAND_ID = 1;
532
+ var CREATE_BLANK_TARGET_TIMEOUT_MS = 5e3;
531
533
  async function discoverTargets(cdpUrl) {
532
534
  const baseUrl = resolveHttpDiscoveryBase(cdpUrl);
533
535
  const [targetsPayload, versionPayload] = await Promise.all([
@@ -539,6 +541,81 @@ async function discoverTargets(cdpUrl) {
539
541
  targets: readPageTargets(targetsPayload)
540
542
  };
541
543
  }
544
+ function createBlankTarget(browserWsUrl) {
545
+ return new Promise((resolve, reject) => {
546
+ const ws = new import_ws.default(browserWsUrl);
547
+ let settled = false;
548
+ function settle(handler) {
549
+ if (settled) {
550
+ return;
551
+ }
552
+ settled = true;
553
+ clearTimeout(timeout);
554
+ ws.close();
555
+ handler();
556
+ }
557
+ const timeout = setTimeout(() => {
558
+ settle(
559
+ () => reject(new Error("Timed out creating a blank tab via CDP."))
560
+ );
561
+ }, CREATE_BLANK_TARGET_TIMEOUT_MS);
562
+ ws.once("open", () => {
563
+ ws.send(
564
+ JSON.stringify({
565
+ id: CREATE_BLANK_TARGET_COMMAND_ID,
566
+ method: "Target.createTarget",
567
+ params: { url: "about:blank" }
568
+ })
569
+ );
570
+ });
571
+ ws.on("message", (data, isBinary) => {
572
+ if (isBinary) {
573
+ return;
574
+ }
575
+ const message = parseMessage(data);
576
+ if (!message || asNumber(message.id) !== CREATE_BLANK_TARGET_COMMAND_ID) {
577
+ return;
578
+ }
579
+ const cdpError = asObject(message.error);
580
+ if (cdpError) {
581
+ settle(
582
+ () => reject(
583
+ new Error(
584
+ `Target.createTarget failed: ${asString(cdpError.message) ?? JSON.stringify(cdpError)}`
585
+ )
586
+ )
587
+ );
588
+ return;
589
+ }
590
+ const targetId = asString(asObject(message.result)?.targetId);
591
+ if (targetId) {
592
+ settle(() => resolve(targetId));
593
+ return;
594
+ }
595
+ settle(
596
+ () => reject(
597
+ new Error(
598
+ "Target.createTarget succeeded but no targetId was returned."
599
+ )
600
+ )
601
+ );
602
+ });
603
+ ws.once("error", (err) => {
604
+ settle(
605
+ () => reject(new Error(`Failed to create blank tab: ${errorMessage(err)}`))
606
+ );
607
+ });
608
+ ws.once("close", () => {
609
+ settle(
610
+ () => reject(
611
+ new Error(
612
+ "CDP browser websocket closed before blank tab creation completed."
613
+ )
614
+ )
615
+ );
616
+ });
617
+ });
618
+ }
542
619
  var CDPProxy = class {
543
620
  constructor(browserWsUrl, targetId) {
544
621
  this.browserWsUrl = browserWsUrl;
@@ -1022,12 +1099,13 @@ var BrowserPool = class {
1022
1099
  let cdpProxy = null;
1023
1100
  try {
1024
1101
  const { browserWsUrl, targets } = await discoverTargets(cdpUrl);
1025
- if (targets.length === 0) {
1026
- throw new Error(
1027
- "No page targets found. Is the browser running with an open window?"
1028
- );
1102
+ let targetId;
1103
+ if (targets.length > 0) {
1104
+ targetId = targets[0].id;
1105
+ } else {
1106
+ targetId = await createBlankTarget(browserWsUrl);
1029
1107
  }
1030
- cdpProxy = new CDPProxy(browserWsUrl, targets[0].id);
1108
+ cdpProxy = new CDPProxy(browserWsUrl, targetId);
1031
1109
  const proxyWsUrl = await cdpProxy.start();
1032
1110
  browser = await import_playwright.chromium.connectOverCDP(proxyWsUrl, {
1033
1111
  timeout: timeout ?? 3e4
package/dist/index.js CHANGED
@@ -78,7 +78,7 @@ import {
78
78
  switchTab,
79
79
  typeText,
80
80
  waitForVisualStability
81
- } from "./chunk-G6V2DJRN.js";
81
+ } from "./chunk-H27BERRF.js";
82
82
  import {
83
83
  CloudCdpClient,
84
84
  CloudSessionClient,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opensteer",
3
- "version": "0.6.5",
3
+ "version": "0.6.6",
4
4
  "description": "Open-source browser automation SDK and CLI that lets AI agents build complex scrapers directly in your codebase.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -12,14 +12,14 @@ Read [opensteer-electron-workflow.md](references/opensteer-electron-workflow.md)
12
12
  ## Core Workflow
13
13
 
14
14
  1. Launch the Electron app with `--remote-debugging-port=<port>`.
15
- 2. Connect Opensteer with `open --connect-url http://127.0.0.1:<port>`.
15
+ 2. Connect Opensteer with `open --cdp-url http://127.0.0.1:<port>`.
16
16
  3. List windows/webviews with `tabs`, then switch with `tab-switch`.
17
17
  4. Run `snapshot action`, interact (`click`, `input`, `press`), then re-snapshot.
18
18
  5. Use `snapshot extraction` and `extract` for structured data.
19
19
 
20
20
  ```bash
21
21
  # Connect Opensteer to a running Electron app on port 9222
22
- opensteer open --connect-url http://127.0.0.1:9222 --session slack-desktop --name slack-desktop
22
+ opensteer open --cdp-url http://127.0.0.1:9222 --session slack-desktop --name slack-desktop
23
23
 
24
24
  # Discover available windows/webviews
25
25
  opensteer tabs --session slack-desktop
@@ -65,8 +65,8 @@ Use `--description` when possible so selector paths persist for replay.
65
65
  Use one Opensteer session per app:
66
66
 
67
67
  ```bash
68
- opensteer open --connect-url http://127.0.0.1:9222 --session slack --name slack-electron
69
- opensteer open --connect-url http://127.0.0.1:9223 --session vscode --name vscode-electron
68
+ opensteer open --cdp-url http://127.0.0.1:9222 --session slack --name slack-electron
69
+ opensteer open --cdp-url http://127.0.0.1:9223 --session vscode --name vscode-electron
70
70
 
71
71
  opensteer snapshot action --session slack
72
72
  opensteer snapshot action --session vscode
@@ -78,6 +78,8 @@ opensteer snapshot action --session vscode
78
78
  - Re-snapshot after each navigation or large UI change before reusing counters.
79
79
  - Keep `--name` stable inside a session for deterministic selector replay.
80
80
  - Close sessions when done: `opensteer close --session <id>`.
81
+ - If the app has no visible page targets, Opensteer creates one automatically.
82
+ - If `--cdp-url` connection fails, verify with `curl -s http://127.0.0.1:<port>/json/version`.
81
83
 
82
84
  ## References
83
85
 
@@ -29,7 +29,7 @@ curl -s http://127.0.0.1:9222/json/version
29
29
  ## Connect and Select Target
30
30
 
31
31
  ```bash
32
- opensteer open --connect-url http://127.0.0.1:9222 --session electron --name electron
32
+ opensteer open --cdp-url http://127.0.0.1:9222 --session electron --name electron
33
33
  opensteer tabs --session electron
34
34
  opensteer tab-switch 0 --session electron
35
35
  opensteer snapshot action --session electron
@@ -63,10 +63,10 @@ opensteer extract '{"items":[{"title":{"element":15},"meta":{"element":16}}]}' \
63
63
 
64
64
  ```bash
65
65
  # Slack
66
- opensteer open --connect-url http://127.0.0.1:9222 --session slack --name slack-electron
66
+ opensteer open --cdp-url http://127.0.0.1:9222 --session slack --name slack-electron
67
67
 
68
68
  # VS Code
69
- opensteer open --connect-url http://127.0.0.1:9223 --session vscode --name vscode-electron
69
+ opensteer open --cdp-url http://127.0.0.1:9223 --session vscode --name vscode-electron
70
70
 
71
71
  opensteer snapshot action --session slack
72
72
  opensteer snapshot action --session vscode
@@ -84,3 +84,5 @@ opensteer snapshot action --session vscode
84
84
  - UI changed and counters are stale; take a fresh `snapshot action`.
85
85
  - Wrong selectors on replay:
86
86
  - `--description` string differs from the original text; use exact wording.
87
+ - Connection works but extraction returns empty:
88
+ - The Electron app may render in a webview. Use `opensteer tabs` + `opensteer tab-switch` to find the correct target.
@@ -7,12 +7,12 @@ This document describes the Opensteer-native flow for automating Electron apps.
7
7
  Electron apps embed Chromium. Opensteer connects through Chrome DevTools Protocol (CDP):
8
8
 
9
9
  - Launch app with `--remote-debugging-port=<port>`
10
- - Attach with `opensteer open --connect-url http://127.0.0.1:<port>`
10
+ - Attach with `opensteer open --cdp-url http://127.0.0.1:<port>`
11
11
 
12
12
  Example:
13
13
 
14
14
  ```bash
15
- opensteer open --connect-url http://127.0.0.1:9222 --session electron --name electron
15
+ opensteer open --cdp-url http://127.0.0.1:9222 --session electron --name electron
16
16
  ```
17
17
 
18
18
  `--session` controls runtime routing (which daemon/browser instance handles commands).
@@ -166,11 +166,22 @@ Run with: `npx tsx scraper.ts`
166
166
 
167
167
  ## Edge Cases
168
168
 
169
- **Connect to an existing browser (CDP):**
169
+ **Connect to a running browser (CDP):**
170
170
  ```bash
171
- opensteer open --connect-url http://localhost:9222 --name "my-scraper"
172
- # Verify CDP is running: curl -s http://127.0.0.1:9222/json/version
171
+ # Verify CDP is reachable first:
172
+ curl -s http://127.0.0.1:9222/json/version
173
+
174
+ # Connect (works even if Chrome has zero open tabs):
175
+ opensteer open --cdp-url http://localhost:9222 --name "my-scraper"
176
+ ```
177
+
178
+ **Real browser mode (your actual Chrome profile):**
179
+ ```bash
180
+ opensteer open https://example.com --browser real --name "my-scraper" # headless
181
+ opensteer open https://example.com --browser real --headed --name "my-scraper" # visible window
182
+ opensteer open https://example.com --browser real --profile "Profile 1" --headed # specific profile
173
183
  ```
184
+ `--browser real` clones your local Chrome profile. Defaults to headless — add `--headed` to see the window. Profile cloning takes several seconds; do not assume the command hung.
174
185
 
175
186
  **Tab management:**
176
187
  ```bash
@@ -184,6 +195,9 @@ opensteer tab-close 1
184
195
  1. SPA content not loaded — add `opensteer.waitForText()` before extraction.
185
196
  2. Missing cache — re-run Phase 1 caching step for the page type that failed.
186
197
  3. Obstacle blocking target — cookie banner, modal, or login wall. Dismiss it first.
198
+ 4. Timeout on navigation — increase timeout: `opensteer navigate <url> --timeout 60000`.
199
+ 5. CDP connection refused — verify `curl -s http://127.0.0.1:<port>/json/version` returns JSON.
200
+ 6. Stale counters — take a fresh `snapshot action` and re-identify elements.
187
201
 
188
202
  ### Advanced: Direct Page Access (rare)
189
203
 
@@ -16,7 +16,10 @@ Global flags: `--session <id>`, `--name <namespace>`, `--headless`, `--descripti
16
16
  opensteer open <url> # Open browser, navigate to URL
17
17
  opensteer open <url> --name "my-scraper" # Set selector cache namespace
18
18
  opensteer open <url> --headless # Headless mode
19
- opensteer open --connect-url http://localhost:9222 # Connect to running browser
19
+ opensteer open --cdp-url http://localhost:9222 # Connect to running browser via CDP
20
+ opensteer open <url> --browser real # Use real Chrome profile (headless)
21
+ opensteer open <url> --browser real --headed # Real Chrome, visible window
22
+ opensteer open <url> --browser real --profile "Work" # Specific Chrome profile
20
23
  opensteer navigate <url> # Navigate with visual stability wait
21
24
  opensteer navigate <url> --timeout 60000 # Custom timeout (default 30s)
22
25
  opensteer back # Go back in history