opensteer 0.6.3 → 0.6.4

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.
Files changed (33) hide show
  1. package/bin/opensteer.mjs +94 -8
  2. package/dist/{browser-profile-client-DK9qa_Dj.d.cts → browser-profile-client-D6PuRefA.d.cts} +1 -1
  3. package/dist/{browser-profile-client-CaL-mwqs.d.ts → browser-profile-client-OUHaODro.d.ts} +1 -1
  4. package/dist/{chunk-SCNX4NN3.js → chunk-54KNQTOL.js} +141 -2
  5. package/dist/{chunk-FTKWQ6X3.js → chunk-6B6LOYU3.js} +1 -1
  6. package/dist/{chunk-3OMXCBPD.js → chunk-G6V2DJRN.js} +442 -591
  7. package/dist/chunk-K5CL76MG.js +81 -0
  8. package/dist/{chunk-KE35RQOJ.js → chunk-KPPOTU3D.js} +53 -144
  9. package/dist/cli/auth.cjs +53 -6
  10. package/dist/cli/auth.d.cts +1 -1
  11. package/dist/cli/auth.d.ts +1 -1
  12. package/dist/cli/auth.js +2 -2
  13. package/dist/cli/local-profile.cjs +197 -0
  14. package/dist/cli/local-profile.d.cts +18 -0
  15. package/dist/cli/local-profile.d.ts +18 -0
  16. package/dist/cli/local-profile.js +97 -0
  17. package/dist/cli/profile.cjs +2844 -2412
  18. package/dist/cli/profile.d.cts +2 -2
  19. package/dist/cli/profile.d.ts +2 -2
  20. package/dist/cli/profile.js +469 -7
  21. package/dist/cli/server.cjs +649 -204
  22. package/dist/cli/server.js +69 -16
  23. package/dist/index.cjs +578 -185
  24. package/dist/index.d.cts +7 -5
  25. package/dist/index.d.ts +7 -5
  26. package/dist/index.js +4 -3
  27. package/dist/{types-BxiRblC7.d.cts → types-BWItZPl_.d.cts} +31 -13
  28. package/dist/{types-BxiRblC7.d.ts → types-BWItZPl_.d.ts} +31 -13
  29. package/package.json +2 -2
  30. package/skills/opensteer/SKILL.md +34 -14
  31. package/skills/opensteer/references/cli-reference.md +1 -1
  32. package/skills/opensteer/references/examples.md +5 -3
  33. package/skills/opensteer/references/sdk-reference.md +16 -14
@@ -418,13 +418,19 @@ var init_extractor = __esm({
418
418
 
419
419
  // src/cli/server.ts
420
420
  var import_net = require("net");
421
- var import_fs4 = require("fs");
421
+ var import_fs5 = require("fs");
422
422
 
423
423
  // src/opensteer.ts
424
424
  var import_crypto = require("crypto");
425
425
 
426
426
  // src/browser/pool.ts
427
- var import_playwright2 = require("playwright");
427
+ var import_node_child_process = require("child_process");
428
+ var import_node_fs = require("fs");
429
+ var import_promises = require("fs/promises");
430
+ var import_node_net = require("net");
431
+ var import_node_os = require("os");
432
+ var import_node_path = require("path");
433
+ var import_playwright = require("playwright");
428
434
 
429
435
  // src/browser/cdp-proxy.ts
430
436
  var import_ws = __toESM(require("ws"), 1);
@@ -763,209 +769,263 @@ function errorMessage(error) {
763
769
  return error instanceof Error ? error.message : String(error);
764
770
  }
765
771
 
766
- // src/browser/chromium-profile.ts
767
- var import_node_util = require("util");
768
- var import_node_child_process2 = require("child_process");
769
- var import_node_crypto = require("crypto");
770
- var import_promises = require("fs/promises");
771
- var import_node_fs = require("fs");
772
- var import_node_path = require("path");
773
- var import_node_os = require("os");
774
- var import_playwright = require("playwright");
775
-
776
- // src/auth/keychain-store.ts
777
- var import_node_child_process = require("child_process");
778
-
779
772
  // src/browser/chrome.ts
780
773
  var import_os = require("os");
781
774
  var import_path = require("path");
775
+ var import_fs = require("fs");
776
+ function detectChromePaths() {
777
+ const os = (0, import_os.platform)();
778
+ if (os === "darwin") {
779
+ const executable2 = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
780
+ return {
781
+ executable: (0, import_fs.existsSync)(executable2) ? executable2 : null,
782
+ defaultUserDataDir: (0, import_path.join)(
783
+ (0, import_os.homedir)(),
784
+ "Library",
785
+ "Application Support",
786
+ "Google",
787
+ "Chrome"
788
+ )
789
+ };
790
+ }
791
+ if (os === "win32") {
792
+ const executable2 = (0, import_path.join)(
793
+ process.env.PROGRAMFILES || "C:\\Program Files",
794
+ "Google",
795
+ "Chrome",
796
+ "Application",
797
+ "chrome.exe"
798
+ );
799
+ return {
800
+ executable: (0, import_fs.existsSync)(executable2) ? executable2 : null,
801
+ defaultUserDataDir: (0, import_path.join)(
802
+ process.env.LOCALAPPDATA || (0, import_path.join)((0, import_os.homedir)(), "AppData", "Local"),
803
+ "Google",
804
+ "Chrome",
805
+ "User Data"
806
+ )
807
+ };
808
+ }
809
+ const executable = "/usr/bin/google-chrome";
810
+ return {
811
+ executable: (0, import_fs.existsSync)(executable) ? executable : null,
812
+ defaultUserDataDir: (0, import_path.join)((0, import_os.homedir)(), ".config", "google-chrome")
813
+ };
814
+ }
782
815
  function expandHome(p) {
783
816
  if (p.startsWith("~/") || p === "~") {
784
817
  return (0, import_path.join)((0, import_os.homedir)(), p.slice(1));
785
818
  }
786
819
  return p;
787
820
  }
788
-
789
- // src/browser/chromium-profile.ts
790
- var execFileAsync = (0, import_node_util.promisify)(import_node_child_process2.execFile);
791
- function directoryExists(filePath) {
792
- try {
793
- return (0, import_node_fs.statSync)(filePath).isDirectory();
794
- } catch {
795
- return false;
821
+ function listLocalChromeProfiles(userDataDir = detectChromePaths().defaultUserDataDir) {
822
+ const resolvedUserDataDir = expandHome(userDataDir);
823
+ const localStatePath = (0, import_path.join)(resolvedUserDataDir, "Local State");
824
+ if (!(0, import_fs.existsSync)(localStatePath)) {
825
+ return [];
796
826
  }
797
- }
798
- function fileExists(filePath) {
799
827
  try {
800
- return (0, import_node_fs.statSync)(filePath).isFile();
801
- } catch {
802
- return false;
803
- }
804
- }
805
- function resolveCookieDbPath(profileDir) {
806
- const candidates = [(0, import_node_path.join)(profileDir, "Network", "Cookies"), (0, import_node_path.join)(profileDir, "Cookies")];
807
- for (const candidate of candidates) {
808
- if (fileExists(candidate)) {
809
- return candidate;
828
+ const raw = JSON.parse((0, import_fs.readFileSync)(localStatePath, "utf-8"));
829
+ const infoCache = raw && typeof raw === "object" && !Array.isArray(raw) && raw.profile && typeof raw.profile === "object" && !Array.isArray(raw.profile) ? raw.profile.info_cache : void 0;
830
+ if (!infoCache || typeof infoCache !== "object") {
831
+ return [];
810
832
  }
833
+ return Object.entries(infoCache).map(([directory, info]) => {
834
+ const record = info && typeof info === "object" && !Array.isArray(info) ? info : {};
835
+ const name = typeof record.name === "string" && record.name.trim() ? record.name.trim() : directory;
836
+ return {
837
+ directory,
838
+ name
839
+ };
840
+ }).filter((profile) => profile.directory.trim().length > 0).sort(
841
+ (left, right) => left.directory.localeCompare(right.directory)
842
+ );
843
+ } catch {
844
+ return [];
811
845
  }
812
- return null;
813
- }
814
- function resolvePersistentChromiumLaunchProfile(inputPath) {
815
- const expandedPath = expandHome(inputPath.trim());
816
- if (!expandedPath) {
817
- return {
818
- userDataDir: inputPath
819
- };
820
- }
821
- if (fileExists(expandedPath) && (0, import_node_path.basename)(expandedPath) === "Cookies") {
822
- const directParent = (0, import_node_path.dirname)(expandedPath);
823
- const profileDir = (0, import_node_path.basename)(directParent) === "Network" ? (0, import_node_path.dirname)(directParent) : directParent;
824
- return {
825
- userDataDir: (0, import_node_path.dirname)(profileDir),
826
- profileDirectory: (0, import_node_path.basename)(profileDir)
827
- };
828
- }
829
- if (directoryExists(expandedPath) && resolveCookieDbPath(expandedPath) && fileExists((0, import_node_path.join)((0, import_node_path.dirname)(expandedPath), "Local State"))) {
830
- return {
831
- userDataDir: (0, import_node_path.dirname)(expandedPath),
832
- profileDirectory: (0, import_node_path.basename)(expandedPath)
833
- };
834
- }
835
- return {
836
- userDataDir: expandedPath
837
- };
838
846
  }
839
847
 
840
848
  // src/browser/pool.ts
841
849
  var BrowserPool = class {
842
850
  browser = null;
843
- persistentContext = null;
844
851
  cdpProxy = null;
852
+ launchedProcess = null;
853
+ tempUserDataDir = null;
845
854
  defaults;
846
855
  constructor(defaults = {}) {
847
856
  this.defaults = defaults;
848
857
  }
849
858
  async launch(options = {}) {
850
- if (this.browser || this.cdpProxy) {
859
+ if (this.browser || this.cdpProxy || this.launchedProcess || this.tempUserDataDir) {
851
860
  await this.close();
852
861
  }
853
- const connectUrl = options.connectUrl ?? this.defaults.connectUrl;
854
- const channel = options.channel ?? this.defaults.channel;
855
- const profileDir = options.profileDir ?? this.defaults.profileDir;
856
- if (connectUrl) {
857
- return this.connectToRunning(connectUrl, options.timeout);
862
+ const mode = options.mode ?? this.defaults.mode ?? "chromium";
863
+ const cdpUrl = options.cdpUrl ?? this.defaults.cdpUrl;
864
+ const userDataDir = options.userDataDir ?? this.defaults.userDataDir;
865
+ const profileDirectory = options.profileDirectory ?? this.defaults.profileDirectory;
866
+ const executablePath = options.executablePath ?? this.defaults.executablePath;
867
+ if (cdpUrl) {
868
+ if (mode === "real") {
869
+ throw new Error(
870
+ 'cdpUrl cannot be combined with mode "real". Use one browser launch path at a time.'
871
+ );
872
+ }
873
+ if (userDataDir || profileDirectory) {
874
+ throw new Error(
875
+ "userDataDir/profileDirectory cannot be combined with cdpUrl."
876
+ );
877
+ }
878
+ if (options.context && Object.keys(options.context).length > 0) {
879
+ throw new Error(
880
+ "context launch options are not supported when attaching over CDP."
881
+ );
882
+ }
883
+ return this.connectToRunning(cdpUrl, options.timeout);
858
884
  }
859
- if (profileDir) {
860
- return this.launchPersistentProfile(options, channel, profileDir);
885
+ if (mode !== "real" && (userDataDir || profileDirectory)) {
886
+ throw new Error(
887
+ 'userDataDir/profileDirectory require mode "real".'
888
+ );
861
889
  }
862
- if (channel) {
863
- return this.launchWithChannel(options, channel);
890
+ if (mode === "real") {
891
+ if (options.context && Object.keys(options.context).length > 0) {
892
+ throw new Error(
893
+ "context launch options are not supported for real-browser mode."
894
+ );
895
+ }
896
+ return this.launchOwnedRealBrowser({
897
+ ...options,
898
+ executablePath,
899
+ userDataDir,
900
+ profileDirectory
901
+ });
864
902
  }
865
903
  return this.launchSandbox(options);
866
904
  }
867
905
  async close() {
868
906
  const browser = this.browser;
869
- const persistentContext = this.persistentContext;
907
+ const cdpProxy = this.cdpProxy;
908
+ const launchedProcess = this.launchedProcess;
909
+ const tempUserDataDir = this.tempUserDataDir;
870
910
  this.browser = null;
871
- this.persistentContext = null;
911
+ this.cdpProxy = null;
912
+ this.launchedProcess = null;
913
+ this.tempUserDataDir = null;
872
914
  try {
873
- if (persistentContext) {
874
- await persistentContext.close();
875
- } else if (browser) {
876
- await browser.close();
915
+ if (browser) {
916
+ await browser.close().catch(() => void 0);
877
917
  }
878
918
  } finally {
879
- this.cdpProxy?.close();
880
- this.cdpProxy = null;
919
+ cdpProxy?.close();
920
+ await killProcessTree(launchedProcess);
921
+ if (tempUserDataDir) {
922
+ await (0, import_promises.rm)(tempUserDataDir, {
923
+ recursive: true,
924
+ force: true
925
+ }).catch(() => void 0);
926
+ }
881
927
  }
882
928
  }
883
- async connectToRunning(connectUrl, timeout) {
884
- this.cdpProxy?.close();
885
- this.cdpProxy = null;
929
+ async connectToRunning(cdpUrl, timeout) {
886
930
  let browser = null;
931
+ let cdpProxy = null;
887
932
  try {
888
- const { browserWsUrl, targets } = await discoverTargets(connectUrl);
933
+ const { browserWsUrl, targets } = await discoverTargets(cdpUrl);
889
934
  if (targets.length === 0) {
890
935
  throw new Error(
891
936
  "No page targets found. Is the browser running with an open window?"
892
937
  );
893
938
  }
894
- const target = targets[0];
895
- this.cdpProxy = new CDPProxy(browserWsUrl, target.id);
896
- const proxyWsUrl = await this.cdpProxy.start();
897
- browser = await import_playwright2.chromium.connectOverCDP(proxyWsUrl, {
939
+ cdpProxy = new CDPProxy(browserWsUrl, targets[0].id);
940
+ const proxyWsUrl = await cdpProxy.start();
941
+ browser = await import_playwright.chromium.connectOverCDP(proxyWsUrl, {
898
942
  timeout: timeout ?? 3e4
899
943
  });
900
944
  this.browser = browser;
901
- this.persistentContext = null;
902
- const contexts = browser.contexts();
903
- if (contexts.length === 0) {
904
- throw new Error(
905
- "Connection succeeded but no browser contexts found. Is the browser running with an open window?"
906
- );
907
- }
908
- const context = contexts[0];
909
- const pages = context.pages();
910
- const page = pages.length > 0 ? pages[0] : await context.newPage();
945
+ this.cdpProxy = cdpProxy;
946
+ const { context, page } = await pickBrowserContextAndPage(browser);
911
947
  return { browser, context, page, isExternal: true };
912
948
  } catch (error) {
913
949
  if (browser) {
914
950
  await browser.close().catch(() => void 0);
915
951
  }
952
+ cdpProxy?.close();
916
953
  this.browser = null;
917
- this.persistentContext = null;
918
- this.cdpProxy?.close();
919
954
  this.cdpProxy = null;
920
955
  throw error;
921
956
  }
922
957
  }
923
- async launchPersistentProfile(options, channel, profileDir) {
924
- const args = [];
925
- const launchProfile = resolvePersistentChromiumLaunchProfile(profileDir);
926
- if (launchProfile.profileDirectory) {
927
- args.push(`--profile-directory=${launchProfile.profileDirectory}`);
928
- }
929
- const context = await import_playwright2.chromium.launchPersistentContext(
930
- launchProfile.userDataDir,
931
- {
932
- channel,
933
- headless: options.headless ?? this.defaults.headless,
934
- executablePath: options.executablePath ?? this.defaults.executablePath ?? void 0,
935
- slowMo: options.slowMo ?? this.defaults.slowMo ?? 0,
936
- timeout: options.timeout,
937
- ...options.context || {},
938
- args
939
- }
940
- );
941
- const browser = context.browser();
942
- if (!browser) {
943
- await context.close().catch(() => void 0);
944
- throw new Error("Persistent browser launch did not expose a browser instance.");
958
+ async launchOwnedRealBrowser(options) {
959
+ const chromePaths = detectChromePaths();
960
+ const executablePath = options.executablePath ?? chromePaths.executable ?? void 0;
961
+ if (!executablePath) {
962
+ throw new Error(
963
+ "Chrome was not found. Set browser.executablePath or install Chrome in a supported location."
964
+ );
945
965
  }
946
- this.browser = browser;
947
- this.persistentContext = context;
948
- const pages = context.pages();
949
- const page = pages.length > 0 ? pages[0] : await context.newPage();
950
- return { browser, context, page, isExternal: false };
951
- }
952
- async launchWithChannel(options, channel) {
953
- const browser = await import_playwright2.chromium.launch({
954
- channel,
955
- headless: options.headless ?? this.defaults.headless,
956
- executablePath: options.executablePath ?? this.defaults.executablePath ?? void 0,
957
- slowMo: options.slowMo ?? this.defaults.slowMo ?? 0,
958
- timeout: options.timeout
966
+ const sourceUserDataDir = expandHome(
967
+ options.userDataDir ?? chromePaths.defaultUserDataDir
968
+ );
969
+ const profileDirectory = options.profileDirectory ?? "Default";
970
+ const tempUserDataDir = await cloneProfileToTempDir(
971
+ sourceUserDataDir,
972
+ profileDirectory
973
+ );
974
+ const debugPort = await reserveDebugPort();
975
+ const headless = resolveLaunchHeadless(
976
+ "real",
977
+ options.headless,
978
+ this.defaults.headless
979
+ );
980
+ const launchArgs = buildRealBrowserLaunchArgs({
981
+ userDataDir: tempUserDataDir,
982
+ profileDirectory,
983
+ debugPort,
984
+ headless
959
985
  });
960
- this.browser = browser;
961
- this.persistentContext = null;
962
- const context = await browser.newContext(options.context || {});
963
- const page = await context.newPage();
964
- return { browser, context, page, isExternal: false };
986
+ const processHandle = (0, import_node_child_process.spawn)(executablePath, launchArgs, {
987
+ detached: process.platform !== "win32",
988
+ stdio: "ignore"
989
+ });
990
+ processHandle.unref();
991
+ let browser = null;
992
+ try {
993
+ const wsUrl = await resolveCdpWebSocketUrl(
994
+ `http://127.0.0.1:${debugPort}`,
995
+ options.timeout ?? 3e4
996
+ );
997
+ browser = await import_playwright.chromium.connectOverCDP(wsUrl, {
998
+ timeout: options.timeout ?? 3e4
999
+ });
1000
+ const { context, page } = await createOwnedBrowserContextAndPage(
1001
+ browser,
1002
+ {
1003
+ headless,
1004
+ initialUrl: options.initialUrl,
1005
+ timeoutMs: options.timeout ?? 3e4
1006
+ }
1007
+ );
1008
+ this.browser = browser;
1009
+ this.launchedProcess = processHandle;
1010
+ this.tempUserDataDir = tempUserDataDir;
1011
+ return { browser, context, page, isExternal: false };
1012
+ } catch (error) {
1013
+ await browser?.close().catch(() => void 0);
1014
+ await killProcessTree(processHandle);
1015
+ await (0, import_promises.rm)(tempUserDataDir, {
1016
+ recursive: true,
1017
+ force: true
1018
+ }).catch(() => void 0);
1019
+ throw error;
1020
+ }
965
1021
  }
966
1022
  async launchSandbox(options) {
967
- const browser = await import_playwright2.chromium.launch({
968
- headless: options.headless ?? this.defaults.headless,
1023
+ const browser = await import_playwright.chromium.launch({
1024
+ headless: resolveLaunchHeadless(
1025
+ "chromium",
1026
+ options.headless,
1027
+ this.defaults.headless
1028
+ ),
969
1029
  executablePath: options.executablePath ?? this.defaults.executablePath ?? void 0,
970
1030
  slowMo: options.slowMo ?? this.defaults.slowMo ?? 0,
971
1031
  timeout: options.timeout
@@ -973,13 +1033,271 @@ var BrowserPool = class {
973
1033
  const context = await browser.newContext(options.context || {});
974
1034
  const page = await context.newPage();
975
1035
  this.browser = browser;
976
- this.persistentContext = null;
977
1036
  return { browser, context, page, isExternal: false };
978
1037
  }
979
1038
  };
1039
+ async function pickBrowserContextAndPage(browser) {
1040
+ const context = getPrimaryBrowserContext(browser);
1041
+ const pages = context.pages();
1042
+ const page = pages.find((candidate) => isInspectablePageUrl2(candidate.url())) || pages[0] || await context.newPage();
1043
+ return { context, page };
1044
+ }
1045
+ function resolveLaunchHeadless(mode, requestedHeadless, defaultHeadless) {
1046
+ if (requestedHeadless !== void 0) {
1047
+ return requestedHeadless;
1048
+ }
1049
+ if (defaultHeadless !== void 0) {
1050
+ return defaultHeadless;
1051
+ }
1052
+ return mode === "real";
1053
+ }
1054
+ async function createOwnedBrowserContextAndPage(browser, options) {
1055
+ const context = getPrimaryBrowserContext(browser);
1056
+ const page = await createOwnedBrowserPage(browser, context, options);
1057
+ return { context, page };
1058
+ }
1059
+ async function createOwnedBrowserPage(browser, context, options) {
1060
+ const targetUrl = options.initialUrl ?? "about:blank";
1061
+ const existingPages = new Set(context.pages());
1062
+ const browserSession = await browser.newBrowserCDPSession();
1063
+ try {
1064
+ const { targetId } = await browserSession.send("Target.createTarget", {
1065
+ url: targetUrl,
1066
+ newWindow: !options.headless
1067
+ });
1068
+ await browserSession.send("Target.activateTarget", { targetId }).catch(() => void 0);
1069
+ const page = await waitForOwnedBrowserPage(context, {
1070
+ existingPages,
1071
+ targetUrl,
1072
+ timeoutMs: options.timeoutMs
1073
+ });
1074
+ if (targetUrl !== "about:blank") {
1075
+ await page.waitForLoadState("domcontentloaded", {
1076
+ timeout: options.timeoutMs
1077
+ });
1078
+ }
1079
+ await closeDisposableStartupTargets(browserSession, targetId);
1080
+ return page;
1081
+ } finally {
1082
+ await browserSession.detach().catch(() => void 0);
1083
+ }
1084
+ }
1085
+ async function closeDisposableStartupTargets(browserSession, preservedTargetId) {
1086
+ const response = await browserSession.send("Target.getTargets").catch(() => null);
1087
+ if (!response) {
1088
+ return;
1089
+ }
1090
+ for (const targetInfo of response.targetInfos) {
1091
+ if (targetInfo.targetId === preservedTargetId || targetInfo.type !== "page" || !isDisposableStartupPageUrl(targetInfo.url)) {
1092
+ continue;
1093
+ }
1094
+ await browserSession.send("Target.closeTarget", { targetId: targetInfo.targetId }).catch(() => void 0);
1095
+ }
1096
+ }
1097
+ async function waitForOwnedBrowserPage(context, options) {
1098
+ const deadline = Date.now() + options.timeoutMs;
1099
+ let fallbackPage = null;
1100
+ while (Date.now() < deadline) {
1101
+ for (const candidate of context.pages()) {
1102
+ if (options.existingPages.has(candidate)) {
1103
+ continue;
1104
+ }
1105
+ const url = candidate.url();
1106
+ if (!isInspectablePageUrl2(url)) {
1107
+ continue;
1108
+ }
1109
+ fallbackPage ??= candidate;
1110
+ if (options.targetUrl === "about:blank") {
1111
+ return candidate;
1112
+ }
1113
+ if (pageLooselyMatchesUrl(url, options.targetUrl)) {
1114
+ return candidate;
1115
+ }
1116
+ }
1117
+ await sleep(100);
1118
+ }
1119
+ if (fallbackPage) {
1120
+ return fallbackPage;
1121
+ }
1122
+ throw new Error(
1123
+ `Chrome created a target for ${options.targetUrl}, but Playwright did not expose the page in time.`
1124
+ );
1125
+ }
1126
+ function getPrimaryBrowserContext(browser) {
1127
+ const contexts = browser.contexts();
1128
+ if (contexts.length === 0) {
1129
+ throw new Error(
1130
+ "Connection succeeded but no browser contexts were exposed."
1131
+ );
1132
+ }
1133
+ return contexts[0];
1134
+ }
1135
+ function isInspectablePageUrl2(url) {
1136
+ return url === "about:blank" || url.startsWith("http://") || url.startsWith("https://");
1137
+ }
1138
+ function isDisposableStartupPageUrl(url) {
1139
+ return url === "about:blank" || url === "chrome://newtab/" || url === "chrome://new-tab-page/";
1140
+ }
1141
+ function pageLooselyMatchesUrl(currentUrl, initialUrl) {
1142
+ try {
1143
+ const current = new URL(currentUrl);
1144
+ const requested = new URL(initialUrl);
1145
+ if (current.href === requested.href) {
1146
+ return true;
1147
+ }
1148
+ return current.hostname === requested.hostname && current.pathname === requested.pathname;
1149
+ } catch {
1150
+ return currentUrl === initialUrl;
1151
+ }
1152
+ }
1153
+ function normalizeDiscoveryUrl(cdpUrl) {
1154
+ let parsed;
1155
+ try {
1156
+ parsed = new URL(cdpUrl);
1157
+ } catch {
1158
+ throw new Error(
1159
+ `Invalid CDP URL "${cdpUrl}". Use an http(s) or ws(s) endpoint.`
1160
+ );
1161
+ }
1162
+ if (parsed.protocol === "ws:" || parsed.protocol === "wss:") {
1163
+ return parsed;
1164
+ }
1165
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
1166
+ throw new Error(
1167
+ `Unsupported CDP URL protocol "${parsed.protocol}". Use http(s) or ws(s).`
1168
+ );
1169
+ }
1170
+ const normalized = new URL(parsed.toString());
1171
+ normalized.pathname = "/json/version";
1172
+ normalized.search = "";
1173
+ normalized.hash = "";
1174
+ return normalized;
1175
+ }
1176
+ async function resolveCdpWebSocketUrl(cdpUrl, timeoutMs) {
1177
+ if (cdpUrl.startsWith("ws://") || cdpUrl.startsWith("wss://")) {
1178
+ return cdpUrl;
1179
+ }
1180
+ const versionUrl = normalizeDiscoveryUrl(cdpUrl);
1181
+ const deadline = Date.now() + timeoutMs;
1182
+ let lastError = "CDP discovery did not respond.";
1183
+ while (Date.now() < deadline) {
1184
+ const remaining = Math.max(deadline - Date.now(), 1e3);
1185
+ try {
1186
+ const response = await fetch(versionUrl, {
1187
+ signal: AbortSignal.timeout(Math.min(remaining, 5e3))
1188
+ });
1189
+ if (!response.ok) {
1190
+ lastError = `${response.status} ${response.statusText}`;
1191
+ } else {
1192
+ const payload = await response.json();
1193
+ const wsUrl = payload && typeof payload === "object" && !Array.isArray(payload) && typeof payload.webSocketDebuggerUrl === "string" ? payload.webSocketDebuggerUrl : null;
1194
+ if (wsUrl && wsUrl.trim()) {
1195
+ return wsUrl;
1196
+ }
1197
+ lastError = "CDP discovery response did not include webSocketDebuggerUrl.";
1198
+ }
1199
+ } catch (error) {
1200
+ lastError = error instanceof Error ? error.message : "Unknown error";
1201
+ }
1202
+ await sleep(100);
1203
+ }
1204
+ throw new Error(
1205
+ `Failed to resolve a CDP websocket URL from ${versionUrl.toString()}: ${lastError}`
1206
+ );
1207
+ }
1208
+ async function reserveDebugPort() {
1209
+ return await new Promise((resolve, reject) => {
1210
+ const server2 = (0, import_node_net.createServer)();
1211
+ server2.unref();
1212
+ server2.on("error", reject);
1213
+ server2.listen(0, "127.0.0.1", () => {
1214
+ const address = server2.address();
1215
+ if (!address || typeof address === "string") {
1216
+ server2.close();
1217
+ reject(new Error("Failed to reserve a local debug port."));
1218
+ return;
1219
+ }
1220
+ server2.close((error) => {
1221
+ if (error) {
1222
+ reject(error);
1223
+ return;
1224
+ }
1225
+ resolve(address.port);
1226
+ });
1227
+ });
1228
+ });
1229
+ }
1230
+ async function cloneProfileToTempDir(userDataDir, profileDirectory) {
1231
+ const resolvedUserDataDir = expandHome(userDataDir);
1232
+ const tempUserDataDir = await (0, import_promises.mkdtemp)(
1233
+ (0, import_node_path.join)((0, import_node_os.tmpdir)(), "opensteer-real-browser-")
1234
+ );
1235
+ const sourceProfileDir = (0, import_node_path.join)(resolvedUserDataDir, profileDirectory);
1236
+ const targetProfileDir = (0, import_node_path.join)(tempUserDataDir, profileDirectory);
1237
+ if ((0, import_node_fs.existsSync)(sourceProfileDir)) {
1238
+ await (0, import_promises.cp)(sourceProfileDir, targetProfileDir, {
1239
+ recursive: true
1240
+ });
1241
+ } else {
1242
+ await (0, import_promises.mkdir)(targetProfileDir, {
1243
+ recursive: true
1244
+ });
1245
+ }
1246
+ const localStatePath = (0, import_node_path.join)(resolvedUserDataDir, "Local State");
1247
+ if ((0, import_node_fs.existsSync)(localStatePath)) {
1248
+ await (0, import_promises.copyFile)(localStatePath, (0, import_node_path.join)(tempUserDataDir, "Local State"));
1249
+ }
1250
+ return tempUserDataDir;
1251
+ }
1252
+ function buildRealBrowserLaunchArgs(options) {
1253
+ const args = [
1254
+ `--user-data-dir=${options.userDataDir}`,
1255
+ `--profile-directory=${options.profileDirectory}`,
1256
+ `--remote-debugging-port=${options.debugPort}`,
1257
+ "--no-first-run",
1258
+ "--no-default-browser-check",
1259
+ "--disable-background-networking",
1260
+ "--disable-sync",
1261
+ "--disable-popup-blocking"
1262
+ ];
1263
+ if (options.headless) {
1264
+ args.push("--headless=new");
1265
+ }
1266
+ return args;
1267
+ }
1268
+ async function killProcessTree(processHandle) {
1269
+ if (!processHandle || processHandle.pid == null || processHandle.exitCode !== null) {
1270
+ return;
1271
+ }
1272
+ if (process.platform === "win32") {
1273
+ await new Promise((resolve) => {
1274
+ const killer = (0, import_node_child_process.spawn)(
1275
+ "taskkill",
1276
+ ["/pid", String(processHandle.pid), "/t", "/f"],
1277
+ {
1278
+ stdio: "ignore"
1279
+ }
1280
+ );
1281
+ killer.on("error", () => resolve());
1282
+ killer.on("exit", () => resolve());
1283
+ });
1284
+ return;
1285
+ }
1286
+ try {
1287
+ process.kill(-processHandle.pid, "SIGKILL");
1288
+ } catch {
1289
+ try {
1290
+ processHandle.kill("SIGKILL");
1291
+ } catch {
1292
+ }
1293
+ }
1294
+ }
1295
+ async function sleep(ms) {
1296
+ await new Promise((resolve) => setTimeout(resolve, ms));
1297
+ }
980
1298
 
981
1299
  // src/config.ts
982
- var import_fs = __toESM(require("fs"), 1);
1300
+ var import_fs2 = __toESM(require("fs"), 1);
983
1301
  var import_path3 = __toESM(require("path"), 1);
984
1302
  var import_url = require("url");
985
1303
  var import_dotenv = require("dotenv");
@@ -1307,9 +1625,10 @@ var DEFAULT_CONFIG = {
1307
1625
  headless: false,
1308
1626
  executablePath: void 0,
1309
1627
  slowMo: 0,
1310
- connectUrl: void 0,
1311
- channel: void 0,
1312
- profileDir: void 0
1628
+ mode: void 0,
1629
+ cdpUrl: void 0,
1630
+ userDataDir: void 0,
1631
+ profileDirectory: void 0
1313
1632
  },
1314
1633
  storage: {
1315
1634
  rootDir: process.cwd()
@@ -1346,9 +1665,9 @@ function loadDotenvValues(rootDir, baseEnv, options = {}) {
1346
1665
  const nodeEnv = baseEnv.NODE_ENV?.trim() || "";
1347
1666
  for (const filename of dotenvFileOrder(nodeEnv)) {
1348
1667
  const filePath = import_path3.default.join(baseDir, filename);
1349
- if (!import_fs.default.existsSync(filePath)) continue;
1668
+ if (!import_fs2.default.existsSync(filePath)) continue;
1350
1669
  try {
1351
- const raw = import_fs.default.readFileSync(filePath, "utf8");
1670
+ const raw = import_fs2.default.readFileSync(filePath, "utf8");
1352
1671
  const parsed = (0, import_dotenv.parse)(raw);
1353
1672
  for (const [key, value] of Object.entries(parsed)) {
1354
1673
  if (values[key] === void 0) {
@@ -1418,9 +1737,9 @@ function assertNoLegacyRuntimeConfig(source, config) {
1418
1737
  }
1419
1738
  function loadConfigFile(rootDir, options = {}) {
1420
1739
  const configPath = import_path3.default.join(rootDir, ".opensteer", "config.json");
1421
- if (!import_fs.default.existsSync(configPath)) return {};
1740
+ if (!import_fs2.default.existsSync(configPath)) return {};
1422
1741
  try {
1423
- const raw = import_fs.default.readFileSync(configPath, "utf8");
1742
+ const raw = import_fs2.default.readFileSync(configPath, "utf8");
1424
1743
  return JSON.parse(raw);
1425
1744
  } catch (error) {
1426
1745
  const message = extractErrorMessage(
@@ -1653,6 +1972,8 @@ function resolveConfigWithEnv(input = {}, options = {}) {
1653
1972
  const fileHasCloudApiKey = hasOwn(fileCloudOptions, "apiKey");
1654
1973
  const fileHasCloudAccessToken = hasOwn(fileCloudOptions, "accessToken");
1655
1974
  const fileRootDir = typeof fileConfig.storage?.rootDir === "string" ? fileConfig.storage.rootDir : void 0;
1975
+ assertNoRemovedBrowserConfig(input.browser, "Opensteer constructor config");
1976
+ assertNoRemovedBrowserConfig(fileConfig.browser, ".opensteer/config.json");
1656
1977
  const envRootDir = input.storage?.rootDir ?? fileRootDir ?? initialRootDir;
1657
1978
  const env = resolveEnv(envRootDir, {
1658
1979
  debug: debugHint,
@@ -1668,14 +1989,30 @@ function resolveConfigWithEnv(input = {}, options = {}) {
1668
1989
  "OPENSTEER_RUNTIME is no longer supported. Use OPENSTEER_MODE instead."
1669
1990
  );
1670
1991
  }
1992
+ if (env.OPENSTEER_CONNECT_URL != null) {
1993
+ throw new Error(
1994
+ "OPENSTEER_CONNECT_URL is no longer supported. Use OPENSTEER_CDP_URL instead."
1995
+ );
1996
+ }
1997
+ if (env.OPENSTEER_CHANNEL != null) {
1998
+ throw new Error(
1999
+ "OPENSTEER_CHANNEL is no longer supported. Use OPENSTEER_BROWSER plus OPENSTEER_BROWSER_PATH when needed."
2000
+ );
2001
+ }
2002
+ if (env.OPENSTEER_PROFILE_DIR != null) {
2003
+ throw new Error(
2004
+ "OPENSTEER_PROFILE_DIR is no longer supported. Use OPENSTEER_USER_DATA_DIR and OPENSTEER_PROFILE_DIRECTORY instead."
2005
+ );
2006
+ }
1671
2007
  const envConfig = {
1672
2008
  browser: {
1673
2009
  headless: parseBool(env.OPENSTEER_HEADLESS),
1674
2010
  executablePath: env.OPENSTEER_BROWSER_PATH || void 0,
1675
2011
  slowMo: parseNumber(env.OPENSTEER_SLOW_MO),
1676
- connectUrl: env.OPENSTEER_CONNECT_URL || void 0,
1677
- channel: env.OPENSTEER_CHANNEL || void 0,
1678
- profileDir: env.OPENSTEER_PROFILE_DIR || void 0
2012
+ mode: env.OPENSTEER_BROWSER === "real" || env.OPENSTEER_BROWSER === "chromium" ? env.OPENSTEER_BROWSER : void 0,
2013
+ cdpUrl: env.OPENSTEER_CDP_URL || void 0,
2014
+ userDataDir: env.OPENSTEER_USER_DATA_DIR || void 0,
2015
+ profileDirectory: env.OPENSTEER_PROFILE_DIRECTORY || void 0
1679
2016
  },
1680
2017
  cursor: {
1681
2018
  enabled: parseBool(env.OPENSTEER_CURSOR)
@@ -1686,6 +2023,34 @@ function resolveConfigWithEnv(input = {}, options = {}) {
1686
2023
  const mergedWithFile = mergeDeep(runtimeDefaults, fileConfig);
1687
2024
  const mergedWithEnv = mergeDeep(mergedWithFile, envConfig);
1688
2025
  const resolved = mergeDeep(mergedWithEnv, input);
2026
+ const browserHeadlessExplicit = input.browser?.headless !== void 0 || fileConfig.browser?.headless !== void 0 || envConfig.browser?.headless !== void 0;
2027
+ if (!browserHeadlessExplicit && resolved.browser?.mode === "real") {
2028
+ resolved.browser = {
2029
+ ...resolved.browser,
2030
+ headless: true
2031
+ };
2032
+ }
2033
+ function assertNoRemovedBrowserConfig(value, source) {
2034
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
2035
+ return;
2036
+ }
2037
+ const record = value;
2038
+ if (record.connectUrl !== void 0) {
2039
+ throw new Error(
2040
+ `${source}.browser.connectUrl is no longer supported. Use browser.cdpUrl instead.`
2041
+ );
2042
+ }
2043
+ if (record.channel !== void 0) {
2044
+ throw new Error(
2045
+ `${source}.browser.channel is no longer supported. Use browser.mode plus browser.executablePath instead.`
2046
+ );
2047
+ }
2048
+ if (record.profileDir !== void 0) {
2049
+ throw new Error(
2050
+ `${source}.browser.profileDir is no longer supported. Use browser.userDataDir and browser.profileDirectory instead.`
2051
+ );
2052
+ }
2053
+ }
1689
2054
  const envApiKey = resolveOpensteerApiKey(env);
1690
2055
  const envAccessTokenRaw = resolveOpensteerAccessToken(env);
1691
2056
  const envBaseUrl = resolveOpensteerBaseUrl(env);
@@ -2201,7 +2566,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
2201
2566
  TRANSIENT_CONTEXT_RETRY_DELAY_MS,
2202
2567
  Math.max(0, deadline - Date.now())
2203
2568
  );
2204
- await sleep(retryDelay);
2569
+ await sleep2(retryDelay);
2205
2570
  }
2206
2571
  }
2207
2572
  }
@@ -2234,7 +2599,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
2234
2599
  () => ({ kind: "resolved" }),
2235
2600
  (error) => ({ kind: "rejected", error })
2236
2601
  );
2237
- const timeoutPromise = sleep(
2602
+ const timeoutPromise = sleep2(
2238
2603
  timeout + FRAME_EVALUATE_GRACE_MS
2239
2604
  ).then(() => ({ kind: "timeout" }));
2240
2605
  const result = await Promise.race([
@@ -2376,14 +2741,14 @@ function isIgnorableFrameError(error) {
2376
2741
  const message = error.message;
2377
2742
  return message.includes("Frame was detached") || message.includes("Target page, context or browser has been closed") || isTransientExecutionContextError(error) || message.includes("No frame for given id found");
2378
2743
  }
2379
- function sleep(ms) {
2744
+ function sleep2(ms) {
2380
2745
  return new Promise((resolve) => {
2381
2746
  setTimeout(resolve, ms);
2382
2747
  });
2383
2748
  }
2384
2749
 
2385
2750
  // src/storage/local.ts
2386
- var import_fs2 = __toESM(require("fs"), 1);
2751
+ var import_fs3 = __toESM(require("fs"), 1);
2387
2752
  var import_path4 = __toESM(require("path"), 1);
2388
2753
 
2389
2754
  // src/storage/registry.ts
@@ -2427,14 +2792,14 @@ var LocalSelectorStorage = class {
2427
2792
  return import_path4.default.join(this.getNamespaceDir(), this.getSelectorFileName(id));
2428
2793
  }
2429
2794
  ensureDirs() {
2430
- import_fs2.default.mkdirSync(this.getNamespaceDir(), { recursive: true });
2795
+ import_fs3.default.mkdirSync(this.getNamespaceDir(), { recursive: true });
2431
2796
  }
2432
2797
  loadRegistry() {
2433
2798
  this.ensureDirs();
2434
2799
  const file = this.getRegistryPath();
2435
- if (!import_fs2.default.existsSync(file)) return createEmptyRegistry(this.namespace);
2800
+ if (!import_fs3.default.existsSync(file)) return createEmptyRegistry(this.namespace);
2436
2801
  try {
2437
- const raw = import_fs2.default.readFileSync(file, "utf8");
2802
+ const raw = import_fs3.default.readFileSync(file, "utf8");
2438
2803
  return JSON.parse(raw);
2439
2804
  } catch (error) {
2440
2805
  const message = extractErrorMessage(
@@ -2451,16 +2816,16 @@ var LocalSelectorStorage = class {
2451
2816
  }
2452
2817
  saveRegistry(registry) {
2453
2818
  this.ensureDirs();
2454
- import_fs2.default.writeFileSync(
2819
+ import_fs3.default.writeFileSync(
2455
2820
  this.getRegistryPath(),
2456
2821
  JSON.stringify(registry, null, 2)
2457
2822
  );
2458
2823
  }
2459
2824
  readSelector(id) {
2460
2825
  const file = this.getSelectorPath(id);
2461
- if (!import_fs2.default.existsSync(file)) return null;
2826
+ if (!import_fs3.default.existsSync(file)) return null;
2462
2827
  try {
2463
- const raw = import_fs2.default.readFileSync(file, "utf8");
2828
+ const raw = import_fs3.default.readFileSync(file, "utf8");
2464
2829
  return JSON.parse(raw);
2465
2830
  } catch (error) {
2466
2831
  const message = extractErrorMessage(
@@ -2477,15 +2842,15 @@ var LocalSelectorStorage = class {
2477
2842
  }
2478
2843
  writeSelector(payload) {
2479
2844
  this.ensureDirs();
2480
- import_fs2.default.writeFileSync(
2845
+ import_fs3.default.writeFileSync(
2481
2846
  this.getSelectorPath(payload.id),
2482
2847
  JSON.stringify(payload, null, 2)
2483
2848
  );
2484
2849
  }
2485
2850
  clearNamespace() {
2486
2851
  const dir = this.getNamespaceDir();
2487
- if (!import_fs2.default.existsSync(dir)) return;
2488
- import_fs2.default.rmSync(dir, { recursive: true, force: true });
2852
+ if (!import_fs3.default.existsSync(dir)) return;
2853
+ import_fs3.default.rmSync(dir, { recursive: true, force: true });
2489
2854
  }
2490
2855
  };
2491
2856
 
@@ -6929,7 +7294,7 @@ var AdaptiveNetworkTracker = class {
6929
7294
  this.idleSince = 0;
6930
7295
  }
6931
7296
  const remaining = Math.max(1, options.deadline - now);
6932
- await sleep2(Math.min(NETWORK_POLL_MS, remaining));
7297
+ await sleep3(Math.min(NETWORK_POLL_MS, remaining));
6933
7298
  }
6934
7299
  }
6935
7300
  handleRequestStarted = (request) => {
@@ -6974,7 +7339,7 @@ var AdaptiveNetworkTracker = class {
6974
7339
  return false;
6975
7340
  }
6976
7341
  };
6977
- async function sleep2(ms) {
7342
+ async function sleep3(ms) {
6978
7343
  await new Promise((resolve) => {
6979
7344
  setTimeout(resolve, ms);
6980
7345
  });
@@ -8451,15 +8816,15 @@ function withTokenQuery(wsUrl, token) {
8451
8816
  }
8452
8817
 
8453
8818
  // src/cloud/local-cache-sync.ts
8454
- var import_fs3 = __toESM(require("fs"), 1);
8819
+ var import_fs4 = __toESM(require("fs"), 1);
8455
8820
  var import_path5 = __toESM(require("path"), 1);
8456
8821
  function collectLocalSelectorCacheEntries(storage, options = {}) {
8457
8822
  const debug = options.debug === true;
8458
8823
  const namespace = storage.getNamespace();
8459
8824
  const namespaceDir = storage.getNamespaceDir();
8460
- if (!import_fs3.default.existsSync(namespaceDir)) return [];
8825
+ if (!import_fs4.default.existsSync(namespaceDir)) return [];
8461
8826
  const entries = [];
8462
- const fileNames = import_fs3.default.readdirSync(namespaceDir);
8827
+ const fileNames = import_fs4.default.readdirSync(namespaceDir);
8463
8828
  for (const fileName of fileNames) {
8464
8829
  if (fileName === "index.json" || !fileName.endsWith(".json")) continue;
8465
8830
  const filePath = import_path5.default.join(namespaceDir, fileName);
@@ -8490,7 +8855,7 @@ function collectLocalSelectorCacheEntries(storage, options = {}) {
8490
8855
  }
8491
8856
  function readSelectorFile(filePath, debug) {
8492
8857
  try {
8493
- const raw = import_fs3.default.readFileSync(filePath, "utf8");
8858
+ const raw = import_fs4.default.readFileSync(filePath, "utf8");
8494
8859
  return JSON.parse(raw);
8495
8860
  } catch (error) {
8496
8861
  const message = extractErrorMessage(
@@ -8585,13 +8950,13 @@ function dedupeNewest(entries) {
8585
8950
  }
8586
8951
 
8587
8952
  // src/cloud/cdp-client.ts
8588
- var import_playwright3 = require("playwright");
8953
+ var import_playwright2 = require("playwright");
8589
8954
  var CloudCdpClient = class {
8590
8955
  async connect(args) {
8591
8956
  const endpoint = withTokenQuery2(args.wsUrl, args.token);
8592
8957
  let browser;
8593
8958
  try {
8594
- browser = await import_playwright3.chromium.connectOverCDP(endpoint);
8959
+ browser = await import_playwright2.chromium.connectOverCDP(endpoint);
8595
8960
  } catch (error) {
8596
8961
  const message = error instanceof Error ? error.message : "Failed to connect to cloud CDP endpoint.";
8597
8962
  throw new OpensteerCloudError("CLOUD_TRANSPORT_ERROR", message);
@@ -10384,7 +10749,7 @@ async function executeAgentAction(page, action) {
10384
10749
  }
10385
10750
  case "wait": {
10386
10751
  const ms = numberOr(action.timeMs, action.time_ms, 1e3);
10387
- await sleep3(ms);
10752
+ await sleep4(ms);
10388
10753
  return;
10389
10754
  }
10390
10755
  case "goto": {
@@ -10549,7 +10914,7 @@ async function pressKeyCombo(page, combo) {
10549
10914
  }
10550
10915
  }
10551
10916
  }
10552
- function sleep3(ms) {
10917
+ function sleep4(ms) {
10553
10918
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
10554
10919
  }
10555
10920
 
@@ -10580,7 +10945,7 @@ var OpensteerCuaAgentHandler = class {
10580
10945
  if (isMutatingAgentAction(action)) {
10581
10946
  this.onMutatingAction?.(action);
10582
10947
  }
10583
- await sleep4(this.config.waitBetweenActionsMs);
10948
+ await sleep5(this.config.waitBetweenActionsMs);
10584
10949
  });
10585
10950
  try {
10586
10951
  const result = await this.client.execute({
@@ -10642,7 +11007,7 @@ var OpensteerCuaAgentHandler = class {
10642
11007
  await this.cursorController.preview({ x, y }, "agent");
10643
11008
  }
10644
11009
  };
10645
- function sleep4(ms) {
11010
+ function sleep5(ms) {
10646
11011
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
10647
11012
  }
10648
11013
 
@@ -11078,7 +11443,7 @@ var CursorController = class {
11078
11443
  for (const step of motion.points) {
11079
11444
  await this.renderer.move(step, this.style);
11080
11445
  if (motion.stepDelayMs > 0) {
11081
- await sleep5(motion.stepDelayMs);
11446
+ await sleep6(motion.stepDelayMs);
11082
11447
  }
11083
11448
  }
11084
11449
  if (shouldPulse(intent)) {
@@ -11236,7 +11601,7 @@ function clamp2(value, min, max) {
11236
11601
  function shouldPulse(intent) {
11237
11602
  return intent === "click" || intent === "dblclick" || intent === "rightclick" || intent === "agent";
11238
11603
  }
11239
- function sleep5(ms) {
11604
+ function sleep6(ms) {
11240
11605
  return new Promise((resolve) => setTimeout(resolve, ms));
11241
11606
  }
11242
11607
 
@@ -11621,9 +11986,11 @@ var Opensteer = class _Opensteer {
11621
11986
  }
11622
11987
  const session2 = await this.pool.launch({
11623
11988
  ...options,
11624
- connectUrl: options.connectUrl ?? this.config.browser?.connectUrl,
11625
- channel: options.channel ?? this.config.browser?.channel,
11626
- profileDir: options.profileDir ?? this.config.browser?.profileDir
11989
+ mode: options.mode ?? this.config.browser?.mode,
11990
+ cdpUrl: options.cdpUrl ?? this.config.browser?.cdpUrl,
11991
+ userDataDir: options.userDataDir ?? this.config.browser?.userDataDir,
11992
+ profileDirectory: options.profileDirectory ?? this.config.browser?.profileDirectory,
11993
+ executablePath: options.executablePath ?? this.config.browser?.executablePath
11627
11994
  });
11628
11995
  this.browser = session2.browser;
11629
11996
  this.contextRef = session2.context;
@@ -11654,6 +12021,32 @@ var Opensteer = class _Opensteer {
11654
12021
  instance2.snapshotCache = null;
11655
12022
  return instance2;
11656
12023
  }
12024
+ static listLocalProfiles(userDataDir) {
12025
+ return listLocalChromeProfiles(userDataDir);
12026
+ }
12027
+ static fromSystemChrome(browser = {}, config = {}) {
12028
+ const chromePaths = detectChromePaths();
12029
+ const executablePath = browser.executablePath ?? config.browser?.executablePath ?? chromePaths.executable ?? void 0;
12030
+ if (!executablePath) {
12031
+ throw new Error(
12032
+ "Chrome was not found. Pass executablePath explicitly or install Chrome in a supported location."
12033
+ );
12034
+ }
12035
+ const userDataDir = browser.userDataDir ?? config.browser?.userDataDir ?? chromePaths.defaultUserDataDir;
12036
+ const autoDetectedProfiles = listLocalChromeProfiles(userDataDir);
12037
+ const profileDirectory = browser.profileDirectory ?? config.browser?.profileDirectory ?? autoDetectedProfiles[0]?.directory ?? "Default";
12038
+ return new _Opensteer({
12039
+ ...config,
12040
+ browser: {
12041
+ ...config.browser || {},
12042
+ mode: "real",
12043
+ headless: browser.headless ?? config.browser?.headless ?? true,
12044
+ executablePath,
12045
+ userDataDir,
12046
+ profileDirectory
12047
+ }
12048
+ });
12049
+ }
11657
12050
  async close() {
11658
12051
  this.snapshotCache = null;
11659
12052
  if (this.cloud) {
@@ -14387,10 +14780,12 @@ function buildServerOpenConfig(options) {
14387
14780
  enabled: options.cursorEnabled
14388
14781
  },
14389
14782
  browser: {
14390
- headless: options.headless ?? false,
14391
- connectUrl: options.connectUrl,
14392
- channel: options.channel,
14393
- profileDir: options.profileDir
14783
+ headless: options.headless,
14784
+ mode: options.mode,
14785
+ cdpUrl: options.cdpUrl,
14786
+ userDataDir: options.userDataDir,
14787
+ profileDirectory: options.profileDirectory,
14788
+ executablePath: options.executablePath
14394
14789
  }
14395
14790
  };
14396
14791
  if (!options.cloudAuth) {
@@ -14442,6 +14837,19 @@ function normalizeAuthScheme(value) {
14442
14837
  );
14443
14838
  }
14444
14839
 
14840
+ // src/cli/open-browser-config.ts
14841
+ function resolveCliBrowserRequestConfig(options) {
14842
+ const mode = options.browser ?? (options.profileDirectory || options.userDataDir || options.executablePath ? "real" : void 0);
14843
+ return {
14844
+ mode,
14845
+ headless: options.headless ?? (mode === "real" ? true : void 0),
14846
+ cdpUrl: options.cdpUrl,
14847
+ profileDirectory: options.profileDirectory,
14848
+ userDataDir: options.userDataDir,
14849
+ executablePath: options.executablePath
14850
+ };
14851
+ }
14852
+
14445
14853
  // src/cli/server.ts
14446
14854
  var instance = null;
14447
14855
  var launchPromise = null;
@@ -14516,15 +14924,15 @@ var socketPath = getSocketPath(session);
14516
14924
  var pidPath = getPidPath(session);
14517
14925
  function cleanup() {
14518
14926
  try {
14519
- (0, import_fs4.unlinkSync)(socketPath);
14927
+ (0, import_fs5.unlinkSync)(socketPath);
14520
14928
  } catch {
14521
14929
  }
14522
14930
  try {
14523
- (0, import_fs4.unlinkSync)(pidPath);
14931
+ (0, import_fs5.unlinkSync)(pidPath);
14524
14932
  } catch {
14525
14933
  }
14526
14934
  try {
14527
- (0, import_fs4.unlinkSync)(getMetadataPath(session));
14935
+ (0, import_fs5.unlinkSync)(getMetadataPath(session));
14528
14936
  } catch {
14529
14937
  }
14530
14938
  }
@@ -14602,9 +15010,11 @@ async function handleRequest(request, socket) {
14602
15010
  try {
14603
15011
  const url = args.url;
14604
15012
  const headless = args.headless;
14605
- const connectUrl = args["connect-url"];
14606
- const channel = args.channel;
14607
- const profileDir = args["profile-dir"];
15013
+ const browser = args.browser === "real" || args.browser === "chromium" ? args.browser : args.browser === void 0 ? void 0 : null;
15014
+ const cdpUrl = args["cdp-url"];
15015
+ const profileDirectory = args.profile;
15016
+ const userDataDir = args["user-data-dir"];
15017
+ const executablePath = args["browser-path"];
14608
15018
  const cloudProfileId = typeof args["cloud-profile-id"] === "string" ? args["cloud-profile-id"].trim() : void 0;
14609
15019
  const cloudProfileReuseIfActive = typeof args["cloud-profile-reuse-if-active"] === "boolean" ? args["cloud-profile-reuse-if-active"] : void 0;
14610
15020
  const requestedCloudProfileBinding = normalizeCloudProfileBinding({
@@ -14619,6 +15029,21 @@ async function handleRequest(request, socket) {
14619
15029
  "--cloud-profile-reuse-if-active requires --cloud-profile-id."
14620
15030
  );
14621
15031
  }
15032
+ if (browser === null) {
15033
+ throw new Error(
15034
+ '--browser must be either "chromium" or "real".'
15035
+ );
15036
+ }
15037
+ if (browser === "chromium" && (profileDirectory || userDataDir || executablePath)) {
15038
+ throw new Error(
15039
+ "--profile, --user-data-dir, and --browser-path require --browser real."
15040
+ );
15041
+ }
15042
+ if (cdpUrl && browser === "real") {
15043
+ throw new Error(
15044
+ "--cdp-url cannot be combined with --browser real."
15045
+ );
15046
+ }
14622
15047
  const requestedCursor = normalizeCursorFlag(args.cursor);
14623
15048
  const requestedName = typeof args.name === "string" && args.name.trim().length > 0 ? sanitizeNamespace(args.name) : null;
14624
15049
  if (requestedCursor !== null) {
@@ -14660,26 +15085,45 @@ async function handleRequest(request, socket) {
14660
15085
  invalidateInstance();
14661
15086
  }
14662
15087
  }
15088
+ const requestedBrowserConfig = resolveCliBrowserRequestConfig({
15089
+ browser: browser ?? void 0,
15090
+ headless,
15091
+ cdpUrl,
15092
+ profileDirectory,
15093
+ userDataDir,
15094
+ executablePath
15095
+ });
14663
15096
  if (instance && !launchPromise) {
14664
15097
  assertCompatibleCloudProfileBinding(
14665
15098
  logicalSession,
14666
15099
  cloudProfileBinding,
14667
15100
  requestedCloudProfileBinding
14668
15101
  );
15102
+ const existingBrowserConfig = instance.getConfig().browser || {};
15103
+ const existingBrowserRecord = existingBrowserConfig;
15104
+ const mismatch = Object.entries(requestedBrowserConfig).find(
15105
+ ([key, value]) => value !== void 0 && existingBrowserRecord[key] !== value
15106
+ );
15107
+ if (mismatch) {
15108
+ const [key, value] = mismatch;
15109
+ throw new Error(
15110
+ `Session '${logicalSession}' is already bound to browser setting "${key}"=${JSON.stringify(existingBrowserRecord[key])}. Requested ${JSON.stringify(value)} does not match. Use the same browser flags for this session or start a different --session.`
15111
+ );
15112
+ }
14669
15113
  }
15114
+ let shouldLaunchInitialUrl = false;
14670
15115
  if (!instance) {
14671
15116
  instance = new Opensteer(
14672
15117
  buildServerOpenConfig({
14673
15118
  scopeDir,
14674
15119
  name: activeNamespace,
14675
15120
  cursorEnabled: effectiveCursorEnabled,
14676
- headless,
14677
- connectUrl,
14678
- channel,
14679
- profileDir,
15121
+ ...requestedBrowserConfig,
14680
15122
  cloudAuth: cloudAuthOverride
14681
15123
  })
14682
15124
  );
15125
+ const resolvedBrowserConfig = instance.getConfig().browser || {};
15126
+ shouldLaunchInitialUrl = Boolean(url) && resolvedBrowserConfig.mode === "real" && !resolvedBrowserConfig.cdpUrl;
14683
15127
  const nextCloudProfileBinding = resolveSessionCloudProfileBinding(
14684
15128
  instance.getConfig(),
14685
15129
  requestedCloudProfileBinding
@@ -14691,12 +15135,13 @@ async function handleRequest(request, socket) {
14691
15135
  );
14692
15136
  }
14693
15137
  launchPromise = instance.launch({
14694
- headless: headless ?? false,
15138
+ initialUrl: shouldLaunchInitialUrl ? url : void 0,
15139
+ ...requestedBrowserConfig,
14695
15140
  cloudBrowserProfile: cloudProfileId ? {
14696
15141
  profileId: cloudProfileId,
14697
15142
  reuseIfActive: cloudProfileReuseIfActive
14698
15143
  } : void 0,
14699
- timeout: connectUrl ? 12e4 : 3e4
15144
+ timeout: cdpUrl ? 12e4 : 3e4
14700
15145
  });
14701
15146
  try {
14702
15147
  await launchPromise;
@@ -14714,7 +15159,7 @@ async function handleRequest(request, socket) {
14714
15159
  } else if (requestedCursor !== null) {
14715
15160
  instance.setCursorEnabled(requestedCursor);
14716
15161
  }
14717
- if (url) {
15162
+ if (url && !shouldLaunchInitialUrl) {
14718
15163
  await instance.goto(url);
14719
15164
  }
14720
15165
  sendResponse(socket, {
@@ -14844,8 +15289,8 @@ async function handleRequest(request, socket) {
14844
15289
  );
14845
15290
  }
14846
15291
  }
14847
- if ((0, import_fs4.existsSync)(socketPath)) {
14848
- (0, import_fs4.unlinkSync)(socketPath);
15292
+ if ((0, import_fs5.existsSync)(socketPath)) {
15293
+ (0, import_fs5.unlinkSync)(socketPath);
14849
15294
  }
14850
15295
  var server = (0, import_net.createServer)((socket) => {
14851
15296
  let buffer = "";
@@ -14875,7 +15320,7 @@ var server = (0, import_net.createServer)((socket) => {
14875
15320
  });
14876
15321
  });
14877
15322
  server.listen(socketPath, () => {
14878
- (0, import_fs4.writeFileSync)(pidPath, String(process.pid));
15323
+ (0, import_fs5.writeFileSync)(pidPath, String(process.pid));
14879
15324
  if (process.send) {
14880
15325
  process.send("ready");
14881
15326
  }