opensteer 0.6.2 → 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-7RMY26CM.js → chunk-54KNQTOL.js} +172 -55
  5. package/dist/{chunk-WJI7TGBQ.js → chunk-6B6LOYU3.js} +1 -1
  6. package/dist/{chunk-F2VDVOJO.js → chunk-G6V2DJRN.js} +451 -609
  7. package/dist/chunk-K5CL76MG.js +81 -0
  8. package/dist/{chunk-WDRMHPWL.js → chunk-KPPOTU3D.js} +159 -180
  9. package/dist/cli/auth.cjs +186 -95
  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 +1747 -1279
  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 +759 -257
  22. package/dist/cli/server.js +69 -16
  23. package/dist/index.cjs +688 -238
  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
package/dist/index.cjs CHANGED
@@ -515,7 +515,13 @@ module.exports = __toCommonJS(index_exports);
515
515
  var import_crypto = require("crypto");
516
516
 
517
517
  // src/browser/pool.ts
518
- var import_playwright2 = require("playwright");
518
+ var import_node_child_process = require("child_process");
519
+ var import_node_fs = require("fs");
520
+ var import_promises = require("fs/promises");
521
+ var import_node_net = require("net");
522
+ var import_node_os = require("os");
523
+ var import_node_path = require("path");
524
+ var import_playwright = require("playwright");
519
525
 
520
526
  // src/browser/cdp-proxy.ts
521
527
  var import_ws = __toESM(require("ws"), 1);
@@ -854,209 +860,263 @@ function errorMessage(error) {
854
860
  return error instanceof Error ? error.message : String(error);
855
861
  }
856
862
 
857
- // src/browser/chromium-profile.ts
858
- var import_node_util = require("util");
859
- var import_node_child_process2 = require("child_process");
860
- var import_node_crypto = require("crypto");
861
- var import_promises = require("fs/promises");
862
- var import_node_fs = require("fs");
863
- var import_node_path = require("path");
864
- var import_node_os = require("os");
865
- var import_playwright = require("playwright");
866
-
867
- // src/auth/keychain-store.ts
868
- var import_node_child_process = require("child_process");
869
-
870
863
  // src/browser/chrome.ts
871
864
  var import_os = require("os");
872
865
  var import_path = require("path");
866
+ var import_fs = require("fs");
867
+ function detectChromePaths() {
868
+ const os = (0, import_os.platform)();
869
+ if (os === "darwin") {
870
+ const executable2 = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
871
+ return {
872
+ executable: (0, import_fs.existsSync)(executable2) ? executable2 : null,
873
+ defaultUserDataDir: (0, import_path.join)(
874
+ (0, import_os.homedir)(),
875
+ "Library",
876
+ "Application Support",
877
+ "Google",
878
+ "Chrome"
879
+ )
880
+ };
881
+ }
882
+ if (os === "win32") {
883
+ const executable2 = (0, import_path.join)(
884
+ process.env.PROGRAMFILES || "C:\\Program Files",
885
+ "Google",
886
+ "Chrome",
887
+ "Application",
888
+ "chrome.exe"
889
+ );
890
+ return {
891
+ executable: (0, import_fs.existsSync)(executable2) ? executable2 : null,
892
+ defaultUserDataDir: (0, import_path.join)(
893
+ process.env.LOCALAPPDATA || (0, import_path.join)((0, import_os.homedir)(), "AppData", "Local"),
894
+ "Google",
895
+ "Chrome",
896
+ "User Data"
897
+ )
898
+ };
899
+ }
900
+ const executable = "/usr/bin/google-chrome";
901
+ return {
902
+ executable: (0, import_fs.existsSync)(executable) ? executable : null,
903
+ defaultUserDataDir: (0, import_path.join)((0, import_os.homedir)(), ".config", "google-chrome")
904
+ };
905
+ }
873
906
  function expandHome(p) {
874
907
  if (p.startsWith("~/") || p === "~") {
875
908
  return (0, import_path.join)((0, import_os.homedir)(), p.slice(1));
876
909
  }
877
910
  return p;
878
911
  }
879
-
880
- // src/browser/chromium-profile.ts
881
- var execFileAsync = (0, import_node_util.promisify)(import_node_child_process2.execFile);
882
- function directoryExists(filePath) {
883
- try {
884
- return (0, import_node_fs.statSync)(filePath).isDirectory();
885
- } catch {
886
- return false;
912
+ function listLocalChromeProfiles(userDataDir = detectChromePaths().defaultUserDataDir) {
913
+ const resolvedUserDataDir = expandHome(userDataDir);
914
+ const localStatePath = (0, import_path.join)(resolvedUserDataDir, "Local State");
915
+ if (!(0, import_fs.existsSync)(localStatePath)) {
916
+ return [];
887
917
  }
888
- }
889
- function fileExists(filePath) {
890
918
  try {
891
- return (0, import_node_fs.statSync)(filePath).isFile();
892
- } catch {
893
- return false;
894
- }
895
- }
896
- function resolveCookieDbPath(profileDir) {
897
- const candidates = [(0, import_node_path.join)(profileDir, "Network", "Cookies"), (0, import_node_path.join)(profileDir, "Cookies")];
898
- for (const candidate of candidates) {
899
- if (fileExists(candidate)) {
900
- return candidate;
919
+ const raw = JSON.parse((0, import_fs.readFileSync)(localStatePath, "utf-8"));
920
+ 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;
921
+ if (!infoCache || typeof infoCache !== "object") {
922
+ return [];
901
923
  }
924
+ return Object.entries(infoCache).map(([directory, info]) => {
925
+ const record = info && typeof info === "object" && !Array.isArray(info) ? info : {};
926
+ const name = typeof record.name === "string" && record.name.trim() ? record.name.trim() : directory;
927
+ return {
928
+ directory,
929
+ name
930
+ };
931
+ }).filter((profile) => profile.directory.trim().length > 0).sort(
932
+ (left, right) => left.directory.localeCompare(right.directory)
933
+ );
934
+ } catch {
935
+ return [];
902
936
  }
903
- return null;
904
- }
905
- function resolvePersistentChromiumLaunchProfile(inputPath) {
906
- const expandedPath = expandHome(inputPath.trim());
907
- if (!expandedPath) {
908
- return {
909
- userDataDir: inputPath
910
- };
911
- }
912
- if (fileExists(expandedPath) && (0, import_node_path.basename)(expandedPath) === "Cookies") {
913
- const directParent = (0, import_node_path.dirname)(expandedPath);
914
- const profileDir = (0, import_node_path.basename)(directParent) === "Network" ? (0, import_node_path.dirname)(directParent) : directParent;
915
- return {
916
- userDataDir: (0, import_node_path.dirname)(profileDir),
917
- profileDirectory: (0, import_node_path.basename)(profileDir)
918
- };
919
- }
920
- if (directoryExists(expandedPath) && resolveCookieDbPath(expandedPath) && fileExists((0, import_node_path.join)((0, import_node_path.dirname)(expandedPath), "Local State"))) {
921
- return {
922
- userDataDir: (0, import_node_path.dirname)(expandedPath),
923
- profileDirectory: (0, import_node_path.basename)(expandedPath)
924
- };
925
- }
926
- return {
927
- userDataDir: expandedPath
928
- };
929
937
  }
930
938
 
931
939
  // src/browser/pool.ts
932
940
  var BrowserPool = class {
933
941
  browser = null;
934
- persistentContext = null;
935
942
  cdpProxy = null;
943
+ launchedProcess = null;
944
+ tempUserDataDir = null;
936
945
  defaults;
937
946
  constructor(defaults = {}) {
938
947
  this.defaults = defaults;
939
948
  }
940
949
  async launch(options = {}) {
941
- if (this.browser || this.cdpProxy) {
950
+ if (this.browser || this.cdpProxy || this.launchedProcess || this.tempUserDataDir) {
942
951
  await this.close();
943
952
  }
944
- const connectUrl = options.connectUrl ?? this.defaults.connectUrl;
945
- const channel = options.channel ?? this.defaults.channel;
946
- const profileDir = options.profileDir ?? this.defaults.profileDir;
947
- if (connectUrl) {
948
- return this.connectToRunning(connectUrl, options.timeout);
953
+ const mode = options.mode ?? this.defaults.mode ?? "chromium";
954
+ const cdpUrl = options.cdpUrl ?? this.defaults.cdpUrl;
955
+ const userDataDir = options.userDataDir ?? this.defaults.userDataDir;
956
+ const profileDirectory = options.profileDirectory ?? this.defaults.profileDirectory;
957
+ const executablePath = options.executablePath ?? this.defaults.executablePath;
958
+ if (cdpUrl) {
959
+ if (mode === "real") {
960
+ throw new Error(
961
+ 'cdpUrl cannot be combined with mode "real". Use one browser launch path at a time.'
962
+ );
963
+ }
964
+ if (userDataDir || profileDirectory) {
965
+ throw new Error(
966
+ "userDataDir/profileDirectory cannot be combined with cdpUrl."
967
+ );
968
+ }
969
+ if (options.context && Object.keys(options.context).length > 0) {
970
+ throw new Error(
971
+ "context launch options are not supported when attaching over CDP."
972
+ );
973
+ }
974
+ return this.connectToRunning(cdpUrl, options.timeout);
949
975
  }
950
- if (profileDir) {
951
- return this.launchPersistentProfile(options, channel, profileDir);
976
+ if (mode !== "real" && (userDataDir || profileDirectory)) {
977
+ throw new Error(
978
+ 'userDataDir/profileDirectory require mode "real".'
979
+ );
952
980
  }
953
- if (channel) {
954
- return this.launchWithChannel(options, channel);
981
+ if (mode === "real") {
982
+ if (options.context && Object.keys(options.context).length > 0) {
983
+ throw new Error(
984
+ "context launch options are not supported for real-browser mode."
985
+ );
986
+ }
987
+ return this.launchOwnedRealBrowser({
988
+ ...options,
989
+ executablePath,
990
+ userDataDir,
991
+ profileDirectory
992
+ });
955
993
  }
956
994
  return this.launchSandbox(options);
957
995
  }
958
996
  async close() {
959
997
  const browser = this.browser;
960
- const persistentContext = this.persistentContext;
998
+ const cdpProxy = this.cdpProxy;
999
+ const launchedProcess = this.launchedProcess;
1000
+ const tempUserDataDir = this.tempUserDataDir;
961
1001
  this.browser = null;
962
- this.persistentContext = null;
1002
+ this.cdpProxy = null;
1003
+ this.launchedProcess = null;
1004
+ this.tempUserDataDir = null;
963
1005
  try {
964
- if (persistentContext) {
965
- await persistentContext.close();
966
- } else if (browser) {
967
- await browser.close();
1006
+ if (browser) {
1007
+ await browser.close().catch(() => void 0);
968
1008
  }
969
1009
  } finally {
970
- this.cdpProxy?.close();
971
- this.cdpProxy = null;
1010
+ cdpProxy?.close();
1011
+ await killProcessTree(launchedProcess);
1012
+ if (tempUserDataDir) {
1013
+ await (0, import_promises.rm)(tempUserDataDir, {
1014
+ recursive: true,
1015
+ force: true
1016
+ }).catch(() => void 0);
1017
+ }
972
1018
  }
973
1019
  }
974
- async connectToRunning(connectUrl, timeout) {
975
- this.cdpProxy?.close();
976
- this.cdpProxy = null;
1020
+ async connectToRunning(cdpUrl, timeout) {
977
1021
  let browser = null;
1022
+ let cdpProxy = null;
978
1023
  try {
979
- const { browserWsUrl, targets } = await discoverTargets(connectUrl);
1024
+ const { browserWsUrl, targets } = await discoverTargets(cdpUrl);
980
1025
  if (targets.length === 0) {
981
1026
  throw new Error(
982
1027
  "No page targets found. Is the browser running with an open window?"
983
1028
  );
984
1029
  }
985
- const target = targets[0];
986
- this.cdpProxy = new CDPProxy(browserWsUrl, target.id);
987
- const proxyWsUrl = await this.cdpProxy.start();
988
- browser = await import_playwright2.chromium.connectOverCDP(proxyWsUrl, {
1030
+ cdpProxy = new CDPProxy(browserWsUrl, targets[0].id);
1031
+ const proxyWsUrl = await cdpProxy.start();
1032
+ browser = await import_playwright.chromium.connectOverCDP(proxyWsUrl, {
989
1033
  timeout: timeout ?? 3e4
990
1034
  });
991
1035
  this.browser = browser;
992
- this.persistentContext = null;
993
- const contexts = browser.contexts();
994
- if (contexts.length === 0) {
995
- throw new Error(
996
- "Connection succeeded but no browser contexts found. Is the browser running with an open window?"
997
- );
998
- }
999
- const context = contexts[0];
1000
- const pages = context.pages();
1001
- const page = pages.length > 0 ? pages[0] : await context.newPage();
1036
+ this.cdpProxy = cdpProxy;
1037
+ const { context, page } = await pickBrowserContextAndPage(browser);
1002
1038
  return { browser, context, page, isExternal: true };
1003
1039
  } catch (error) {
1004
1040
  if (browser) {
1005
1041
  await browser.close().catch(() => void 0);
1006
1042
  }
1043
+ cdpProxy?.close();
1007
1044
  this.browser = null;
1008
- this.persistentContext = null;
1009
- this.cdpProxy?.close();
1010
1045
  this.cdpProxy = null;
1011
1046
  throw error;
1012
1047
  }
1013
1048
  }
1014
- async launchPersistentProfile(options, channel, profileDir) {
1015
- const args = [];
1016
- const launchProfile = resolvePersistentChromiumLaunchProfile(profileDir);
1017
- if (launchProfile.profileDirectory) {
1018
- args.push(`--profile-directory=${launchProfile.profileDirectory}`);
1019
- }
1020
- const context = await import_playwright2.chromium.launchPersistentContext(
1021
- launchProfile.userDataDir,
1022
- {
1023
- channel,
1024
- headless: options.headless ?? this.defaults.headless,
1025
- executablePath: options.executablePath ?? this.defaults.executablePath ?? void 0,
1026
- slowMo: options.slowMo ?? this.defaults.slowMo ?? 0,
1027
- timeout: options.timeout,
1028
- ...options.context || {},
1029
- args
1030
- }
1031
- );
1032
- const browser = context.browser();
1033
- if (!browser) {
1034
- await context.close().catch(() => void 0);
1035
- throw new Error("Persistent browser launch did not expose a browser instance.");
1049
+ async launchOwnedRealBrowser(options) {
1050
+ const chromePaths = detectChromePaths();
1051
+ const executablePath = options.executablePath ?? chromePaths.executable ?? void 0;
1052
+ if (!executablePath) {
1053
+ throw new Error(
1054
+ "Chrome was not found. Set browser.executablePath or install Chrome in a supported location."
1055
+ );
1036
1056
  }
1037
- this.browser = browser;
1038
- this.persistentContext = context;
1039
- const pages = context.pages();
1040
- const page = pages.length > 0 ? pages[0] : await context.newPage();
1041
- return { browser, context, page, isExternal: false };
1042
- }
1043
- async launchWithChannel(options, channel) {
1044
- const browser = await import_playwright2.chromium.launch({
1045
- channel,
1046
- headless: options.headless ?? this.defaults.headless,
1047
- executablePath: options.executablePath ?? this.defaults.executablePath ?? void 0,
1048
- slowMo: options.slowMo ?? this.defaults.slowMo ?? 0,
1049
- timeout: options.timeout
1057
+ const sourceUserDataDir = expandHome(
1058
+ options.userDataDir ?? chromePaths.defaultUserDataDir
1059
+ );
1060
+ const profileDirectory = options.profileDirectory ?? "Default";
1061
+ const tempUserDataDir = await cloneProfileToTempDir(
1062
+ sourceUserDataDir,
1063
+ profileDirectory
1064
+ );
1065
+ const debugPort = await reserveDebugPort();
1066
+ const headless = resolveLaunchHeadless(
1067
+ "real",
1068
+ options.headless,
1069
+ this.defaults.headless
1070
+ );
1071
+ const launchArgs = buildRealBrowserLaunchArgs({
1072
+ userDataDir: tempUserDataDir,
1073
+ profileDirectory,
1074
+ debugPort,
1075
+ headless
1050
1076
  });
1051
- this.browser = browser;
1052
- this.persistentContext = null;
1053
- const context = await browser.newContext(options.context || {});
1054
- const page = await context.newPage();
1055
- return { browser, context, page, isExternal: false };
1077
+ const processHandle = (0, import_node_child_process.spawn)(executablePath, launchArgs, {
1078
+ detached: process.platform !== "win32",
1079
+ stdio: "ignore"
1080
+ });
1081
+ processHandle.unref();
1082
+ let browser = null;
1083
+ try {
1084
+ const wsUrl = await resolveCdpWebSocketUrl(
1085
+ `http://127.0.0.1:${debugPort}`,
1086
+ options.timeout ?? 3e4
1087
+ );
1088
+ browser = await import_playwright.chromium.connectOverCDP(wsUrl, {
1089
+ timeout: options.timeout ?? 3e4
1090
+ });
1091
+ const { context, page } = await createOwnedBrowserContextAndPage(
1092
+ browser,
1093
+ {
1094
+ headless,
1095
+ initialUrl: options.initialUrl,
1096
+ timeoutMs: options.timeout ?? 3e4
1097
+ }
1098
+ );
1099
+ this.browser = browser;
1100
+ this.launchedProcess = processHandle;
1101
+ this.tempUserDataDir = tempUserDataDir;
1102
+ return { browser, context, page, isExternal: false };
1103
+ } catch (error) {
1104
+ await browser?.close().catch(() => void 0);
1105
+ await killProcessTree(processHandle);
1106
+ await (0, import_promises.rm)(tempUserDataDir, {
1107
+ recursive: true,
1108
+ force: true
1109
+ }).catch(() => void 0);
1110
+ throw error;
1111
+ }
1056
1112
  }
1057
1113
  async launchSandbox(options) {
1058
- const browser = await import_playwright2.chromium.launch({
1059
- headless: options.headless ?? this.defaults.headless,
1114
+ const browser = await import_playwright.chromium.launch({
1115
+ headless: resolveLaunchHeadless(
1116
+ "chromium",
1117
+ options.headless,
1118
+ this.defaults.headless
1119
+ ),
1060
1120
  executablePath: options.executablePath ?? this.defaults.executablePath ?? void 0,
1061
1121
  slowMo: options.slowMo ?? this.defaults.slowMo ?? 0,
1062
1122
  timeout: options.timeout
@@ -1064,13 +1124,271 @@ var BrowserPool = class {
1064
1124
  const context = await browser.newContext(options.context || {});
1065
1125
  const page = await context.newPage();
1066
1126
  this.browser = browser;
1067
- this.persistentContext = null;
1068
1127
  return { browser, context, page, isExternal: false };
1069
1128
  }
1070
1129
  };
1130
+ async function pickBrowserContextAndPage(browser) {
1131
+ const context = getPrimaryBrowserContext(browser);
1132
+ const pages = context.pages();
1133
+ const page = pages.find((candidate) => isInspectablePageUrl2(candidate.url())) || pages[0] || await context.newPage();
1134
+ return { context, page };
1135
+ }
1136
+ function resolveLaunchHeadless(mode, requestedHeadless, defaultHeadless) {
1137
+ if (requestedHeadless !== void 0) {
1138
+ return requestedHeadless;
1139
+ }
1140
+ if (defaultHeadless !== void 0) {
1141
+ return defaultHeadless;
1142
+ }
1143
+ return mode === "real";
1144
+ }
1145
+ async function createOwnedBrowserContextAndPage(browser, options) {
1146
+ const context = getPrimaryBrowserContext(browser);
1147
+ const page = await createOwnedBrowserPage(browser, context, options);
1148
+ return { context, page };
1149
+ }
1150
+ async function createOwnedBrowserPage(browser, context, options) {
1151
+ const targetUrl = options.initialUrl ?? "about:blank";
1152
+ const existingPages = new Set(context.pages());
1153
+ const browserSession = await browser.newBrowserCDPSession();
1154
+ try {
1155
+ const { targetId } = await browserSession.send("Target.createTarget", {
1156
+ url: targetUrl,
1157
+ newWindow: !options.headless
1158
+ });
1159
+ await browserSession.send("Target.activateTarget", { targetId }).catch(() => void 0);
1160
+ const page = await waitForOwnedBrowserPage(context, {
1161
+ existingPages,
1162
+ targetUrl,
1163
+ timeoutMs: options.timeoutMs
1164
+ });
1165
+ if (targetUrl !== "about:blank") {
1166
+ await page.waitForLoadState("domcontentloaded", {
1167
+ timeout: options.timeoutMs
1168
+ });
1169
+ }
1170
+ await closeDisposableStartupTargets(browserSession, targetId);
1171
+ return page;
1172
+ } finally {
1173
+ await browserSession.detach().catch(() => void 0);
1174
+ }
1175
+ }
1176
+ async function closeDisposableStartupTargets(browserSession, preservedTargetId) {
1177
+ const response = await browserSession.send("Target.getTargets").catch(() => null);
1178
+ if (!response) {
1179
+ return;
1180
+ }
1181
+ for (const targetInfo of response.targetInfos) {
1182
+ if (targetInfo.targetId === preservedTargetId || targetInfo.type !== "page" || !isDisposableStartupPageUrl(targetInfo.url)) {
1183
+ continue;
1184
+ }
1185
+ await browserSession.send("Target.closeTarget", { targetId: targetInfo.targetId }).catch(() => void 0);
1186
+ }
1187
+ }
1188
+ async function waitForOwnedBrowserPage(context, options) {
1189
+ const deadline = Date.now() + options.timeoutMs;
1190
+ let fallbackPage = null;
1191
+ while (Date.now() < deadline) {
1192
+ for (const candidate of context.pages()) {
1193
+ if (options.existingPages.has(candidate)) {
1194
+ continue;
1195
+ }
1196
+ const url = candidate.url();
1197
+ if (!isInspectablePageUrl2(url)) {
1198
+ continue;
1199
+ }
1200
+ fallbackPage ??= candidate;
1201
+ if (options.targetUrl === "about:blank") {
1202
+ return candidate;
1203
+ }
1204
+ if (pageLooselyMatchesUrl(url, options.targetUrl)) {
1205
+ return candidate;
1206
+ }
1207
+ }
1208
+ await sleep(100);
1209
+ }
1210
+ if (fallbackPage) {
1211
+ return fallbackPage;
1212
+ }
1213
+ throw new Error(
1214
+ `Chrome created a target for ${options.targetUrl}, but Playwright did not expose the page in time.`
1215
+ );
1216
+ }
1217
+ function getPrimaryBrowserContext(browser) {
1218
+ const contexts = browser.contexts();
1219
+ if (contexts.length === 0) {
1220
+ throw new Error(
1221
+ "Connection succeeded but no browser contexts were exposed."
1222
+ );
1223
+ }
1224
+ return contexts[0];
1225
+ }
1226
+ function isInspectablePageUrl2(url) {
1227
+ return url === "about:blank" || url.startsWith("http://") || url.startsWith("https://");
1228
+ }
1229
+ function isDisposableStartupPageUrl(url) {
1230
+ return url === "about:blank" || url === "chrome://newtab/" || url === "chrome://new-tab-page/";
1231
+ }
1232
+ function pageLooselyMatchesUrl(currentUrl, initialUrl) {
1233
+ try {
1234
+ const current = new URL(currentUrl);
1235
+ const requested = new URL(initialUrl);
1236
+ if (current.href === requested.href) {
1237
+ return true;
1238
+ }
1239
+ return current.hostname === requested.hostname && current.pathname === requested.pathname;
1240
+ } catch {
1241
+ return currentUrl === initialUrl;
1242
+ }
1243
+ }
1244
+ function normalizeDiscoveryUrl(cdpUrl) {
1245
+ let parsed;
1246
+ try {
1247
+ parsed = new URL(cdpUrl);
1248
+ } catch {
1249
+ throw new Error(
1250
+ `Invalid CDP URL "${cdpUrl}". Use an http(s) or ws(s) endpoint.`
1251
+ );
1252
+ }
1253
+ if (parsed.protocol === "ws:" || parsed.protocol === "wss:") {
1254
+ return parsed;
1255
+ }
1256
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
1257
+ throw new Error(
1258
+ `Unsupported CDP URL protocol "${parsed.protocol}". Use http(s) or ws(s).`
1259
+ );
1260
+ }
1261
+ const normalized = new URL(parsed.toString());
1262
+ normalized.pathname = "/json/version";
1263
+ normalized.search = "";
1264
+ normalized.hash = "";
1265
+ return normalized;
1266
+ }
1267
+ async function resolveCdpWebSocketUrl(cdpUrl, timeoutMs) {
1268
+ if (cdpUrl.startsWith("ws://") || cdpUrl.startsWith("wss://")) {
1269
+ return cdpUrl;
1270
+ }
1271
+ const versionUrl = normalizeDiscoveryUrl(cdpUrl);
1272
+ const deadline = Date.now() + timeoutMs;
1273
+ let lastError = "CDP discovery did not respond.";
1274
+ while (Date.now() < deadline) {
1275
+ const remaining = Math.max(deadline - Date.now(), 1e3);
1276
+ try {
1277
+ const response = await fetch(versionUrl, {
1278
+ signal: AbortSignal.timeout(Math.min(remaining, 5e3))
1279
+ });
1280
+ if (!response.ok) {
1281
+ lastError = `${response.status} ${response.statusText}`;
1282
+ } else {
1283
+ const payload = await response.json();
1284
+ const wsUrl = payload && typeof payload === "object" && !Array.isArray(payload) && typeof payload.webSocketDebuggerUrl === "string" ? payload.webSocketDebuggerUrl : null;
1285
+ if (wsUrl && wsUrl.trim()) {
1286
+ return wsUrl;
1287
+ }
1288
+ lastError = "CDP discovery response did not include webSocketDebuggerUrl.";
1289
+ }
1290
+ } catch (error) {
1291
+ lastError = error instanceof Error ? error.message : "Unknown error";
1292
+ }
1293
+ await sleep(100);
1294
+ }
1295
+ throw new Error(
1296
+ `Failed to resolve a CDP websocket URL from ${versionUrl.toString()}: ${lastError}`
1297
+ );
1298
+ }
1299
+ async function reserveDebugPort() {
1300
+ return await new Promise((resolve, reject) => {
1301
+ const server = (0, import_node_net.createServer)();
1302
+ server.unref();
1303
+ server.on("error", reject);
1304
+ server.listen(0, "127.0.0.1", () => {
1305
+ const address = server.address();
1306
+ if (!address || typeof address === "string") {
1307
+ server.close();
1308
+ reject(new Error("Failed to reserve a local debug port."));
1309
+ return;
1310
+ }
1311
+ server.close((error) => {
1312
+ if (error) {
1313
+ reject(error);
1314
+ return;
1315
+ }
1316
+ resolve(address.port);
1317
+ });
1318
+ });
1319
+ });
1320
+ }
1321
+ async function cloneProfileToTempDir(userDataDir, profileDirectory) {
1322
+ const resolvedUserDataDir = expandHome(userDataDir);
1323
+ const tempUserDataDir = await (0, import_promises.mkdtemp)(
1324
+ (0, import_node_path.join)((0, import_node_os.tmpdir)(), "opensteer-real-browser-")
1325
+ );
1326
+ const sourceProfileDir = (0, import_node_path.join)(resolvedUserDataDir, profileDirectory);
1327
+ const targetProfileDir = (0, import_node_path.join)(tempUserDataDir, profileDirectory);
1328
+ if ((0, import_node_fs.existsSync)(sourceProfileDir)) {
1329
+ await (0, import_promises.cp)(sourceProfileDir, targetProfileDir, {
1330
+ recursive: true
1331
+ });
1332
+ } else {
1333
+ await (0, import_promises.mkdir)(targetProfileDir, {
1334
+ recursive: true
1335
+ });
1336
+ }
1337
+ const localStatePath = (0, import_node_path.join)(resolvedUserDataDir, "Local State");
1338
+ if ((0, import_node_fs.existsSync)(localStatePath)) {
1339
+ await (0, import_promises.copyFile)(localStatePath, (0, import_node_path.join)(tempUserDataDir, "Local State"));
1340
+ }
1341
+ return tempUserDataDir;
1342
+ }
1343
+ function buildRealBrowserLaunchArgs(options) {
1344
+ const args = [
1345
+ `--user-data-dir=${options.userDataDir}`,
1346
+ `--profile-directory=${options.profileDirectory}`,
1347
+ `--remote-debugging-port=${options.debugPort}`,
1348
+ "--no-first-run",
1349
+ "--no-default-browser-check",
1350
+ "--disable-background-networking",
1351
+ "--disable-sync",
1352
+ "--disable-popup-blocking"
1353
+ ];
1354
+ if (options.headless) {
1355
+ args.push("--headless=new");
1356
+ }
1357
+ return args;
1358
+ }
1359
+ async function killProcessTree(processHandle) {
1360
+ if (!processHandle || processHandle.pid == null || processHandle.exitCode !== null) {
1361
+ return;
1362
+ }
1363
+ if (process.platform === "win32") {
1364
+ await new Promise((resolve) => {
1365
+ const killer = (0, import_node_child_process.spawn)(
1366
+ "taskkill",
1367
+ ["/pid", String(processHandle.pid), "/t", "/f"],
1368
+ {
1369
+ stdio: "ignore"
1370
+ }
1371
+ );
1372
+ killer.on("error", () => resolve());
1373
+ killer.on("exit", () => resolve());
1374
+ });
1375
+ return;
1376
+ }
1377
+ try {
1378
+ process.kill(-processHandle.pid, "SIGKILL");
1379
+ } catch {
1380
+ try {
1381
+ processHandle.kill("SIGKILL");
1382
+ } catch {
1383
+ }
1384
+ }
1385
+ }
1386
+ async function sleep(ms) {
1387
+ await new Promise((resolve) => setTimeout(resolve, ms));
1388
+ }
1071
1389
 
1072
1390
  // src/config.ts
1073
- var import_fs = __toESM(require("fs"), 1);
1391
+ var import_fs2 = __toESM(require("fs"), 1);
1074
1392
  var import_path3 = __toESM(require("path"), 1);
1075
1393
  var import_url = require("url");
1076
1394
  var import_dotenv = require("dotenv");
@@ -1301,6 +1619,65 @@ function toJsonSafeValue(value, seen) {
1301
1619
  return void 0;
1302
1620
  }
1303
1621
 
1622
+ // src/cloud/credential-selection.ts
1623
+ function selectCloudCredential(options) {
1624
+ const apiKey = normalizeNonEmptyString(options.apiKey);
1625
+ const accessToken = normalizeNonEmptyString(options.accessToken);
1626
+ if (apiKey) {
1627
+ if (options.authScheme === "bearer") {
1628
+ return {
1629
+ apiKey,
1630
+ authScheme: "bearer",
1631
+ kind: "access-token",
1632
+ token: apiKey,
1633
+ compatibilityBearerApiKey: true
1634
+ };
1635
+ }
1636
+ return {
1637
+ apiKey,
1638
+ authScheme: "api-key",
1639
+ kind: "api-key",
1640
+ token: apiKey
1641
+ };
1642
+ }
1643
+ if (accessToken) {
1644
+ return {
1645
+ accessToken,
1646
+ authScheme: "bearer",
1647
+ kind: "access-token",
1648
+ token: accessToken
1649
+ };
1650
+ }
1651
+ return null;
1652
+ }
1653
+ function selectCloudCredentialByPrecedence(layers, authScheme) {
1654
+ for (const layer of layers) {
1655
+ const hasApiKey = layer.hasApiKey ?? Object.prototype.hasOwnProperty.call(layer, "apiKey");
1656
+ const hasAccessToken = layer.hasAccessToken ?? Object.prototype.hasOwnProperty.call(layer, "accessToken");
1657
+ if (!hasApiKey && !hasAccessToken) {
1658
+ continue;
1659
+ }
1660
+ return {
1661
+ source: layer.source,
1662
+ apiKey: layer.apiKey,
1663
+ accessToken: layer.accessToken,
1664
+ hasApiKey,
1665
+ hasAccessToken,
1666
+ credential: selectCloudCredential({
1667
+ apiKey: layer.apiKey,
1668
+ accessToken: layer.accessToken,
1669
+ authScheme: layer.authScheme ?? authScheme
1670
+ })
1671
+ };
1672
+ }
1673
+ return null;
1674
+ }
1675
+ function normalizeNonEmptyString(value) {
1676
+ if (typeof value !== "string") return void 0;
1677
+ const normalized = value.trim();
1678
+ return normalized.length ? normalized : void 0;
1679
+ }
1680
+
1304
1681
  // src/storage/namespace.ts
1305
1682
  var import_path2 = __toESM(require("path"), 1);
1306
1683
  var DEFAULT_NAMESPACE = "default";
@@ -1339,9 +1716,10 @@ var DEFAULT_CONFIG = {
1339
1716
  headless: false,
1340
1717
  executablePath: void 0,
1341
1718
  slowMo: 0,
1342
- connectUrl: void 0,
1343
- channel: void 0,
1344
- profileDir: void 0
1719
+ mode: void 0,
1720
+ cdpUrl: void 0,
1721
+ userDataDir: void 0,
1722
+ profileDirectory: void 0
1345
1723
  },
1346
1724
  storage: {
1347
1725
  rootDir: process.cwd()
@@ -1378,9 +1756,9 @@ function loadDotenvValues(rootDir, baseEnv, options = {}) {
1378
1756
  const nodeEnv = baseEnv.NODE_ENV?.trim() || "";
1379
1757
  for (const filename of dotenvFileOrder(nodeEnv)) {
1380
1758
  const filePath = import_path3.default.join(baseDir, filename);
1381
- if (!import_fs.default.existsSync(filePath)) continue;
1759
+ if (!import_fs2.default.existsSync(filePath)) continue;
1382
1760
  try {
1383
- const raw = import_fs.default.readFileSync(filePath, "utf8");
1761
+ const raw = import_fs2.default.readFileSync(filePath, "utf8");
1384
1762
  const parsed = (0, import_dotenv.parse)(raw);
1385
1763
  for (const [key, value] of Object.entries(parsed)) {
1386
1764
  if (values[key] === void 0) {
@@ -1450,9 +1828,9 @@ function assertNoLegacyRuntimeConfig(source, config) {
1450
1828
  }
1451
1829
  function loadConfigFile(rootDir, options = {}) {
1452
1830
  const configPath = import_path3.default.join(rootDir, ".opensteer", "config.json");
1453
- if (!import_fs.default.existsSync(configPath)) return {};
1831
+ if (!import_fs2.default.existsSync(configPath)) return {};
1454
1832
  try {
1455
- const raw = import_fs.default.readFileSync(configPath, "utf8");
1833
+ const raw = import_fs2.default.readFileSync(configPath, "utf8");
1456
1834
  return JSON.parse(raw);
1457
1835
  } catch (error) {
1458
1836
  const message = extractErrorMessage(
@@ -1625,11 +2003,6 @@ function normalizeCloudOptions(value) {
1625
2003
  }
1626
2004
  return value;
1627
2005
  }
1628
- function normalizeNonEmptyString(value) {
1629
- if (typeof value !== "string") return void 0;
1630
- const normalized = value.trim();
1631
- return normalized.length ? normalized : void 0;
1632
- }
1633
2006
  function parseCloudEnabled(value, source) {
1634
2007
  if (value == null) return void 0;
1635
2008
  if (typeof value === "boolean") return value;
@@ -1638,6 +2011,18 @@ function parseCloudEnabled(value, source) {
1638
2011
  `Invalid ${source} value "${String(value)}". Use true, false, or a cloud options object.`
1639
2012
  );
1640
2013
  }
2014
+ function resolveCloudCredentialFields(selectedLayer) {
2015
+ const credential = selectedLayer?.credential;
2016
+ if (!credential) return {};
2017
+ if (credential.kind === "api-key" || credential.compatibilityBearerApiKey === true && selectedLayer?.source !== "env") {
2018
+ return {
2019
+ apiKey: credential.token
2020
+ };
2021
+ }
2022
+ return {
2023
+ accessToken: credential.token
2024
+ };
2025
+ }
1641
2026
  function resolveCloudSelection(config, env = process.env) {
1642
2027
  const configCloud = parseCloudEnabled(config.cloud, "cloud");
1643
2028
  if (configCloud !== void 0) {
@@ -1674,7 +2059,12 @@ function resolveConfigWithEnv(input = {}, options = {}) {
1674
2059
  });
1675
2060
  assertNoLegacyAiConfig(".opensteer/config.json", fileConfig);
1676
2061
  assertNoLegacyRuntimeConfig(".opensteer/config.json", fileConfig);
2062
+ const fileCloudOptions = normalizeCloudOptions(fileConfig.cloud);
2063
+ const fileHasCloudApiKey = hasOwn(fileCloudOptions, "apiKey");
2064
+ const fileHasCloudAccessToken = hasOwn(fileCloudOptions, "accessToken");
1677
2065
  const fileRootDir = typeof fileConfig.storage?.rootDir === "string" ? fileConfig.storage.rootDir : void 0;
2066
+ assertNoRemovedBrowserConfig(input.browser, "Opensteer constructor config");
2067
+ assertNoRemovedBrowserConfig(fileConfig.browser, ".opensteer/config.json");
1678
2068
  const envRootDir = input.storage?.rootDir ?? fileRootDir ?? initialRootDir;
1679
2069
  const env = resolveEnv(envRootDir, {
1680
2070
  debug: debugHint,
@@ -1690,14 +2080,30 @@ function resolveConfigWithEnv(input = {}, options = {}) {
1690
2080
  "OPENSTEER_RUNTIME is no longer supported. Use OPENSTEER_MODE instead."
1691
2081
  );
1692
2082
  }
2083
+ if (env.OPENSTEER_CONNECT_URL != null) {
2084
+ throw new Error(
2085
+ "OPENSTEER_CONNECT_URL is no longer supported. Use OPENSTEER_CDP_URL instead."
2086
+ );
2087
+ }
2088
+ if (env.OPENSTEER_CHANNEL != null) {
2089
+ throw new Error(
2090
+ "OPENSTEER_CHANNEL is no longer supported. Use OPENSTEER_BROWSER plus OPENSTEER_BROWSER_PATH when needed."
2091
+ );
2092
+ }
2093
+ if (env.OPENSTEER_PROFILE_DIR != null) {
2094
+ throw new Error(
2095
+ "OPENSTEER_PROFILE_DIR is no longer supported. Use OPENSTEER_USER_DATA_DIR and OPENSTEER_PROFILE_DIRECTORY instead."
2096
+ );
2097
+ }
1693
2098
  const envConfig = {
1694
2099
  browser: {
1695
2100
  headless: parseBool(env.OPENSTEER_HEADLESS),
1696
2101
  executablePath: env.OPENSTEER_BROWSER_PATH || void 0,
1697
2102
  slowMo: parseNumber(env.OPENSTEER_SLOW_MO),
1698
- connectUrl: env.OPENSTEER_CONNECT_URL || void 0,
1699
- channel: env.OPENSTEER_CHANNEL || void 0,
1700
- profileDir: env.OPENSTEER_PROFILE_DIR || void 0
2103
+ mode: env.OPENSTEER_BROWSER === "real" || env.OPENSTEER_BROWSER === "chromium" ? env.OPENSTEER_BROWSER : void 0,
2104
+ cdpUrl: env.OPENSTEER_CDP_URL || void 0,
2105
+ userDataDir: env.OPENSTEER_USER_DATA_DIR || void 0,
2106
+ profileDirectory: env.OPENSTEER_PROFILE_DIRECTORY || void 0
1701
2107
  },
1702
2108
  cursor: {
1703
2109
  enabled: parseBool(env.OPENSTEER_CURSOR)
@@ -1708,17 +2114,38 @@ function resolveConfigWithEnv(input = {}, options = {}) {
1708
2114
  const mergedWithFile = mergeDeep(runtimeDefaults, fileConfig);
1709
2115
  const mergedWithEnv = mergeDeep(mergedWithFile, envConfig);
1710
2116
  const resolved = mergeDeep(mergedWithEnv, input);
2117
+ const browserHeadlessExplicit = input.browser?.headless !== void 0 || fileConfig.browser?.headless !== void 0 || envConfig.browser?.headless !== void 0;
2118
+ if (!browserHeadlessExplicit && resolved.browser?.mode === "real") {
2119
+ resolved.browser = {
2120
+ ...resolved.browser,
2121
+ headless: true
2122
+ };
2123
+ }
2124
+ function assertNoRemovedBrowserConfig(value, source) {
2125
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
2126
+ return;
2127
+ }
2128
+ const record = value;
2129
+ if (record.connectUrl !== void 0) {
2130
+ throw new Error(
2131
+ `${source}.browser.connectUrl is no longer supported. Use browser.cdpUrl instead.`
2132
+ );
2133
+ }
2134
+ if (record.channel !== void 0) {
2135
+ throw new Error(
2136
+ `${source}.browser.channel is no longer supported. Use browser.mode plus browser.executablePath instead.`
2137
+ );
2138
+ }
2139
+ if (record.profileDir !== void 0) {
2140
+ throw new Error(
2141
+ `${source}.browser.profileDir is no longer supported. Use browser.userDataDir and browser.profileDirectory instead.`
2142
+ );
2143
+ }
2144
+ }
1711
2145
  const envApiKey = resolveOpensteerApiKey(env);
1712
2146
  const envAccessTokenRaw = resolveOpensteerAccessToken(env);
1713
2147
  const envBaseUrl = resolveOpensteerBaseUrl(env);
1714
2148
  const envAuthScheme = resolveOpensteerAuthScheme(env);
1715
- if (envApiKey && envAccessTokenRaw) {
1716
- throw new Error(
1717
- "OPENSTEER_API_KEY and OPENSTEER_ACCESS_TOKEN are mutually exclusive. Set only one."
1718
- );
1719
- }
1720
- const envAccessToken = envAccessTokenRaw || (envAuthScheme === "bearer" ? envApiKey : void 0);
1721
- const envApiCredential = envAuthScheme === "bearer" && !envAccessTokenRaw ? void 0 : envApiKey;
1722
2149
  const envCloudProfileId = resolveOpensteerCloudProfileId(env);
1723
2150
  const envCloudProfileReuseIfActive = resolveOpensteerCloudProfileReuseIfActive(env);
1724
2151
  const envCloudAnnounce = parseCloudAnnounce(
@@ -1747,11 +2174,6 @@ function resolveConfigWithEnv(input = {}, options = {}) {
1747
2174
  const inputHasCloudBaseUrl = Boolean(
1748
2175
  inputCloudOptions && Object.prototype.hasOwnProperty.call(inputCloudOptions, "baseUrl")
1749
2176
  );
1750
- if (normalizeNonEmptyString(inputCloudOptions?.apiKey) && normalizeNonEmptyString(inputCloudOptions?.accessToken)) {
1751
- throw new Error(
1752
- "cloud.apiKey and cloud.accessToken are mutually exclusive. Set only one."
1753
- );
1754
- }
1755
2177
  const cloudSelection = resolveCloudSelection({
1756
2178
  cloud: resolved.cloud
1757
2179
  }, env);
@@ -1762,11 +2184,6 @@ function resolveConfigWithEnv(input = {}, options = {}) {
1762
2184
  accessToken: resolvedCloudAccessTokenRaw,
1763
2185
  ...resolvedCloudRest
1764
2186
  } = resolvedCloud;
1765
- if (normalizeNonEmptyString(resolvedCloudApiKeyRaw) && normalizeNonEmptyString(resolvedCloudAccessTokenRaw)) {
1766
- throw new Error(
1767
- "Cloud config cannot include both apiKey and accessToken at the same time."
1768
- );
1769
- }
1770
2187
  const resolvedCloudBrowserProfile = normalizeCloudBrowserProfileOptions(
1771
2188
  resolvedCloud.browserProfile,
1772
2189
  "resolved.cloud.browserProfile"
@@ -1778,25 +2195,40 @@ function resolveConfigWithEnv(input = {}, options = {}) {
1778
2195
  const browserProfile = inputCloudBrowserProfile ?? envCloudBrowserProfile ?? resolvedCloudBrowserProfile;
1779
2196
  let authScheme = inputAuthScheme ?? envAuthScheme ?? parseAuthScheme(resolvedCloud.authScheme, "cloud.authScheme") ?? "api-key";
1780
2197
  const announce = inputCloudAnnounce ?? envCloudAnnounce ?? parseCloudAnnounce(resolvedCloud.announce, "cloud.announce") ?? "always";
1781
- const credentialOverriddenByInput = inputHasCloudApiKey || inputHasCloudAccessToken;
1782
- let apiKey = normalizeNonEmptyString(resolvedCloudApiKeyRaw);
1783
- let accessToken = normalizeNonEmptyString(resolvedCloudAccessTokenRaw);
1784
- if (!credentialOverriddenByInput) {
1785
- if (envAccessToken) {
1786
- accessToken = envAccessToken;
1787
- apiKey = void 0;
1788
- } else if (envApiCredential) {
1789
- apiKey = envApiCredential;
1790
- accessToken = void 0;
1791
- }
1792
- }
2198
+ const selectedCredentialLayer = selectCloudCredentialByPrecedence(
2199
+ [
2200
+ {
2201
+ source: "input",
2202
+ apiKey: inputCloudOptions?.apiKey,
2203
+ accessToken: inputCloudOptions?.accessToken,
2204
+ hasApiKey: inputHasCloudApiKey,
2205
+ hasAccessToken: inputHasCloudAccessToken
2206
+ },
2207
+ {
2208
+ source: "env",
2209
+ apiKey: envApiKey,
2210
+ accessToken: envAccessTokenRaw,
2211
+ hasApiKey: envApiKey !== void 0,
2212
+ hasAccessToken: envAccessTokenRaw !== void 0
2213
+ },
2214
+ {
2215
+ source: "file",
2216
+ apiKey: fileCloudOptions?.apiKey,
2217
+ accessToken: fileCloudOptions?.accessToken,
2218
+ hasApiKey: fileHasCloudApiKey,
2219
+ hasAccessToken: fileHasCloudAccessToken
2220
+ }
2221
+ ],
2222
+ authScheme
2223
+ );
2224
+ const { apiKey, accessToken } = resolveCloudCredentialFields(selectedCredentialLayer);
1793
2225
  if (accessToken) {
1794
2226
  authScheme = "bearer";
1795
2227
  }
1796
2228
  resolved.cloud = {
1797
2229
  ...resolvedCloudRest,
1798
- ...inputHasCloudApiKey ? { apiKey: resolvedCloudApiKeyRaw } : apiKey ? { apiKey } : {},
1799
- ...inputHasCloudAccessToken ? { accessToken: resolvedCloudAccessTokenRaw } : accessToken ? { accessToken } : {},
2230
+ ...apiKey ? { apiKey } : selectedCredentialLayer?.hasApiKey && !accessToken ? { apiKey: selectedCredentialLayer.apiKey } : {},
2231
+ ...accessToken ? { accessToken } : selectedCredentialLayer?.hasAccessToken && !apiKey ? { accessToken: selectedCredentialLayer.accessToken } : {},
1800
2232
  authScheme,
1801
2233
  announce,
1802
2234
  ...browserProfile ? { browserProfile } : {}
@@ -2225,7 +2657,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
2225
2657
  TRANSIENT_CONTEXT_RETRY_DELAY_MS,
2226
2658
  Math.max(0, deadline - Date.now())
2227
2659
  );
2228
- await sleep(retryDelay);
2660
+ await sleep2(retryDelay);
2229
2661
  }
2230
2662
  }
2231
2663
  }
@@ -2258,7 +2690,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
2258
2690
  () => ({ kind: "resolved" }),
2259
2691
  (error) => ({ kind: "rejected", error })
2260
2692
  );
2261
- const timeoutPromise = sleep(
2693
+ const timeoutPromise = sleep2(
2262
2694
  timeout + FRAME_EVALUATE_GRACE_MS
2263
2695
  ).then(() => ({ kind: "timeout" }));
2264
2696
  const result = await Promise.race([
@@ -2400,14 +2832,14 @@ function isIgnorableFrameError(error) {
2400
2832
  const message = error.message;
2401
2833
  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");
2402
2834
  }
2403
- function sleep(ms) {
2835
+ function sleep2(ms) {
2404
2836
  return new Promise((resolve) => {
2405
2837
  setTimeout(resolve, ms);
2406
2838
  });
2407
2839
  }
2408
2840
 
2409
2841
  // src/storage/local.ts
2410
- var import_fs2 = __toESM(require("fs"), 1);
2842
+ var import_fs3 = __toESM(require("fs"), 1);
2411
2843
  var import_path4 = __toESM(require("path"), 1);
2412
2844
 
2413
2845
  // src/storage/registry.ts
@@ -2451,14 +2883,14 @@ var LocalSelectorStorage = class {
2451
2883
  return import_path4.default.join(this.getNamespaceDir(), this.getSelectorFileName(id));
2452
2884
  }
2453
2885
  ensureDirs() {
2454
- import_fs2.default.mkdirSync(this.getNamespaceDir(), { recursive: true });
2886
+ import_fs3.default.mkdirSync(this.getNamespaceDir(), { recursive: true });
2455
2887
  }
2456
2888
  loadRegistry() {
2457
2889
  this.ensureDirs();
2458
2890
  const file = this.getRegistryPath();
2459
- if (!import_fs2.default.existsSync(file)) return createEmptyRegistry(this.namespace);
2891
+ if (!import_fs3.default.existsSync(file)) return createEmptyRegistry(this.namespace);
2460
2892
  try {
2461
- const raw = import_fs2.default.readFileSync(file, "utf8");
2893
+ const raw = import_fs3.default.readFileSync(file, "utf8");
2462
2894
  return JSON.parse(raw);
2463
2895
  } catch (error) {
2464
2896
  const message = extractErrorMessage(
@@ -2475,16 +2907,16 @@ var LocalSelectorStorage = class {
2475
2907
  }
2476
2908
  saveRegistry(registry) {
2477
2909
  this.ensureDirs();
2478
- import_fs2.default.writeFileSync(
2910
+ import_fs3.default.writeFileSync(
2479
2911
  this.getRegistryPath(),
2480
2912
  JSON.stringify(registry, null, 2)
2481
2913
  );
2482
2914
  }
2483
2915
  readSelector(id) {
2484
2916
  const file = this.getSelectorPath(id);
2485
- if (!import_fs2.default.existsSync(file)) return null;
2917
+ if (!import_fs3.default.existsSync(file)) return null;
2486
2918
  try {
2487
- const raw = import_fs2.default.readFileSync(file, "utf8");
2919
+ const raw = import_fs3.default.readFileSync(file, "utf8");
2488
2920
  return JSON.parse(raw);
2489
2921
  } catch (error) {
2490
2922
  const message = extractErrorMessage(
@@ -2501,15 +2933,15 @@ var LocalSelectorStorage = class {
2501
2933
  }
2502
2934
  writeSelector(payload) {
2503
2935
  this.ensureDirs();
2504
- import_fs2.default.writeFileSync(
2936
+ import_fs3.default.writeFileSync(
2505
2937
  this.getSelectorPath(payload.id),
2506
2938
  JSON.stringify(payload, null, 2)
2507
2939
  );
2508
2940
  }
2509
2941
  clearNamespace() {
2510
2942
  const dir = this.getNamespaceDir();
2511
- if (!import_fs2.default.existsSync(dir)) return;
2512
- import_fs2.default.rmSync(dir, { recursive: true, force: true });
2943
+ if (!import_fs3.default.existsSync(dir)) return;
2944
+ import_fs3.default.rmSync(dir, { recursive: true, force: true });
2513
2945
  }
2514
2946
  };
2515
2947
 
@@ -6963,7 +7395,7 @@ var AdaptiveNetworkTracker = class {
6963
7395
  this.idleSince = 0;
6964
7396
  }
6965
7397
  const remaining = Math.max(1, options.deadline - now);
6966
- await sleep2(Math.min(NETWORK_POLL_MS, remaining));
7398
+ await sleep3(Math.min(NETWORK_POLL_MS, remaining));
6967
7399
  }
6968
7400
  }
6969
7401
  handleRequestStarted = (request) => {
@@ -7008,7 +7440,7 @@ var AdaptiveNetworkTracker = class {
7008
7440
  return false;
7009
7441
  }
7010
7442
  };
7011
- async function sleep2(ms) {
7443
+ async function sleep3(ms) {
7012
7444
  await new Promise((resolve) => {
7013
7445
  setTimeout(resolve, ms);
7014
7446
  });
@@ -8485,15 +8917,15 @@ function withTokenQuery(wsUrl, token) {
8485
8917
  }
8486
8918
 
8487
8919
  // src/cloud/local-cache-sync.ts
8488
- var import_fs3 = __toESM(require("fs"), 1);
8920
+ var import_fs4 = __toESM(require("fs"), 1);
8489
8921
  var import_path5 = __toESM(require("path"), 1);
8490
8922
  function collectLocalSelectorCacheEntries(storage, options = {}) {
8491
8923
  const debug = options.debug === true;
8492
8924
  const namespace = storage.getNamespace();
8493
8925
  const namespaceDir = storage.getNamespaceDir();
8494
- if (!import_fs3.default.existsSync(namespaceDir)) return [];
8926
+ if (!import_fs4.default.existsSync(namespaceDir)) return [];
8495
8927
  const entries = [];
8496
- const fileNames = import_fs3.default.readdirSync(namespaceDir);
8928
+ const fileNames = import_fs4.default.readdirSync(namespaceDir);
8497
8929
  for (const fileName of fileNames) {
8498
8930
  if (fileName === "index.json" || !fileName.endsWith(".json")) continue;
8499
8931
  const filePath = import_path5.default.join(namespaceDir, fileName);
@@ -8524,7 +8956,7 @@ function collectLocalSelectorCacheEntries(storage, options = {}) {
8524
8956
  }
8525
8957
  function readSelectorFile(filePath, debug) {
8526
8958
  try {
8527
- const raw = import_fs3.default.readFileSync(filePath, "utf8");
8959
+ const raw = import_fs4.default.readFileSync(filePath, "utf8");
8528
8960
  return JSON.parse(raw);
8529
8961
  } catch (error) {
8530
8962
  const message = extractErrorMessage(
@@ -8619,13 +9051,13 @@ function dedupeNewest(entries) {
8619
9051
  }
8620
9052
 
8621
9053
  // src/cloud/cdp-client.ts
8622
- var import_playwright3 = require("playwright");
9054
+ var import_playwright2 = require("playwright");
8623
9055
  var CloudCdpClient = class {
8624
9056
  async connect(args) {
8625
9057
  const endpoint = withTokenQuery2(args.wsUrl, args.token);
8626
9058
  let browser;
8627
9059
  try {
8628
- browser = await import_playwright3.chromium.connectOverCDP(endpoint);
9060
+ browser = await import_playwright2.chromium.connectOverCDP(endpoint);
8629
9061
  } catch (error) {
8630
9062
  const message = error instanceof Error ? error.message : "Failed to connect to cloud CDP endpoint.";
8631
9063
  throw new OpensteerCloudError("CLOUD_TRANSPORT_ERROR", message);
@@ -10418,7 +10850,7 @@ async function executeAgentAction(page, action) {
10418
10850
  }
10419
10851
  case "wait": {
10420
10852
  const ms = numberOr(action.timeMs, action.time_ms, 1e3);
10421
- await sleep3(ms);
10853
+ await sleep4(ms);
10422
10854
  return;
10423
10855
  }
10424
10856
  case "goto": {
@@ -10583,7 +11015,7 @@ async function pressKeyCombo(page, combo) {
10583
11015
  }
10584
11016
  }
10585
11017
  }
10586
- function sleep3(ms) {
11018
+ function sleep4(ms) {
10587
11019
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
10588
11020
  }
10589
11021
 
@@ -10614,7 +11046,7 @@ var OpensteerCuaAgentHandler = class {
10614
11046
  if (isMutatingAgentAction(action)) {
10615
11047
  this.onMutatingAction?.(action);
10616
11048
  }
10617
- await sleep4(this.config.waitBetweenActionsMs);
11049
+ await sleep5(this.config.waitBetweenActionsMs);
10618
11050
  });
10619
11051
  try {
10620
11052
  const result = await this.client.execute({
@@ -10676,7 +11108,7 @@ var OpensteerCuaAgentHandler = class {
10676
11108
  await this.cursorController.preview({ x, y }, "agent");
10677
11109
  }
10678
11110
  };
10679
- function sleep4(ms) {
11111
+ function sleep5(ms) {
10680
11112
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
10681
11113
  }
10682
11114
 
@@ -11112,7 +11544,7 @@ var CursorController = class {
11112
11544
  for (const step of motion.points) {
11113
11545
  await this.renderer.move(step, this.style);
11114
11546
  if (motion.stepDelayMs > 0) {
11115
- await sleep5(motion.stepDelayMs);
11547
+ await sleep6(motion.stepDelayMs);
11116
11548
  }
11117
11549
  }
11118
11550
  if (shouldPulse(intent)) {
@@ -11270,7 +11702,7 @@ function clamp2(value, min, max) {
11270
11702
  function shouldPulse(intent) {
11271
11703
  return intent === "click" || intent === "dblclick" || intent === "rightclick" || intent === "agent";
11272
11704
  }
11273
- function sleep5(ms) {
11705
+ function sleep6(ms) {
11274
11706
  return new Promise((resolve) => setTimeout(resolve, ms));
11275
11707
  }
11276
11708
 
@@ -11321,30 +11753,20 @@ var Opensteer = class _Opensteer {
11321
11753
  this.pool = new BrowserPool(resolved.browser || {});
11322
11754
  if (cloudSelection.cloud) {
11323
11755
  const cloudConfig = resolved.cloud && typeof resolved.cloud === "object" ? resolved.cloud : void 0;
11324
- const apiKey = cloudConfig?.apiKey?.trim();
11325
- const accessToken = cloudConfig?.accessToken?.trim();
11326
- if (apiKey && accessToken) {
11327
- throw new Error(
11328
- "Cloud mode cannot use both cloud.apiKey and cloud.accessToken. Set only one credential."
11329
- );
11330
- }
11331
- let credential = "";
11332
- let authScheme = cloudConfig?.authScheme ?? "api-key";
11333
- if (accessToken) {
11334
- credential = accessToken;
11335
- authScheme = "bearer";
11336
- } else if (apiKey) {
11337
- credential = apiKey;
11338
- }
11756
+ const credential = selectCloudCredential({
11757
+ apiKey: cloudConfig?.apiKey,
11758
+ accessToken: cloudConfig?.accessToken,
11759
+ authScheme: cloudConfig?.authScheme
11760
+ });
11339
11761
  if (!credential) {
11340
11762
  throw new Error(
11341
11763
  "Cloud mode requires credentials via cloud.apiKey/cloud.accessToken or OPENSTEER_API_KEY/OPENSTEER_ACCESS_TOKEN."
11342
11764
  );
11343
11765
  }
11344
11766
  this.cloud = createCloudRuntimeState(
11345
- credential,
11767
+ credential.token,
11346
11768
  cloudConfig?.baseUrl,
11347
- authScheme
11769
+ credential.authScheme
11348
11770
  );
11349
11771
  } else {
11350
11772
  this.cloud = null;
@@ -11665,9 +12087,11 @@ var Opensteer = class _Opensteer {
11665
12087
  }
11666
12088
  const session = await this.pool.launch({
11667
12089
  ...options,
11668
- connectUrl: options.connectUrl ?? this.config.browser?.connectUrl,
11669
- channel: options.channel ?? this.config.browser?.channel,
11670
- profileDir: options.profileDir ?? this.config.browser?.profileDir
12090
+ mode: options.mode ?? this.config.browser?.mode,
12091
+ cdpUrl: options.cdpUrl ?? this.config.browser?.cdpUrl,
12092
+ userDataDir: options.userDataDir ?? this.config.browser?.userDataDir,
12093
+ profileDirectory: options.profileDirectory ?? this.config.browser?.profileDirectory,
12094
+ executablePath: options.executablePath ?? this.config.browser?.executablePath
11671
12095
  });
11672
12096
  this.browser = session.browser;
11673
12097
  this.contextRef = session.context;
@@ -11698,6 +12122,32 @@ var Opensteer = class _Opensteer {
11698
12122
  instance.snapshotCache = null;
11699
12123
  return instance;
11700
12124
  }
12125
+ static listLocalProfiles(userDataDir) {
12126
+ return listLocalChromeProfiles(userDataDir);
12127
+ }
12128
+ static fromSystemChrome(browser = {}, config = {}) {
12129
+ const chromePaths = detectChromePaths();
12130
+ const executablePath = browser.executablePath ?? config.browser?.executablePath ?? chromePaths.executable ?? void 0;
12131
+ if (!executablePath) {
12132
+ throw new Error(
12133
+ "Chrome was not found. Pass executablePath explicitly or install Chrome in a supported location."
12134
+ );
12135
+ }
12136
+ const userDataDir = browser.userDataDir ?? config.browser?.userDataDir ?? chromePaths.defaultUserDataDir;
12137
+ const autoDetectedProfiles = listLocalChromeProfiles(userDataDir);
12138
+ const profileDirectory = browser.profileDirectory ?? config.browser?.profileDirectory ?? autoDetectedProfiles[0]?.directory ?? "Default";
12139
+ return new _Opensteer({
12140
+ ...config,
12141
+ browser: {
12142
+ ...config.browser || {},
12143
+ mode: "real",
12144
+ headless: browser.headless ?? config.browser?.headless ?? true,
12145
+ executablePath,
12146
+ userDataDir,
12147
+ profileDirectory
12148
+ }
12149
+ });
12150
+ }
11701
12151
  async close() {
11702
12152
  this.snapshotCache = null;
11703
12153
  if (this.cloud) {
@@ -14188,7 +14638,7 @@ var CdpOverlayCursorRenderer = class {
14188
14638
  outlineColor: toProtocolRgba(pulseOutline)
14189
14639
  });
14190
14640
  });
14191
- await sleep6(PULSE_DELAY_MS);
14641
+ await sleep7(PULSE_DELAY_MS);
14192
14642
  await this.move(point, style);
14193
14643
  }
14194
14644
  async clear() {
@@ -14312,7 +14762,7 @@ function clampAlpha(value) {
14312
14762
  function roundPointValue(value) {
14313
14763
  return Math.round(value * 100) / 100;
14314
14764
  }
14315
- function sleep6(ms) {
14765
+ function sleep7(ms) {
14316
14766
  return new Promise((resolve) => setTimeout(resolve, ms));
14317
14767
  }
14318
14768
  // Annotate the CommonJS export names for ESM import in node: