opensteer 0.4.7 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -21,6 +21,8 @@
21
21
  - Breaking: `OPENSTEER_MODE` now uses `local` or `cloud`; `remote` is no longer a supported value.
22
22
  - Opensteer now enables built-in LLM resolve/extract by default with model `gpt-5.1`.
23
23
  - Cloud mode now falls back to `OPENSTEER_API_KEY` when `cloud.apiKey` is omitted.
24
+ - Added automatic `.env` loading from `storage.rootDir` (default `process.cwd()`) so constructor config can consume env vars without requiring `import 'dotenv/config'`.
25
+ - `.env` autoload follows common precedence (`.env.<NODE_ENV>.local`, `.env.local`, `.env.<NODE_ENV>`, `.env`) with `.env.local` skipped in `test`, does not overwrite existing env values, and can be disabled via `OPENSTEER_DISABLE_DOTENV_AUTOLOAD`.
24
26
  - Mutating actions now include smart best-effort post-action wait with per-action
25
27
  profiles and optional per-call overrides via `wait`.
26
28
  - Added structured interaction diagnostics via `OpensteerActionError` for
package/README.md CHANGED
@@ -164,6 +164,11 @@ Opensteer defaults to local mode.
164
164
  - `OPENSTEER_MODE=local` runs local Playwright.
165
165
  - `OPENSTEER_MODE=cloud` enables cloud mode (requires `OPENSTEER_API_KEY`).
166
166
  - `cloud: true` in constructor config always enables cloud mode.
167
+ - Opensteer auto-loads `.env` files from your `storage.rootDir` (default:
168
+ `process.cwd()`) using this order: `.env.<NODE_ENV>.local`, `.env.local`
169
+ (skipped when `NODE_ENV=test`), `.env.<NODE_ENV>`, `.env`.
170
+ - Existing `process.env` values are never overwritten by `.env` values.
171
+ - Set `OPENSTEER_DISABLE_DOTENV_AUTOLOAD=true` to disable auto-loading.
167
172
 
168
173
  Cloud mode is fail-fast: it does not automatically fall back to local mode.
169
174
 
@@ -341,7 +341,9 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
341
341
  const frameRecords = await this.getFrameRecords();
342
342
  const mainFrame = frameRecords[0];
343
343
  if (!mainFrame) return;
344
- await this.waitForFrameVisualStability(mainFrame.frameId, timeout, settleMs);
344
+ await this.waitForFrameVisualStability(mainFrame.frameId, timeout, settleMs, {
345
+ retryTransientContextErrors: true
346
+ });
345
347
  }
346
348
  async collectVisibleFrameIds() {
347
349
  const frameRecords = await this.getFrameRecords();
@@ -370,19 +372,40 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
370
372
  }
371
373
  return visibleFrameIds;
372
374
  }
373
- async waitForFrameVisualStability(frameId, timeout, settleMs) {
375
+ async waitForFrameVisualStability(frameId, timeout, settleMs, options = {}) {
374
376
  if (timeout <= 0) return;
375
377
  const script = buildStabilityScript(timeout, settleMs);
376
- let contextId = await this.ensureFrameContextId(frameId);
377
- try {
378
- await this.evaluateWithGuard(contextId, script, timeout);
379
- } catch (error) {
380
- if (!isMissingExecutionContextError(error)) {
381
- throw error;
378
+ const retryTransientContextErrors = options.retryTransientContextErrors ?? true;
379
+ if (!retryTransientContextErrors) {
380
+ let contextId = await this.ensureFrameContextId(frameId);
381
+ try {
382
+ await this.evaluateWithGuard(contextId, script, timeout);
383
+ } catch (error) {
384
+ if (!isMissingExecutionContextReferenceError(error)) {
385
+ throw error;
386
+ }
387
+ this.contextsByFrame.delete(frameId);
388
+ contextId = await this.ensureFrameContextId(frameId);
389
+ await this.evaluateWithGuard(contextId, script, timeout);
390
+ }
391
+ return;
392
+ }
393
+ const deadline = Date.now() + timeout;
394
+ while (true) {
395
+ const remaining = Math.max(0, deadline - Date.now());
396
+ if (remaining === 0) {
397
+ return;
398
+ }
399
+ const contextId = await this.ensureFrameContextId(frameId);
400
+ try {
401
+ await this.evaluateWithGuard(contextId, script, remaining);
402
+ return;
403
+ } catch (error) {
404
+ if (!isTransientExecutionContextError(error)) {
405
+ throw error;
406
+ }
407
+ this.contextsByFrame.delete(frameId);
382
408
  }
383
- this.contextsByFrame.delete(frameId);
384
- contextId = await this.ensureFrameContextId(frameId);
385
- await this.evaluateWithGuard(contextId, script, timeout);
386
409
  }
387
410
  }
388
411
  async initialize() {
@@ -502,7 +525,10 @@ async function waitForVisualStabilityAcrossFrames(page, options = {}) {
502
525
  await runtime.waitForFrameVisualStability(
503
526
  frameId,
504
527
  remaining,
505
- settleMs
528
+ settleMs,
529
+ {
530
+ retryTransientContextErrors: false
531
+ }
506
532
  );
507
533
  } catch (error) {
508
534
  if (isIgnorableFrameError(error)) return;
@@ -540,7 +566,12 @@ function sameFrameIds(before, after) {
540
566
  function formatCdpException(details) {
541
567
  return details.exception?.description || details.text || "CDP runtime evaluation failed.";
542
568
  }
543
- function isMissingExecutionContextError(error) {
569
+ function isTransientExecutionContextError(error) {
570
+ if (!(error instanceof Error)) return false;
571
+ const message = error.message;
572
+ return message.includes("Execution context was destroyed") || message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context");
573
+ }
574
+ function isMissingExecutionContextReferenceError(error) {
544
575
  if (!(error instanceof Error)) return false;
545
576
  const message = error.message;
546
577
  return message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context");
@@ -548,7 +579,7 @@ function isMissingExecutionContextError(error) {
548
579
  function isIgnorableFrameError(error) {
549
580
  if (!(error instanceof Error)) return false;
550
581
  const message = error.message;
551
- return message.includes("Frame was detached") || message.includes("Execution context was destroyed") || message.includes("Target page, context or browser has been closed") || message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context") || message.includes("No frame for given id found");
582
+ 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");
552
583
  }
553
584
  function sleep(ms) {
554
585
  return new Promise((resolve) => {
@@ -5981,6 +6012,7 @@ var BrowserPool = class {
5981
6012
  import fs3 from "fs";
5982
6013
  import path4 from "path";
5983
6014
  import { fileURLToPath } from "url";
6015
+ import { parse as parseDotenv } from "dotenv";
5984
6016
  var DEFAULT_CONFIG = {
5985
6017
  browser: {
5986
6018
  headless: false,
@@ -5996,6 +6028,53 @@ var DEFAULT_CONFIG = {
5996
6028
  model: "gpt-5.1",
5997
6029
  debug: false
5998
6030
  };
6031
+ function dotenvFileOrder(nodeEnv) {
6032
+ const normalized = nodeEnv?.trim() || "";
6033
+ const files = [];
6034
+ if (normalized) {
6035
+ files.push(`.env.${normalized}.local`);
6036
+ }
6037
+ if (normalized !== "test") {
6038
+ files.push(".env.local");
6039
+ }
6040
+ if (normalized) {
6041
+ files.push(`.env.${normalized}`);
6042
+ }
6043
+ files.push(".env");
6044
+ return files;
6045
+ }
6046
+ function loadDotenvValues(rootDir, baseEnv) {
6047
+ const values = {};
6048
+ if (parseBool(baseEnv.OPENSTEER_DISABLE_DOTENV_AUTOLOAD) === true) {
6049
+ return values;
6050
+ }
6051
+ const baseDir = path4.resolve(rootDir);
6052
+ const nodeEnv = baseEnv.NODE_ENV?.trim() || "";
6053
+ for (const filename of dotenvFileOrder(nodeEnv)) {
6054
+ const filePath = path4.join(baseDir, filename);
6055
+ if (!fs3.existsSync(filePath)) continue;
6056
+ try {
6057
+ const raw = fs3.readFileSync(filePath, "utf8");
6058
+ const parsed = parseDotenv(raw);
6059
+ for (const [key, value] of Object.entries(parsed)) {
6060
+ if (values[key] === void 0) {
6061
+ values[key] = value;
6062
+ }
6063
+ }
6064
+ } catch {
6065
+ continue;
6066
+ }
6067
+ }
6068
+ return values;
6069
+ }
6070
+ function resolveEnv(rootDir) {
6071
+ const baseEnv = process.env;
6072
+ const dotenvValues = loadDotenvValues(rootDir, baseEnv);
6073
+ return {
6074
+ ...dotenvValues,
6075
+ ...baseEnv
6076
+ };
6077
+ }
5999
6078
  function hasOwn(config, key) {
6000
6079
  if (!config || typeof config !== "object") return false;
6001
6080
  return Object.prototype.hasOwnProperty.call(config, key);
@@ -6124,16 +6203,13 @@ function parseCloudAnnounce(value, source) {
6124
6203
  `Invalid ${source} value "${value}". Use "always", "off", or "tty".`
6125
6204
  );
6126
6205
  }
6127
- function resolveOpensteerApiKey() {
6128
- const value = process.env.OPENSTEER_API_KEY?.trim();
6206
+ function resolveOpensteerApiKey(env) {
6207
+ const value = env.OPENSTEER_API_KEY?.trim();
6129
6208
  if (!value) return void 0;
6130
6209
  return value;
6131
6210
  }
6132
- function resolveOpensteerAuthScheme() {
6133
- return parseAuthScheme(
6134
- process.env.OPENSTEER_AUTH_SCHEME,
6135
- "OPENSTEER_AUTH_SCHEME"
6136
- );
6211
+ function resolveOpensteerAuthScheme(env) {
6212
+ return parseAuthScheme(env.OPENSTEER_AUTH_SCHEME, "OPENSTEER_AUTH_SCHEME");
6137
6213
  }
6138
6214
  function normalizeCloudOptions(value) {
6139
6215
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -6149,7 +6225,7 @@ function parseCloudEnabled(value, source) {
6149
6225
  `Invalid ${source} value "${String(value)}". Use true, false, or a cloud options object.`
6150
6226
  );
6151
6227
  }
6152
- function resolveCloudSelection(config) {
6228
+ function resolveCloudSelection(config, env = process.env) {
6153
6229
  const configCloud = parseCloudEnabled(config.cloud, "cloud");
6154
6230
  if (configCloud !== void 0) {
6155
6231
  return {
@@ -6157,10 +6233,7 @@ function resolveCloudSelection(config) {
6157
6233
  source: "config.cloud"
6158
6234
  };
6159
6235
  }
6160
- const envMode = parseRuntimeMode(
6161
- process.env.OPENSTEER_MODE,
6162
- "OPENSTEER_MODE"
6163
- );
6236
+ const envMode = parseRuntimeMode(env.OPENSTEER_MODE, "OPENSTEER_MODE");
6164
6237
  if (envMode) {
6165
6238
  return {
6166
6239
  cloud: envMode === "cloud",
@@ -6173,41 +6246,49 @@ function resolveCloudSelection(config) {
6173
6246
  };
6174
6247
  }
6175
6248
  function resolveConfig(input = {}) {
6176
- if (process.env.OPENSTEER_AI_MODEL) {
6249
+ const initialRootDir = input.storage?.rootDir ?? process.cwd();
6250
+ const runtimeDefaults = mergeDeep(DEFAULT_CONFIG, {
6251
+ storage: {
6252
+ rootDir: initialRootDir
6253
+ }
6254
+ });
6255
+ assertNoLegacyAiConfig("Opensteer constructor config", input);
6256
+ assertNoLegacyRuntimeConfig("Opensteer constructor config", input);
6257
+ const fileConfig = loadConfigFile(initialRootDir);
6258
+ assertNoLegacyAiConfig(".opensteer/config.json", fileConfig);
6259
+ assertNoLegacyRuntimeConfig(".opensteer/config.json", fileConfig);
6260
+ const fileRootDir = typeof fileConfig.storage?.rootDir === "string" ? fileConfig.storage.rootDir : void 0;
6261
+ const envRootDir = input.storage?.rootDir ?? fileRootDir ?? initialRootDir;
6262
+ const env = resolveEnv(envRootDir);
6263
+ if (env.OPENSTEER_AI_MODEL) {
6177
6264
  throw new Error(
6178
6265
  "OPENSTEER_AI_MODEL is no longer supported. Use OPENSTEER_MODEL instead."
6179
6266
  );
6180
6267
  }
6181
- if (process.env.OPENSTEER_RUNTIME != null) {
6268
+ if (env.OPENSTEER_RUNTIME != null) {
6182
6269
  throw new Error(
6183
6270
  "OPENSTEER_RUNTIME is no longer supported. Use OPENSTEER_MODE instead."
6184
6271
  );
6185
6272
  }
6186
- assertNoLegacyAiConfig("Opensteer constructor config", input);
6187
- assertNoLegacyRuntimeConfig("Opensteer constructor config", input);
6188
- const rootDir = input.storage?.rootDir ?? DEFAULT_CONFIG.storage.rootDir ?? process.cwd();
6189
- const fileConfig = loadConfigFile(rootDir);
6190
- assertNoLegacyAiConfig(".opensteer/config.json", fileConfig);
6191
- assertNoLegacyRuntimeConfig(".opensteer/config.json", fileConfig);
6192
6273
  const envConfig = {
6193
6274
  browser: {
6194
- headless: parseBool(process.env.OPENSTEER_HEADLESS),
6195
- executablePath: process.env.OPENSTEER_BROWSER_PATH || void 0,
6196
- slowMo: parseNumber(process.env.OPENSTEER_SLOW_MO),
6197
- connectUrl: process.env.OPENSTEER_CONNECT_URL || void 0,
6198
- channel: process.env.OPENSTEER_CHANNEL || void 0,
6199
- profileDir: process.env.OPENSTEER_PROFILE_DIR || void 0
6275
+ headless: parseBool(env.OPENSTEER_HEADLESS),
6276
+ executablePath: env.OPENSTEER_BROWSER_PATH || void 0,
6277
+ slowMo: parseNumber(env.OPENSTEER_SLOW_MO),
6278
+ connectUrl: env.OPENSTEER_CONNECT_URL || void 0,
6279
+ channel: env.OPENSTEER_CHANNEL || void 0,
6280
+ profileDir: env.OPENSTEER_PROFILE_DIR || void 0
6200
6281
  },
6201
- model: process.env.OPENSTEER_MODEL || void 0,
6202
- debug: parseBool(process.env.OPENSTEER_DEBUG)
6282
+ model: env.OPENSTEER_MODEL || void 0,
6283
+ debug: parseBool(env.OPENSTEER_DEBUG)
6203
6284
  };
6204
- const mergedWithFile = mergeDeep(DEFAULT_CONFIG, fileConfig);
6285
+ const mergedWithFile = mergeDeep(runtimeDefaults, fileConfig);
6205
6286
  const mergedWithEnv = mergeDeep(mergedWithFile, envConfig);
6206
6287
  const resolved = mergeDeep(mergedWithEnv, input);
6207
- const envApiKey = resolveOpensteerApiKey();
6208
- const envAuthScheme = resolveOpensteerAuthScheme();
6288
+ const envApiKey = resolveOpensteerApiKey(env);
6289
+ const envAuthScheme = resolveOpensteerAuthScheme(env);
6209
6290
  const envCloudAnnounce = parseCloudAnnounce(
6210
- process.env.OPENSTEER_REMOTE_ANNOUNCE,
6291
+ env.OPENSTEER_REMOTE_ANNOUNCE,
6211
6292
  "OPENSTEER_REMOTE_ANNOUNCE"
6212
6293
  );
6213
6294
  const inputCloudOptions = normalizeCloudOptions(input.cloud);
@@ -6224,7 +6305,7 @@ function resolveConfig(input = {}) {
6224
6305
  );
6225
6306
  const cloudSelection = resolveCloudSelection({
6226
6307
  cloud: resolved.cloud
6227
- });
6308
+ }, env);
6228
6309
  if (cloudSelection.cloud) {
6229
6310
  const resolvedCloud = normalizeCloudOptions(resolved.cloud) ?? {};
6230
6311
  const authScheme = inputAuthScheme ?? envAuthScheme ?? parseAuthScheme(resolvedCloud.authScheme, "cloud.authScheme") ?? "api-key";
@@ -806,6 +806,7 @@ var BrowserPool = class {
806
806
  var import_fs = __toESM(require("fs"), 1);
807
807
  var import_path3 = __toESM(require("path"), 1);
808
808
  var import_url = require("url");
809
+ var import_dotenv = require("dotenv");
809
810
 
810
811
  // src/storage/namespace.ts
811
812
  var import_path2 = __toESM(require("path"), 1);
@@ -855,6 +856,53 @@ var DEFAULT_CONFIG = {
855
856
  model: "gpt-5.1",
856
857
  debug: false
857
858
  };
859
+ function dotenvFileOrder(nodeEnv) {
860
+ const normalized = nodeEnv?.trim() || "";
861
+ const files = [];
862
+ if (normalized) {
863
+ files.push(`.env.${normalized}.local`);
864
+ }
865
+ if (normalized !== "test") {
866
+ files.push(".env.local");
867
+ }
868
+ if (normalized) {
869
+ files.push(`.env.${normalized}`);
870
+ }
871
+ files.push(".env");
872
+ return files;
873
+ }
874
+ function loadDotenvValues(rootDir, baseEnv) {
875
+ const values = {};
876
+ if (parseBool(baseEnv.OPENSTEER_DISABLE_DOTENV_AUTOLOAD) === true) {
877
+ return values;
878
+ }
879
+ const baseDir = import_path3.default.resolve(rootDir);
880
+ const nodeEnv = baseEnv.NODE_ENV?.trim() || "";
881
+ for (const filename of dotenvFileOrder(nodeEnv)) {
882
+ const filePath = import_path3.default.join(baseDir, filename);
883
+ if (!import_fs.default.existsSync(filePath)) continue;
884
+ try {
885
+ const raw = import_fs.default.readFileSync(filePath, "utf8");
886
+ const parsed = (0, import_dotenv.parse)(raw);
887
+ for (const [key, value] of Object.entries(parsed)) {
888
+ if (values[key] === void 0) {
889
+ values[key] = value;
890
+ }
891
+ }
892
+ } catch {
893
+ continue;
894
+ }
895
+ }
896
+ return values;
897
+ }
898
+ function resolveEnv(rootDir) {
899
+ const baseEnv = process.env;
900
+ const dotenvValues = loadDotenvValues(rootDir, baseEnv);
901
+ return {
902
+ ...dotenvValues,
903
+ ...baseEnv
904
+ };
905
+ }
858
906
  function hasOwn(config, key) {
859
907
  if (!config || typeof config !== "object") return false;
860
908
  return Object.prototype.hasOwnProperty.call(config, key);
@@ -983,16 +1031,13 @@ function parseCloudAnnounce(value, source) {
983
1031
  `Invalid ${source} value "${value}". Use "always", "off", or "tty".`
984
1032
  );
985
1033
  }
986
- function resolveOpensteerApiKey() {
987
- const value = process.env.OPENSTEER_API_KEY?.trim();
1034
+ function resolveOpensteerApiKey(env) {
1035
+ const value = env.OPENSTEER_API_KEY?.trim();
988
1036
  if (!value) return void 0;
989
1037
  return value;
990
1038
  }
991
- function resolveOpensteerAuthScheme() {
992
- return parseAuthScheme(
993
- process.env.OPENSTEER_AUTH_SCHEME,
994
- "OPENSTEER_AUTH_SCHEME"
995
- );
1039
+ function resolveOpensteerAuthScheme(env) {
1040
+ return parseAuthScheme(env.OPENSTEER_AUTH_SCHEME, "OPENSTEER_AUTH_SCHEME");
996
1041
  }
997
1042
  function normalizeCloudOptions(value) {
998
1043
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -1008,7 +1053,7 @@ function parseCloudEnabled(value, source) {
1008
1053
  `Invalid ${source} value "${String(value)}". Use true, false, or a cloud options object.`
1009
1054
  );
1010
1055
  }
1011
- function resolveCloudSelection(config) {
1056
+ function resolveCloudSelection(config, env = process.env) {
1012
1057
  const configCloud = parseCloudEnabled(config.cloud, "cloud");
1013
1058
  if (configCloud !== void 0) {
1014
1059
  return {
@@ -1016,10 +1061,7 @@ function resolveCloudSelection(config) {
1016
1061
  source: "config.cloud"
1017
1062
  };
1018
1063
  }
1019
- const envMode = parseRuntimeMode(
1020
- process.env.OPENSTEER_MODE,
1021
- "OPENSTEER_MODE"
1022
- );
1064
+ const envMode = parseRuntimeMode(env.OPENSTEER_MODE, "OPENSTEER_MODE");
1023
1065
  if (envMode) {
1024
1066
  return {
1025
1067
  cloud: envMode === "cloud",
@@ -1032,41 +1074,49 @@ function resolveCloudSelection(config) {
1032
1074
  };
1033
1075
  }
1034
1076
  function resolveConfig(input = {}) {
1035
- if (process.env.OPENSTEER_AI_MODEL) {
1077
+ const initialRootDir = input.storage?.rootDir ?? process.cwd();
1078
+ const runtimeDefaults = mergeDeep(DEFAULT_CONFIG, {
1079
+ storage: {
1080
+ rootDir: initialRootDir
1081
+ }
1082
+ });
1083
+ assertNoLegacyAiConfig("Opensteer constructor config", input);
1084
+ assertNoLegacyRuntimeConfig("Opensteer constructor config", input);
1085
+ const fileConfig = loadConfigFile(initialRootDir);
1086
+ assertNoLegacyAiConfig(".opensteer/config.json", fileConfig);
1087
+ assertNoLegacyRuntimeConfig(".opensteer/config.json", fileConfig);
1088
+ const fileRootDir = typeof fileConfig.storage?.rootDir === "string" ? fileConfig.storage.rootDir : void 0;
1089
+ const envRootDir = input.storage?.rootDir ?? fileRootDir ?? initialRootDir;
1090
+ const env = resolveEnv(envRootDir);
1091
+ if (env.OPENSTEER_AI_MODEL) {
1036
1092
  throw new Error(
1037
1093
  "OPENSTEER_AI_MODEL is no longer supported. Use OPENSTEER_MODEL instead."
1038
1094
  );
1039
1095
  }
1040
- if (process.env.OPENSTEER_RUNTIME != null) {
1096
+ if (env.OPENSTEER_RUNTIME != null) {
1041
1097
  throw new Error(
1042
1098
  "OPENSTEER_RUNTIME is no longer supported. Use OPENSTEER_MODE instead."
1043
1099
  );
1044
1100
  }
1045
- assertNoLegacyAiConfig("Opensteer constructor config", input);
1046
- assertNoLegacyRuntimeConfig("Opensteer constructor config", input);
1047
- const rootDir = input.storage?.rootDir ?? DEFAULT_CONFIG.storage.rootDir ?? process.cwd();
1048
- const fileConfig = loadConfigFile(rootDir);
1049
- assertNoLegacyAiConfig(".opensteer/config.json", fileConfig);
1050
- assertNoLegacyRuntimeConfig(".opensteer/config.json", fileConfig);
1051
1101
  const envConfig = {
1052
1102
  browser: {
1053
- headless: parseBool(process.env.OPENSTEER_HEADLESS),
1054
- executablePath: process.env.OPENSTEER_BROWSER_PATH || void 0,
1055
- slowMo: parseNumber(process.env.OPENSTEER_SLOW_MO),
1056
- connectUrl: process.env.OPENSTEER_CONNECT_URL || void 0,
1057
- channel: process.env.OPENSTEER_CHANNEL || void 0,
1058
- profileDir: process.env.OPENSTEER_PROFILE_DIR || void 0
1103
+ headless: parseBool(env.OPENSTEER_HEADLESS),
1104
+ executablePath: env.OPENSTEER_BROWSER_PATH || void 0,
1105
+ slowMo: parseNumber(env.OPENSTEER_SLOW_MO),
1106
+ connectUrl: env.OPENSTEER_CONNECT_URL || void 0,
1107
+ channel: env.OPENSTEER_CHANNEL || void 0,
1108
+ profileDir: env.OPENSTEER_PROFILE_DIR || void 0
1059
1109
  },
1060
- model: process.env.OPENSTEER_MODEL || void 0,
1061
- debug: parseBool(process.env.OPENSTEER_DEBUG)
1110
+ model: env.OPENSTEER_MODEL || void 0,
1111
+ debug: parseBool(env.OPENSTEER_DEBUG)
1062
1112
  };
1063
- const mergedWithFile = mergeDeep(DEFAULT_CONFIG, fileConfig);
1113
+ const mergedWithFile = mergeDeep(runtimeDefaults, fileConfig);
1064
1114
  const mergedWithEnv = mergeDeep(mergedWithFile, envConfig);
1065
1115
  const resolved = mergeDeep(mergedWithEnv, input);
1066
- const envApiKey = resolveOpensteerApiKey();
1067
- const envAuthScheme = resolveOpensteerAuthScheme();
1116
+ const envApiKey = resolveOpensteerApiKey(env);
1117
+ const envAuthScheme = resolveOpensteerAuthScheme(env);
1068
1118
  const envCloudAnnounce = parseCloudAnnounce(
1069
- process.env.OPENSTEER_REMOTE_ANNOUNCE,
1119
+ env.OPENSTEER_REMOTE_ANNOUNCE,
1070
1120
  "OPENSTEER_REMOTE_ANNOUNCE"
1071
1121
  );
1072
1122
  const inputCloudOptions = normalizeCloudOptions(input.cloud);
@@ -1083,7 +1133,7 @@ function resolveConfig(input = {}) {
1083
1133
  );
1084
1134
  const cloudSelection = resolveCloudSelection({
1085
1135
  cloud: resolved.cloud
1086
- });
1136
+ }, env);
1087
1137
  if (cloudSelection.cloud) {
1088
1138
  const resolvedCloud = normalizeCloudOptions(resolved.cloud) ?? {};
1089
1139
  const authScheme = inputAuthScheme ?? envAuthScheme ?? parseAuthScheme(resolvedCloud.authScheme, "cloud.authScheme") ?? "api-key";
@@ -1443,7 +1493,9 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
1443
1493
  const frameRecords = await this.getFrameRecords();
1444
1494
  const mainFrame = frameRecords[0];
1445
1495
  if (!mainFrame) return;
1446
- await this.waitForFrameVisualStability(mainFrame.frameId, timeout, settleMs);
1496
+ await this.waitForFrameVisualStability(mainFrame.frameId, timeout, settleMs, {
1497
+ retryTransientContextErrors: true
1498
+ });
1447
1499
  }
1448
1500
  async collectVisibleFrameIds() {
1449
1501
  const frameRecords = await this.getFrameRecords();
@@ -1472,19 +1524,40 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
1472
1524
  }
1473
1525
  return visibleFrameIds;
1474
1526
  }
1475
- async waitForFrameVisualStability(frameId, timeout, settleMs) {
1527
+ async waitForFrameVisualStability(frameId, timeout, settleMs, options = {}) {
1476
1528
  if (timeout <= 0) return;
1477
1529
  const script = buildStabilityScript(timeout, settleMs);
1478
- let contextId = await this.ensureFrameContextId(frameId);
1479
- try {
1480
- await this.evaluateWithGuard(contextId, script, timeout);
1481
- } catch (error) {
1482
- if (!isMissingExecutionContextError(error)) {
1483
- throw error;
1530
+ const retryTransientContextErrors = options.retryTransientContextErrors ?? true;
1531
+ if (!retryTransientContextErrors) {
1532
+ let contextId = await this.ensureFrameContextId(frameId);
1533
+ try {
1534
+ await this.evaluateWithGuard(contextId, script, timeout);
1535
+ } catch (error) {
1536
+ if (!isMissingExecutionContextReferenceError(error)) {
1537
+ throw error;
1538
+ }
1539
+ this.contextsByFrame.delete(frameId);
1540
+ contextId = await this.ensureFrameContextId(frameId);
1541
+ await this.evaluateWithGuard(contextId, script, timeout);
1542
+ }
1543
+ return;
1544
+ }
1545
+ const deadline = Date.now() + timeout;
1546
+ while (true) {
1547
+ const remaining = Math.max(0, deadline - Date.now());
1548
+ if (remaining === 0) {
1549
+ return;
1550
+ }
1551
+ const contextId = await this.ensureFrameContextId(frameId);
1552
+ try {
1553
+ await this.evaluateWithGuard(contextId, script, remaining);
1554
+ return;
1555
+ } catch (error) {
1556
+ if (!isTransientExecutionContextError(error)) {
1557
+ throw error;
1558
+ }
1559
+ this.contextsByFrame.delete(frameId);
1484
1560
  }
1485
- this.contextsByFrame.delete(frameId);
1486
- contextId = await this.ensureFrameContextId(frameId);
1487
- await this.evaluateWithGuard(contextId, script, timeout);
1488
1561
  }
1489
1562
  }
1490
1563
  async initialize() {
@@ -1604,7 +1677,10 @@ async function waitForVisualStabilityAcrossFrames(page, options = {}) {
1604
1677
  await runtime.waitForFrameVisualStability(
1605
1678
  frameId,
1606
1679
  remaining,
1607
- settleMs
1680
+ settleMs,
1681
+ {
1682
+ retryTransientContextErrors: false
1683
+ }
1608
1684
  );
1609
1685
  } catch (error) {
1610
1686
  if (isIgnorableFrameError(error)) return;
@@ -1642,7 +1718,12 @@ function sameFrameIds(before, after) {
1642
1718
  function formatCdpException(details) {
1643
1719
  return details.exception?.description || details.text || "CDP runtime evaluation failed.";
1644
1720
  }
1645
- function isMissingExecutionContextError(error) {
1721
+ function isTransientExecutionContextError(error) {
1722
+ if (!(error instanceof Error)) return false;
1723
+ const message = error.message;
1724
+ return message.includes("Execution context was destroyed") || message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context");
1725
+ }
1726
+ function isMissingExecutionContextReferenceError(error) {
1646
1727
  if (!(error instanceof Error)) return false;
1647
1728
  const message = error.message;
1648
1729
  return message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context");
@@ -1650,7 +1731,7 @@ function isMissingExecutionContextError(error) {
1650
1731
  function isIgnorableFrameError(error) {
1651
1732
  if (!(error instanceof Error)) return false;
1652
1733
  const message = error.message;
1653
- return message.includes("Frame was detached") || message.includes("Execution context was destroyed") || message.includes("Target page, context or browser has been closed") || message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context") || message.includes("No frame for given id found");
1734
+ 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");
1654
1735
  }
1655
1736
  function sleep(ms) {
1656
1737
  return new Promise((resolve) => {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Opensteer
3
- } from "../chunk-PIJI7FBH.js";
3
+ } from "../chunk-U6YJI5GO.js";
4
4
  import "../chunk-3H5RRIMZ.js";
5
5
 
6
6
  // src/cli/server.ts
package/dist/index.cjs CHANGED
@@ -881,6 +881,7 @@ var BrowserPool = class {
881
881
  var import_fs = __toESM(require("fs"), 1);
882
882
  var import_path3 = __toESM(require("path"), 1);
883
883
  var import_url = require("url");
884
+ var import_dotenv = require("dotenv");
884
885
 
885
886
  // src/storage/namespace.ts
886
887
  var import_path2 = __toESM(require("path"), 1);
@@ -930,6 +931,53 @@ var DEFAULT_CONFIG = {
930
931
  model: "gpt-5.1",
931
932
  debug: false
932
933
  };
934
+ function dotenvFileOrder(nodeEnv) {
935
+ const normalized = nodeEnv?.trim() || "";
936
+ const files = [];
937
+ if (normalized) {
938
+ files.push(`.env.${normalized}.local`);
939
+ }
940
+ if (normalized !== "test") {
941
+ files.push(".env.local");
942
+ }
943
+ if (normalized) {
944
+ files.push(`.env.${normalized}`);
945
+ }
946
+ files.push(".env");
947
+ return files;
948
+ }
949
+ function loadDotenvValues(rootDir, baseEnv) {
950
+ const values = {};
951
+ if (parseBool(baseEnv.OPENSTEER_DISABLE_DOTENV_AUTOLOAD) === true) {
952
+ return values;
953
+ }
954
+ const baseDir = import_path3.default.resolve(rootDir);
955
+ const nodeEnv = baseEnv.NODE_ENV?.trim() || "";
956
+ for (const filename of dotenvFileOrder(nodeEnv)) {
957
+ const filePath = import_path3.default.join(baseDir, filename);
958
+ if (!import_fs.default.existsSync(filePath)) continue;
959
+ try {
960
+ const raw = import_fs.default.readFileSync(filePath, "utf8");
961
+ const parsed = (0, import_dotenv.parse)(raw);
962
+ for (const [key, value] of Object.entries(parsed)) {
963
+ if (values[key] === void 0) {
964
+ values[key] = value;
965
+ }
966
+ }
967
+ } catch {
968
+ continue;
969
+ }
970
+ }
971
+ return values;
972
+ }
973
+ function resolveEnv(rootDir) {
974
+ const baseEnv = process.env;
975
+ const dotenvValues = loadDotenvValues(rootDir, baseEnv);
976
+ return {
977
+ ...dotenvValues,
978
+ ...baseEnv
979
+ };
980
+ }
933
981
  function hasOwn(config, key) {
934
982
  if (!config || typeof config !== "object") return false;
935
983
  return Object.prototype.hasOwnProperty.call(config, key);
@@ -1058,16 +1106,13 @@ function parseCloudAnnounce(value, source) {
1058
1106
  `Invalid ${source} value "${value}". Use "always", "off", or "tty".`
1059
1107
  );
1060
1108
  }
1061
- function resolveOpensteerApiKey() {
1062
- const value = process.env.OPENSTEER_API_KEY?.trim();
1109
+ function resolveOpensteerApiKey(env) {
1110
+ const value = env.OPENSTEER_API_KEY?.trim();
1063
1111
  if (!value) return void 0;
1064
1112
  return value;
1065
1113
  }
1066
- function resolveOpensteerAuthScheme() {
1067
- return parseAuthScheme(
1068
- process.env.OPENSTEER_AUTH_SCHEME,
1069
- "OPENSTEER_AUTH_SCHEME"
1070
- );
1114
+ function resolveOpensteerAuthScheme(env) {
1115
+ return parseAuthScheme(env.OPENSTEER_AUTH_SCHEME, "OPENSTEER_AUTH_SCHEME");
1071
1116
  }
1072
1117
  function normalizeCloudOptions(value) {
1073
1118
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -1083,7 +1128,7 @@ function parseCloudEnabled(value, source) {
1083
1128
  `Invalid ${source} value "${String(value)}". Use true, false, or a cloud options object.`
1084
1129
  );
1085
1130
  }
1086
- function resolveCloudSelection(config) {
1131
+ function resolveCloudSelection(config, env = process.env) {
1087
1132
  const configCloud = parseCloudEnabled(config.cloud, "cloud");
1088
1133
  if (configCloud !== void 0) {
1089
1134
  return {
@@ -1091,10 +1136,7 @@ function resolveCloudSelection(config) {
1091
1136
  source: "config.cloud"
1092
1137
  };
1093
1138
  }
1094
- const envMode = parseRuntimeMode(
1095
- process.env.OPENSTEER_MODE,
1096
- "OPENSTEER_MODE"
1097
- );
1139
+ const envMode = parseRuntimeMode(env.OPENSTEER_MODE, "OPENSTEER_MODE");
1098
1140
  if (envMode) {
1099
1141
  return {
1100
1142
  cloud: envMode === "cloud",
@@ -1107,41 +1149,49 @@ function resolveCloudSelection(config) {
1107
1149
  };
1108
1150
  }
1109
1151
  function resolveConfig(input = {}) {
1110
- if (process.env.OPENSTEER_AI_MODEL) {
1152
+ const initialRootDir = input.storage?.rootDir ?? process.cwd();
1153
+ const runtimeDefaults = mergeDeep(DEFAULT_CONFIG, {
1154
+ storage: {
1155
+ rootDir: initialRootDir
1156
+ }
1157
+ });
1158
+ assertNoLegacyAiConfig("Opensteer constructor config", input);
1159
+ assertNoLegacyRuntimeConfig("Opensteer constructor config", input);
1160
+ const fileConfig = loadConfigFile(initialRootDir);
1161
+ assertNoLegacyAiConfig(".opensteer/config.json", fileConfig);
1162
+ assertNoLegacyRuntimeConfig(".opensteer/config.json", fileConfig);
1163
+ const fileRootDir = typeof fileConfig.storage?.rootDir === "string" ? fileConfig.storage.rootDir : void 0;
1164
+ const envRootDir = input.storage?.rootDir ?? fileRootDir ?? initialRootDir;
1165
+ const env = resolveEnv(envRootDir);
1166
+ if (env.OPENSTEER_AI_MODEL) {
1111
1167
  throw new Error(
1112
1168
  "OPENSTEER_AI_MODEL is no longer supported. Use OPENSTEER_MODEL instead."
1113
1169
  );
1114
1170
  }
1115
- if (process.env.OPENSTEER_RUNTIME != null) {
1171
+ if (env.OPENSTEER_RUNTIME != null) {
1116
1172
  throw new Error(
1117
1173
  "OPENSTEER_RUNTIME is no longer supported. Use OPENSTEER_MODE instead."
1118
1174
  );
1119
1175
  }
1120
- assertNoLegacyAiConfig("Opensteer constructor config", input);
1121
- assertNoLegacyRuntimeConfig("Opensteer constructor config", input);
1122
- const rootDir = input.storage?.rootDir ?? DEFAULT_CONFIG.storage.rootDir ?? process.cwd();
1123
- const fileConfig = loadConfigFile(rootDir);
1124
- assertNoLegacyAiConfig(".opensteer/config.json", fileConfig);
1125
- assertNoLegacyRuntimeConfig(".opensteer/config.json", fileConfig);
1126
1176
  const envConfig = {
1127
1177
  browser: {
1128
- headless: parseBool(process.env.OPENSTEER_HEADLESS),
1129
- executablePath: process.env.OPENSTEER_BROWSER_PATH || void 0,
1130
- slowMo: parseNumber(process.env.OPENSTEER_SLOW_MO),
1131
- connectUrl: process.env.OPENSTEER_CONNECT_URL || void 0,
1132
- channel: process.env.OPENSTEER_CHANNEL || void 0,
1133
- profileDir: process.env.OPENSTEER_PROFILE_DIR || void 0
1178
+ headless: parseBool(env.OPENSTEER_HEADLESS),
1179
+ executablePath: env.OPENSTEER_BROWSER_PATH || void 0,
1180
+ slowMo: parseNumber(env.OPENSTEER_SLOW_MO),
1181
+ connectUrl: env.OPENSTEER_CONNECT_URL || void 0,
1182
+ channel: env.OPENSTEER_CHANNEL || void 0,
1183
+ profileDir: env.OPENSTEER_PROFILE_DIR || void 0
1134
1184
  },
1135
- model: process.env.OPENSTEER_MODEL || void 0,
1136
- debug: parseBool(process.env.OPENSTEER_DEBUG)
1185
+ model: env.OPENSTEER_MODEL || void 0,
1186
+ debug: parseBool(env.OPENSTEER_DEBUG)
1137
1187
  };
1138
- const mergedWithFile = mergeDeep(DEFAULT_CONFIG, fileConfig);
1188
+ const mergedWithFile = mergeDeep(runtimeDefaults, fileConfig);
1139
1189
  const mergedWithEnv = mergeDeep(mergedWithFile, envConfig);
1140
1190
  const resolved = mergeDeep(mergedWithEnv, input);
1141
- const envApiKey = resolveOpensteerApiKey();
1142
- const envAuthScheme = resolveOpensteerAuthScheme();
1191
+ const envApiKey = resolveOpensteerApiKey(env);
1192
+ const envAuthScheme = resolveOpensteerAuthScheme(env);
1143
1193
  const envCloudAnnounce = parseCloudAnnounce(
1144
- process.env.OPENSTEER_REMOTE_ANNOUNCE,
1194
+ env.OPENSTEER_REMOTE_ANNOUNCE,
1145
1195
  "OPENSTEER_REMOTE_ANNOUNCE"
1146
1196
  );
1147
1197
  const inputCloudOptions = normalizeCloudOptions(input.cloud);
@@ -1158,7 +1208,7 @@ function resolveConfig(input = {}) {
1158
1208
  );
1159
1209
  const cloudSelection = resolveCloudSelection({
1160
1210
  cloud: resolved.cloud
1161
- });
1211
+ }, env);
1162
1212
  if (cloudSelection.cloud) {
1163
1213
  const resolvedCloud = normalizeCloudOptions(resolved.cloud) ?? {};
1164
1214
  const authScheme = inputAuthScheme ?? envAuthScheme ?? parseAuthScheme(resolvedCloud.authScheme, "cloud.authScheme") ?? "api-key";
@@ -1518,7 +1568,9 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
1518
1568
  const frameRecords = await this.getFrameRecords();
1519
1569
  const mainFrame = frameRecords[0];
1520
1570
  if (!mainFrame) return;
1521
- await this.waitForFrameVisualStability(mainFrame.frameId, timeout, settleMs);
1571
+ await this.waitForFrameVisualStability(mainFrame.frameId, timeout, settleMs, {
1572
+ retryTransientContextErrors: true
1573
+ });
1522
1574
  }
1523
1575
  async collectVisibleFrameIds() {
1524
1576
  const frameRecords = await this.getFrameRecords();
@@ -1547,19 +1599,40 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
1547
1599
  }
1548
1600
  return visibleFrameIds;
1549
1601
  }
1550
- async waitForFrameVisualStability(frameId, timeout, settleMs) {
1602
+ async waitForFrameVisualStability(frameId, timeout, settleMs, options = {}) {
1551
1603
  if (timeout <= 0) return;
1552
1604
  const script = buildStabilityScript(timeout, settleMs);
1553
- let contextId = await this.ensureFrameContextId(frameId);
1554
- try {
1555
- await this.evaluateWithGuard(contextId, script, timeout);
1556
- } catch (error) {
1557
- if (!isMissingExecutionContextError(error)) {
1558
- throw error;
1605
+ const retryTransientContextErrors = options.retryTransientContextErrors ?? true;
1606
+ if (!retryTransientContextErrors) {
1607
+ let contextId = await this.ensureFrameContextId(frameId);
1608
+ try {
1609
+ await this.evaluateWithGuard(contextId, script, timeout);
1610
+ } catch (error) {
1611
+ if (!isMissingExecutionContextReferenceError(error)) {
1612
+ throw error;
1613
+ }
1614
+ this.contextsByFrame.delete(frameId);
1615
+ contextId = await this.ensureFrameContextId(frameId);
1616
+ await this.evaluateWithGuard(contextId, script, timeout);
1617
+ }
1618
+ return;
1619
+ }
1620
+ const deadline = Date.now() + timeout;
1621
+ while (true) {
1622
+ const remaining = Math.max(0, deadline - Date.now());
1623
+ if (remaining === 0) {
1624
+ return;
1625
+ }
1626
+ const contextId = await this.ensureFrameContextId(frameId);
1627
+ try {
1628
+ await this.evaluateWithGuard(contextId, script, remaining);
1629
+ return;
1630
+ } catch (error) {
1631
+ if (!isTransientExecutionContextError(error)) {
1632
+ throw error;
1633
+ }
1634
+ this.contextsByFrame.delete(frameId);
1559
1635
  }
1560
- this.contextsByFrame.delete(frameId);
1561
- contextId = await this.ensureFrameContextId(frameId);
1562
- await this.evaluateWithGuard(contextId, script, timeout);
1563
1636
  }
1564
1637
  }
1565
1638
  async initialize() {
@@ -1679,7 +1752,10 @@ async function waitForVisualStabilityAcrossFrames(page, options = {}) {
1679
1752
  await runtime.waitForFrameVisualStability(
1680
1753
  frameId,
1681
1754
  remaining,
1682
- settleMs
1755
+ settleMs,
1756
+ {
1757
+ retryTransientContextErrors: false
1758
+ }
1683
1759
  );
1684
1760
  } catch (error) {
1685
1761
  if (isIgnorableFrameError(error)) return;
@@ -1717,7 +1793,12 @@ function sameFrameIds(before, after) {
1717
1793
  function formatCdpException(details) {
1718
1794
  return details.exception?.description || details.text || "CDP runtime evaluation failed.";
1719
1795
  }
1720
- function isMissingExecutionContextError(error) {
1796
+ function isTransientExecutionContextError(error) {
1797
+ if (!(error instanceof Error)) return false;
1798
+ const message = error.message;
1799
+ return message.includes("Execution context was destroyed") || message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context");
1800
+ }
1801
+ function isMissingExecutionContextReferenceError(error) {
1721
1802
  if (!(error instanceof Error)) return false;
1722
1803
  const message = error.message;
1723
1804
  return message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context");
@@ -1725,7 +1806,7 @@ function isMissingExecutionContextError(error) {
1725
1806
  function isIgnorableFrameError(error) {
1726
1807
  if (!(error instanceof Error)) return false;
1727
1808
  const message = error.message;
1728
- return message.includes("Frame was detached") || message.includes("Execution context was destroyed") || message.includes("Target page, context or browser has been closed") || message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context") || message.includes("No frame for given id found");
1809
+ 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");
1729
1810
  }
1730
1811
  function sleep(ms) {
1731
1812
  return new Promise((resolve) => {
package/dist/index.js CHANGED
@@ -68,7 +68,7 @@ import {
68
68
  switchTab,
69
69
  typeText,
70
70
  waitForVisualStability
71
- } from "./chunk-PIJI7FBH.js";
71
+ } from "./chunk-U6YJI5GO.js";
72
72
  import {
73
73
  createResolveCallback
74
74
  } from "./chunk-SRJLH34D.js";
package/package.json CHANGED
@@ -1,75 +1,82 @@
1
1
  {
2
- "name": "opensteer",
3
- "version": "0.4.7",
4
- "description": "Open-source browser automation SDK with robust selectors and deterministic replay.",
5
- "license": "MIT",
6
- "type": "module",
7
- "bin": {
8
- "opensteer": "./bin/opensteer.mjs"
9
- },
10
- "main": "./dist/index.cjs",
11
- "module": "./dist/index.js",
12
- "types": "./dist/index.d.ts",
13
- "exports": {
14
- ".": {
15
- "types": "./dist/index.d.ts",
16
- "import": "./dist/index.js",
17
- "require": "./dist/index.cjs"
2
+ "name": "opensteer",
3
+ "version": "0.4.9",
4
+ "packageManager": "pnpm@10.29.3",
5
+ "description": "Open-source browser automation SDK with robust selectors and deterministic replay.",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "bin": {
9
+ "opensteer": "./bin/opensteer.mjs"
10
+ },
11
+ "main": "./dist/index.cjs",
12
+ "module": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/index.js",
18
+ "require": "./dist/index.cjs"
19
+ }
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "bin",
24
+ "README.md",
25
+ "LICENSE",
26
+ "CHANGELOG.md"
27
+ ],
28
+ "scripts": {
29
+ "build": "tsup src/index.ts src/cli/server.ts --dts --format esm,cjs --clean --external ai --external zod --external @ai-sdk/openai --external @ai-sdk/anthropic --external @ai-sdk/google --external @ai-sdk/xai --external @ai-sdk/groq",
30
+ "prepublishOnly": "pnpm run build",
31
+ "test": "vitest run",
32
+ "test:live-web": "vitest run --config vitest.live-web.config.ts",
33
+ "test:unit": "vitest run tests/html tests/element-path tests/config.test.ts tests/storage",
34
+ "test:actions": "vitest run tests/actions",
35
+ "test:integration": "vitest run tests/integration",
36
+ "test:ai": "vitest run tests/ai tests/e2e/ai-resolve.test.ts tests/e2e/ai-extract-products.test.ts",
37
+ "test:e2e": "vitest run tests/e2e",
38
+ "test:app:dev": "pnpm --dir tests/test-app run dev",
39
+ "typecheck": "tsc -p tsconfig.json --noEmit"
40
+ },
41
+ "dependencies": {
42
+ "@ai-sdk/anthropic": "^3.0.46",
43
+ "@ai-sdk/google": "^3.0.30",
44
+ "@ai-sdk/groq": "^3.0.24",
45
+ "@ai-sdk/openai": "^3.0.26",
46
+ "@ai-sdk/xai": "^3.0.57",
47
+ "ai": "^6.0.77",
48
+ "cheerio": "^1.0.0-rc.12",
49
+ "dotenv": "^17.2.4",
50
+ "playwright": "^1.50.0",
51
+ "ws": "^8.18.0",
52
+ "zod": "^4.3.6"
53
+ },
54
+ "devDependencies": {
55
+ "@types/node": "^22.10.0",
56
+ "@types/ws": "^8.5.13",
57
+ "domhandler": "^5.0.3",
58
+ "tsup": "^8.0.1",
59
+ "typescript": "^5.6.3",
60
+ "vite": "^7.3.1",
61
+ "vitest": "^2.1.8"
62
+ },
63
+ "pnpm": {
64
+ "onlyBuiltDependencies": [
65
+ "esbuild"
66
+ ]
67
+ },
68
+ "engines": {
69
+ "node": ">=20"
70
+ },
71
+ "keywords": [
72
+ "browser-automation",
73
+ "selectors",
74
+ "playwright",
75
+ "llm",
76
+ "agents"
77
+ ],
78
+ "repository": {
79
+ "type": "git",
80
+ "url": "https://github.com/opensteer-ai/opensteer"
18
81
  }
19
- },
20
- "files": [
21
- "dist",
22
- "bin",
23
- "README.md",
24
- "LICENSE",
25
- "CHANGELOG.md"
26
- ],
27
- "dependencies": {
28
- "@ai-sdk/anthropic": "^3.0.46",
29
- "@ai-sdk/google": "^3.0.30",
30
- "@ai-sdk/groq": "^3.0.24",
31
- "@ai-sdk/openai": "^3.0.26",
32
- "@ai-sdk/xai": "^3.0.57",
33
- "ai": "^6.0.77",
34
- "cheerio": "^1.0.0-rc.12",
35
- "dotenv": "^17.2.4",
36
- "playwright": "^1.50.0",
37
- "ws": "^8.18.0",
38
- "zod": "^4.3.6"
39
- },
40
- "devDependencies": {
41
- "@types/node": "^22.10.0",
42
- "@types/ws": "^8.5.13",
43
- "domhandler": "^5.0.3",
44
- "tsup": "^8.0.1",
45
- "typescript": "^5.6.3",
46
- "vite": "^7.3.1",
47
- "vitest": "^2.1.8"
48
- },
49
- "engines": {
50
- "node": ">=20"
51
- },
52
- "keywords": [
53
- "browser-automation",
54
- "selectors",
55
- "playwright",
56
- "llm",
57
- "agents"
58
- ],
59
- "repository": {
60
- "type": "git",
61
- "url": "https://github.com/opensteer-ai/opensteer"
62
- },
63
- "scripts": {
64
- "build": "tsup src/index.ts src/cli/server.ts --dts --format esm,cjs --clean --external ai --external zod --external @ai-sdk/openai --external @ai-sdk/anthropic --external @ai-sdk/google --external @ai-sdk/xai --external @ai-sdk/groq",
65
- "test": "vitest run",
66
- "test:live-web": "vitest run --config vitest.live-web.config.ts",
67
- "test:unit": "vitest run tests/html tests/element-path tests/config.test.ts tests/storage",
68
- "test:actions": "vitest run tests/actions",
69
- "test:integration": "vitest run tests/integration",
70
- "test:ai": "vitest run tests/ai tests/e2e/ai-resolve.test.ts tests/e2e/ai-extract-products.test.ts",
71
- "test:e2e": "vitest run tests/e2e",
72
- "test:app:dev": "pnpm --dir tests/test-app run dev",
73
- "typecheck": "tsc -p tsconfig.json --noEmit"
74
- }
75
- }
82
+ }