opensteer 0.4.9 → 0.4.11

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
@@ -36,6 +36,11 @@
36
36
  timeout/stale-target cases more accurately.
37
37
  - Cloud action failures now accept optional structured failure details and map
38
38
  them to `OpensteerActionError` when available.
39
+ - Docs: refreshed README and getting-started guidance to match current SDK/CLI
40
+ behavior and env vars.
41
+ - Docs: added CLI reference and docs index.
42
+ - OSS community docs: expanded `CONTRIBUTING.md` and added `SECURITY.md` +
43
+ `SUPPORT.md`.
39
44
 
40
45
  ## 0.1.0
41
46
 
package/README.md CHANGED
@@ -1,186 +1,115 @@
1
1
  # Opensteer
2
2
 
3
- Lean browser automation SDK for coding agents and script replay.
3
+ Open-source browser automation SDK for coding agents and deterministic replay.
4
4
 
5
- `opensteer` provides descriptor-aware actions (`click`, `dblclick`,
6
- `rightclick`, `hover`, `input`, `select`, `scroll`, `extract`,
7
- `extractFromPlan`, `uploadFile`), observation (`snapshot`, `state`,
8
- `screenshot`), navigation (`goto`), and convenience methods for tabs, cookies,
9
- keyboard, element info, and wait.
5
+ Opensteer combines descriptor-aware actions, resilient selector persistence,
6
+ clean HTML snapshots, and first-class local or cloud runtime support.
10
7
 
11
- For anything not covered, use raw Playwright via `opensteer.page` and
12
- `opensteer.context`.
8
+ ## Requirements
9
+
10
+ - Node.js `>=20`
11
+ - A browser environment supported by Playwright
12
+ - API key for your selected model provider if you use LLM resolve/extract
13
13
 
14
14
  ## Install
15
15
 
16
16
  ```bash
17
17
  # npm
18
- npm install opensteer playwright
18
+ npm install opensteer
19
19
  # pnpm
20
- pnpm add opensteer playwright
20
+ pnpm add opensteer
21
21
  ```
22
22
 
23
- ## CLI Session Routing
24
-
25
- OpenSteer CLI now separates runtime routing from selector caching:
26
-
27
- - Runtime routing: `--session` or `OPENSTEER_SESSION`
28
- - Selector cache namespace: `--name` or `OPENSTEER_NAME` (used on `open`)
29
-
30
- If neither `--session` nor `OPENSTEER_SESSION` is set:
31
-
32
- - In an interactive terminal, OpenSteer creates/reuses a terminal-scoped default session.
33
- - In non-interactive environments (agents/CI), it fails fast unless you set
34
- `OPENSTEER_SESSION` or `OPENSTEER_CLIENT_ID`.
35
-
36
- Example:
23
+ If your environment skips Playwright browser downloads, run:
37
24
 
38
25
  ```bash
39
- export OPENSTEER_SESSION=agent-a
40
- opensteer open https://example.com --name product-scraper
41
- opensteer snapshot
42
- opensteer click 3
43
- opensteer status
26
+ npx playwright install chromium
44
27
  ```
45
28
 
46
- `opensteer status` reports `resolvedSession`, `sessionSource`, `resolvedName`, and `nameSource`.
47
-
48
- ## Quickstart
29
+ ## Quickstart (SDK)
49
30
 
50
31
  ```ts
51
32
  import { Opensteer } from "opensteer";
52
33
 
53
- const opensteer = new Opensteer({ name: "my-scraper" }); // defaults to model: 'gpt-5.1'
34
+ const opensteer = new Opensteer({ name: "my-scraper" });
54
35
  await opensteer.launch({ headless: false });
55
36
 
56
- await opensteer.goto("https://example.com");
57
- const html = await opensteer.snapshot();
58
-
59
- await opensteer.click({ description: "login-button" });
60
- await opensteer.input({ description: "email", text: "user@example.com" });
61
- await opensteer.page.keyboard.press("Enter");
62
-
63
- await opensteer.close();
64
- ```
65
-
66
- ## Core Model
67
-
68
- - `opensteer.page`: raw Playwright `Page`
69
- - `opensteer.context`: raw Playwright `BrowserContext`
70
- - Opensteer methods: descriptor-aware operations that can persist selectors
71
- - Selector storage: `.opensteer/selectors/<namespace>`
72
-
73
- ## Resolution Chain
74
-
75
- For actions like `click`/`input`/`hover`/`select`/`scroll`:
76
-
77
- 1. Use persisted path for `description` (if present)
78
- 2. Use `element` counter from snapshot
79
- 3. Use explicit CSS `selector`
80
- 4. Use built-in LLM resolution (`description` required)
81
- 5. Throw
82
-
83
- When steps 2-4 resolve and `description` is provided, the path is persisted.
84
-
85
- ## Smart Post-Action Wait
86
-
87
- Mutating actions (`click`, `input`, `select`, `scroll`, etc.) include a
88
- best-effort post-action wait so delayed visual updates are usually settled
89
- before the method resolves.
90
-
91
- You can disable or tune this per call:
92
-
93
- ```ts
94
- await opensteer.click({ description: "Save button", wait: false });
37
+ try {
38
+ await opensteer.goto("https://example.com");
39
+ const html = await opensteer.snapshot();
40
+ console.log(html.slice(0, 500));
95
41
 
96
- await opensteer.click({
97
- description: "Save button",
98
- wait: { timeout: 9000, settleMs: 900, includeNetwork: true, networkQuietMs: 400 },
99
- });
42
+ await opensteer.click({ description: "main call to action", element: 3 });
43
+ } finally {
44
+ await opensteer.close();
45
+ }
100
46
  ```
101
47
 
102
- ## Action Failure Diagnostics
103
-
104
- Descriptor-aware interaction methods (`click`, `dblclick`, `rightclick`,
105
- `hover`, `input`, `select`, `scroll`, `uploadFile`) throw
106
- `OpensteerActionError` when an interaction cannot be completed.
107
-
108
- The error includes structured failure metadata for agent/tooling decisions:
48
+ ## Quickstart (CLI)
109
49
 
110
- - `error.failure.code` (`ActionFailureCode`)
111
- - `error.failure.message`
112
- - `error.failure.retryable`
113
- - `error.failure.classificationSource`
114
- - `error.failure.details` (for blocker and observation details when available)
115
-
116
- ```ts
117
- import { Opensteer, OpensteerActionError } from "opensteer";
118
-
119
- try {
120
- await opensteer.click({ description: "Save button" });
121
- } catch (err) {
122
- if (err instanceof OpensteerActionError) {
123
- console.error(err.failure.code); // e.g. BLOCKED_BY_INTERCEPTOR
124
- console.error(err.failure.message);
125
- console.error(err.failure.classificationSource);
126
- }
127
- throw err;
128
- }
129
- ```
50
+ Opensteer CLI separates runtime routing from selector namespace routing.
130
51
 
131
- ## Snapshot Modes
52
+ - Runtime routing: `--session` or `OPENSTEER_SESSION`
53
+ - Selector namespace: `--name` or `OPENSTEER_NAME` (used by `open`)
132
54
 
133
- ```ts
134
- await opensteer.snapshot(); // action mode (default)
135
- await opensteer.snapshot({ mode: "extraction" });
136
- await opensteer.snapshot({ mode: "clickable" });
137
- await opensteer.snapshot({ mode: "scrollable" });
138
- await opensteer.snapshot({ mode: "full" });
55
+ ```bash
56
+ opensteer open https://example.com --session agent-a --name product-scraper
57
+ opensteer snapshot --session agent-a
58
+ opensteer click 3 --session agent-a
59
+ opensteer status --session agent-a
60
+ opensteer close --session agent-a
139
61
  ```
140
62
 
141
- ## Two Usage Patterns
63
+ In non-interactive environments, set `OPENSTEER_SESSION` or
64
+ `OPENSTEER_CLIENT_ID` explicitly.
142
65
 
143
- ### Explore (coding agent, no API key required)
66
+ ## Resolution and Replay Model
144
67
 
145
- Use `snapshot()` + `element` counters while exploring in real time, then persist
146
- stable descriptions for replay.
68
+ For descriptor-aware actions (`click`, `input`, `hover`, `select`, `scroll`):
147
69
 
148
- ### Run (script replay / built-in LLM)
149
-
150
- Opensteer uses built-in LLM resolve/extract by default. You can override the
151
- default model with top-level `model` or `OPENSTEER_MODEL`.
70
+ 1. Reuse persisted path for `description`
71
+ 2. Use `element` counter from snapshot
72
+ 3. Use explicit CSS `selector`
73
+ 4. Use built-in LLM resolution (`description` required)
74
+ 5. Throw actionable error
152
75
 
153
- ```ts
154
- const opensteer = new Opensteer({
155
- name: "run-mode",
156
- model: "gpt-5-mini",
157
- });
158
- ```
76
+ When steps 2-4 succeed and `description` is present, Opensteer persists the
77
+ path for deterministic replay in `.opensteer/selectors/<namespace>`.
159
78
 
160
- ## Mode Selection
79
+ ## Cloud Mode
161
80
 
162
81
  Opensteer defaults to local mode.
163
82
 
164
- - `OPENSTEER_MODE=local` runs local Playwright.
165
- - `OPENSTEER_MODE=cloud` enables cloud mode (requires `OPENSTEER_API_KEY`).
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.
83
+ - `OPENSTEER_MODE=local|cloud`
84
+ - `OPENSTEER_API_KEY` or `cloud.apiKey` required in cloud mode
85
+ - `OPENSTEER_BASE_URL` or `cloud.baseUrl` to override the default cloud host
86
+ - `OPENSTEER_AUTH_SCHEME` or `cloud.authScheme` for auth header mode
87
+ (`api-key` or `bearer`)
88
+ - `cloud: true` or a `cloud` options object overrides `OPENSTEER_MODE`
172
89
 
173
- Cloud mode is fail-fast: it does not automatically fall back to local mode.
90
+ `.env` files are auto-loaded from `storage.rootDir` (default `process.cwd()`)
91
+ in this order: `.env.<NODE_ENV>.local`, `.env.local` (except in test),
92
+ `.env.<NODE_ENV>`, `.env`. Existing `process.env` values are not overwritten.
93
+ Set `OPENSTEER_DISABLE_DOTENV_AUTOLOAD=true` to disable.
174
94
 
175
95
  ## Docs
176
96
 
177
- - `docs/getting-started.md`
178
- - `docs/api-reference.md`
179
- - `docs/cloud-integration.md`
180
- - `docs/html-cleaning.md`
181
- - `docs/selectors.md`
182
- - `docs/live-web-tests.md`
97
+ - [Getting Started](docs/getting-started.md)
98
+ - [API Reference](docs/api-reference.md)
99
+ - [CLI Reference](docs/cli-reference.md)
100
+ - [Cloud Integration](docs/cloud-integration.md)
101
+ - [Selectors and Storage](docs/selectors.md)
102
+ - [HTML Cleaning and Snapshot Modes](docs/html-cleaning.md)
103
+ - [Live Web Validation Suite](docs/live-web-tests.md)
104
+
105
+ ## Community
106
+
107
+ - [Contributing Guide](CONTRIBUTING.md)
108
+ - [Code of Conduct](CODE_OF_CONDUCT.md)
109
+ - [Security Policy](SECURITY.md)
110
+ - [Support](SUPPORT.md)
111
+ - [Changelog](CHANGELOG.md)
183
112
 
184
113
  ## License
185
114
 
186
- MIT
115
+ [MIT](LICENSE)
package/bin/opensteer.mjs CHANGED
@@ -781,7 +781,7 @@ Environment:
781
781
  OPENSTEER_MODE Runtime routing: "local" (default) or "cloud"
782
782
  OPENSTEER_API_KEY Required when cloud mode is selected
783
783
  OPENSTEER_BASE_URL Override cloud control-plane base URL
784
- OPENSTEER_APP_URL Cloud app base URL for emitting browser session links (default: https://opensteer.com)
784
+ OPENSTEER_AUTH_SCHEME Cloud auth scheme: api-key (default) or bearer
785
785
  OPENSTEER_REMOTE_ANNOUNCE Cloud session announcement policy: always (default), off, tty
786
786
  `)
787
787
  }
@@ -38,6 +38,7 @@ function sanitizeNamespaceSegment(segment) {
38
38
  var DEFAULT_TIMEOUT = 3e4;
39
39
  var DEFAULT_SETTLE_MS = 750;
40
40
  var FRAME_EVALUATE_GRACE_MS = 200;
41
+ var TRANSIENT_CONTEXT_RETRY_DELAY_MS = 25;
41
42
  var STEALTH_WORLD_NAME = "__opensteer_wait__";
42
43
  var StealthWaitUnavailableError = class extends Error {
43
44
  constructor(cause) {
@@ -341,9 +342,12 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
341
342
  const frameRecords = await this.getFrameRecords();
342
343
  const mainFrame = frameRecords[0];
343
344
  if (!mainFrame) return;
344
- await this.waitForFrameVisualStability(mainFrame.frameId, timeout, settleMs, {
345
- retryTransientContextErrors: true
346
- });
345
+ await this.waitForFrameVisualStability(
346
+ mainFrame.frameId,
347
+ timeout,
348
+ settleMs,
349
+ true
350
+ );
347
351
  }
348
352
  async collectVisibleFrameIds() {
349
353
  const frameRecords = await this.getFrameRecords();
@@ -372,16 +376,15 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
372
376
  }
373
377
  return visibleFrameIds;
374
378
  }
375
- async waitForFrameVisualStability(frameId, timeout, settleMs, options = {}) {
379
+ async waitForFrameVisualStability(frameId, timeout, settleMs, retryTransientContextErrors = true) {
376
380
  if (timeout <= 0) return;
377
381
  const script = buildStabilityScript(timeout, settleMs);
378
- const retryTransientContextErrors = options.retryTransientContextErrors ?? true;
379
382
  if (!retryTransientContextErrors) {
380
383
  let contextId = await this.ensureFrameContextId(frameId);
381
384
  try {
382
385
  await this.evaluateWithGuard(contextId, script, timeout);
383
386
  } catch (error) {
384
- if (!isMissingExecutionContextReferenceError(error)) {
387
+ if (!isMissingExecutionContextError(error)) {
385
388
  throw error;
386
389
  }
387
390
  this.contextsByFrame.delete(frameId);
@@ -405,6 +408,11 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
405
408
  throw error;
406
409
  }
407
410
  this.contextsByFrame.delete(frameId);
411
+ const retryDelay = Math.min(
412
+ TRANSIENT_CONTEXT_RETRY_DELAY_MS,
413
+ Math.max(0, deadline - Date.now())
414
+ );
415
+ await sleep(retryDelay);
408
416
  }
409
417
  }
410
418
  }
@@ -526,9 +534,7 @@ async function waitForVisualStabilityAcrossFrames(page, options = {}) {
526
534
  frameId,
527
535
  remaining,
528
536
  settleMs,
529
- {
530
- retryTransientContextErrors: false
531
- }
537
+ false
532
538
  );
533
539
  } catch (error) {
534
540
  if (isIgnorableFrameError(error)) return;
@@ -571,7 +577,7 @@ function isTransientExecutionContextError(error) {
571
577
  const message = error.message;
572
578
  return message.includes("Execution context was destroyed") || message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context");
573
579
  }
574
- function isMissingExecutionContextReferenceError(error) {
580
+ function isMissingExecutionContextError(error) {
575
581
  if (!(error instanceof Error)) return false;
576
582
  const message = error.message;
577
583
  return message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context");
@@ -6208,6 +6214,11 @@ function resolveOpensteerApiKey(env) {
6208
6214
  if (!value) return void 0;
6209
6215
  return value;
6210
6216
  }
6217
+ function resolveOpensteerBaseUrl(env) {
6218
+ const value = env.OPENSTEER_BASE_URL?.trim();
6219
+ if (!value) return void 0;
6220
+ return value;
6221
+ }
6211
6222
  function resolveOpensteerAuthScheme(env) {
6212
6223
  return parseAuthScheme(env.OPENSTEER_AUTH_SCHEME, "OPENSTEER_AUTH_SCHEME");
6213
6224
  }
@@ -6286,6 +6297,7 @@ function resolveConfig(input = {}) {
6286
6297
  const mergedWithEnv = mergeDeep(mergedWithFile, envConfig);
6287
6298
  const resolved = mergeDeep(mergedWithEnv, input);
6288
6299
  const envApiKey = resolveOpensteerApiKey(env);
6300
+ const envBaseUrl = resolveOpensteerBaseUrl(env);
6289
6301
  const envAuthScheme = resolveOpensteerAuthScheme(env);
6290
6302
  const envCloudAnnounce = parseCloudAnnounce(
6291
6303
  env.OPENSTEER_REMOTE_ANNOUNCE,
@@ -6303,6 +6315,9 @@ function resolveConfig(input = {}) {
6303
6315
  const inputHasCloudApiKey = Boolean(
6304
6316
  inputCloudOptions && Object.prototype.hasOwnProperty.call(inputCloudOptions, "apiKey")
6305
6317
  );
6318
+ const inputHasCloudBaseUrl = Boolean(
6319
+ inputCloudOptions && Object.prototype.hasOwnProperty.call(inputCloudOptions, "baseUrl")
6320
+ );
6306
6321
  const cloudSelection = resolveCloudSelection({
6307
6322
  cloud: resolved.cloud
6308
6323
  }, env);
@@ -6322,6 +6337,12 @@ function resolveConfig(input = {}) {
6322
6337
  apiKey: envApiKey
6323
6338
  };
6324
6339
  }
6340
+ if (envBaseUrl && cloudSelection.cloud && !inputHasCloudBaseUrl) {
6341
+ resolved.cloud = {
6342
+ ...normalizeCloudOptions(resolved.cloud) ?? {},
6343
+ baseUrl: envBaseUrl
6344
+ };
6345
+ }
6325
6346
  return resolved;
6326
6347
  }
6327
6348
  function resolveNamespace(config, rootDir) {
@@ -7716,12 +7737,16 @@ function clonePersistedExtractNode(node) {
7716
7737
 
7717
7738
  // src/cloud/runtime.ts
7718
7739
  var DEFAULT_CLOUD_BASE_URL = "https://remote.opensteer.com";
7719
- var DEFAULT_CLOUD_APP_URL = "https://opensteer.com";
7720
- function createCloudRuntimeState(key, baseUrl = resolveCloudBaseUrl(), authScheme = "api-key", appUrl = resolveCloudAppUrl()) {
7740
+ function createCloudRuntimeState(key, baseUrl = resolveCloudBaseUrl(), authScheme = "api-key") {
7741
+ const normalizedBaseUrl = normalizeCloudBaseUrl(baseUrl);
7721
7742
  return {
7722
- sessionClient: new CloudSessionClient(baseUrl, key, authScheme),
7743
+ sessionClient: new CloudSessionClient(
7744
+ normalizedBaseUrl,
7745
+ key,
7746
+ authScheme
7747
+ ),
7723
7748
  cdpClient: new CloudCdpClient(),
7724
- appUrl: normalizeCloudAppUrl(appUrl),
7749
+ baseUrl: normalizedBaseUrl,
7725
7750
  actionClient: null,
7726
7751
  sessionId: null,
7727
7752
  localRunId: null,
@@ -7731,15 +7756,9 @@ function createCloudRuntimeState(key, baseUrl = resolveCloudBaseUrl(), authSchem
7731
7756
  function resolveCloudBaseUrl() {
7732
7757
  const value = process.env.OPENSTEER_BASE_URL?.trim();
7733
7758
  if (!value) return DEFAULT_CLOUD_BASE_URL;
7734
- return value.replace(/\/+$/, "");
7735
- }
7736
- function resolveCloudAppUrl() {
7737
- const value = process.env.OPENSTEER_APP_URL?.trim();
7738
- if (!value) return DEFAULT_CLOUD_APP_URL;
7739
- return normalizeCloudAppUrl(value);
7759
+ return normalizeCloudBaseUrl(value);
7740
7760
  }
7741
- function normalizeCloudAppUrl(value) {
7742
- if (!value) return null;
7761
+ function normalizeCloudBaseUrl(value) {
7743
7762
  return value.replace(/\/+$/, "");
7744
7763
  }
7745
7764
  function readCloudActionDescription(payload) {
@@ -7797,8 +7816,7 @@ var Opensteer = class _Opensteer {
7797
7816
  this.cloud = createCloudRuntimeState(
7798
7817
  apiKey,
7799
7818
  cloudConfig?.baseUrl,
7800
- cloudConfig?.authScheme,
7801
- cloudConfig?.appUrl
7819
+ cloudConfig?.authScheme
7802
7820
  );
7803
7821
  } else {
7804
7822
  this.cloud = null;
@@ -7990,10 +8008,7 @@ var Opensteer = class _Opensteer {
7990
8008
  this.snapshotCache = null;
7991
8009
  this.cloud.actionClient = actionClient;
7992
8010
  this.cloud.sessionId = sessionId;
7993
- this.cloud.cloudSessionUrl = buildCloudSessionUrl(
7994
- this.cloud.appUrl,
7995
- session2.cloudSession.sessionId
7996
- );
8011
+ this.cloud.cloudSessionUrl = session2.cloudSessionUrl;
7997
8012
  this.announceCloudSession({
7998
8013
  sessionId: session2.sessionId,
7999
8014
  workspaceId: session2.cloudSession.workspaceId,
@@ -10050,12 +10065,6 @@ function buildLocalRunId(namespace) {
10050
10065
  const normalized = namespace.trim() || "default";
10051
10066
  return `${normalized}-${Date.now().toString(36)}-${randomUUID2().slice(0, 8)}`;
10052
10067
  }
10053
- function buildCloudSessionUrl(appUrl, sessionId) {
10054
- if (!appUrl) {
10055
- return null;
10056
- }
10057
- return `${appUrl}/browser/${encodeURIComponent(sessionId)}`;
10058
- }
10059
10068
 
10060
10069
  export {
10061
10070
  normalizeNamespace,
@@ -1036,6 +1036,11 @@ function resolveOpensteerApiKey(env) {
1036
1036
  if (!value) return void 0;
1037
1037
  return value;
1038
1038
  }
1039
+ function resolveOpensteerBaseUrl(env) {
1040
+ const value = env.OPENSTEER_BASE_URL?.trim();
1041
+ if (!value) return void 0;
1042
+ return value;
1043
+ }
1039
1044
  function resolveOpensteerAuthScheme(env) {
1040
1045
  return parseAuthScheme(env.OPENSTEER_AUTH_SCHEME, "OPENSTEER_AUTH_SCHEME");
1041
1046
  }
@@ -1114,6 +1119,7 @@ function resolveConfig(input = {}) {
1114
1119
  const mergedWithEnv = mergeDeep(mergedWithFile, envConfig);
1115
1120
  const resolved = mergeDeep(mergedWithEnv, input);
1116
1121
  const envApiKey = resolveOpensteerApiKey(env);
1122
+ const envBaseUrl = resolveOpensteerBaseUrl(env);
1117
1123
  const envAuthScheme = resolveOpensteerAuthScheme(env);
1118
1124
  const envCloudAnnounce = parseCloudAnnounce(
1119
1125
  env.OPENSTEER_REMOTE_ANNOUNCE,
@@ -1131,6 +1137,9 @@ function resolveConfig(input = {}) {
1131
1137
  const inputHasCloudApiKey = Boolean(
1132
1138
  inputCloudOptions && Object.prototype.hasOwnProperty.call(inputCloudOptions, "apiKey")
1133
1139
  );
1140
+ const inputHasCloudBaseUrl = Boolean(
1141
+ inputCloudOptions && Object.prototype.hasOwnProperty.call(inputCloudOptions, "baseUrl")
1142
+ );
1134
1143
  const cloudSelection = resolveCloudSelection({
1135
1144
  cloud: resolved.cloud
1136
1145
  }, env);
@@ -1150,6 +1159,12 @@ function resolveConfig(input = {}) {
1150
1159
  apiKey: envApiKey
1151
1160
  };
1152
1161
  }
1162
+ if (envBaseUrl && cloudSelection.cloud && !inputHasCloudBaseUrl) {
1163
+ resolved.cloud = {
1164
+ ...normalizeCloudOptions(resolved.cloud) ?? {},
1165
+ baseUrl: envBaseUrl
1166
+ };
1167
+ }
1153
1168
  return resolved;
1154
1169
  }
1155
1170
  function resolveNamespace(config, rootDir) {
@@ -1190,6 +1205,7 @@ function getCallerFilePath() {
1190
1205
  var DEFAULT_TIMEOUT = 3e4;
1191
1206
  var DEFAULT_SETTLE_MS = 750;
1192
1207
  var FRAME_EVALUATE_GRACE_MS = 200;
1208
+ var TRANSIENT_CONTEXT_RETRY_DELAY_MS = 25;
1193
1209
  var STEALTH_WORLD_NAME = "__opensteer_wait__";
1194
1210
  var StealthWaitUnavailableError = class extends Error {
1195
1211
  constructor(cause) {
@@ -1493,9 +1509,12 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
1493
1509
  const frameRecords = await this.getFrameRecords();
1494
1510
  const mainFrame = frameRecords[0];
1495
1511
  if (!mainFrame) return;
1496
- await this.waitForFrameVisualStability(mainFrame.frameId, timeout, settleMs, {
1497
- retryTransientContextErrors: true
1498
- });
1512
+ await this.waitForFrameVisualStability(
1513
+ mainFrame.frameId,
1514
+ timeout,
1515
+ settleMs,
1516
+ true
1517
+ );
1499
1518
  }
1500
1519
  async collectVisibleFrameIds() {
1501
1520
  const frameRecords = await this.getFrameRecords();
@@ -1524,16 +1543,15 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
1524
1543
  }
1525
1544
  return visibleFrameIds;
1526
1545
  }
1527
- async waitForFrameVisualStability(frameId, timeout, settleMs, options = {}) {
1546
+ async waitForFrameVisualStability(frameId, timeout, settleMs, retryTransientContextErrors = true) {
1528
1547
  if (timeout <= 0) return;
1529
1548
  const script = buildStabilityScript(timeout, settleMs);
1530
- const retryTransientContextErrors = options.retryTransientContextErrors ?? true;
1531
1549
  if (!retryTransientContextErrors) {
1532
1550
  let contextId = await this.ensureFrameContextId(frameId);
1533
1551
  try {
1534
1552
  await this.evaluateWithGuard(contextId, script, timeout);
1535
1553
  } catch (error) {
1536
- if (!isMissingExecutionContextReferenceError(error)) {
1554
+ if (!isMissingExecutionContextError(error)) {
1537
1555
  throw error;
1538
1556
  }
1539
1557
  this.contextsByFrame.delete(frameId);
@@ -1557,6 +1575,11 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
1557
1575
  throw error;
1558
1576
  }
1559
1577
  this.contextsByFrame.delete(frameId);
1578
+ const retryDelay = Math.min(
1579
+ TRANSIENT_CONTEXT_RETRY_DELAY_MS,
1580
+ Math.max(0, deadline - Date.now())
1581
+ );
1582
+ await sleep(retryDelay);
1560
1583
  }
1561
1584
  }
1562
1585
  }
@@ -1678,9 +1701,7 @@ async function waitForVisualStabilityAcrossFrames(page, options = {}) {
1678
1701
  frameId,
1679
1702
  remaining,
1680
1703
  settleMs,
1681
- {
1682
- retryTransientContextErrors: false
1683
- }
1704
+ false
1684
1705
  );
1685
1706
  } catch (error) {
1686
1707
  if (isIgnorableFrameError(error)) return;
@@ -1723,7 +1744,7 @@ function isTransientExecutionContextError(error) {
1723
1744
  const message = error.message;
1724
1745
  return message.includes("Execution context was destroyed") || message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context");
1725
1746
  }
1726
- function isMissingExecutionContextReferenceError(error) {
1747
+ function isMissingExecutionContextError(error) {
1727
1748
  if (!(error instanceof Error)) return false;
1728
1749
  const message = error.message;
1729
1750
  return message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context");
@@ -8050,12 +8071,16 @@ function toCloudErrorCode(code) {
8050
8071
 
8051
8072
  // src/cloud/runtime.ts
8052
8073
  var DEFAULT_CLOUD_BASE_URL = "https://remote.opensteer.com";
8053
- var DEFAULT_CLOUD_APP_URL = "https://opensteer.com";
8054
- function createCloudRuntimeState(key, baseUrl = resolveCloudBaseUrl(), authScheme = "api-key", appUrl = resolveCloudAppUrl()) {
8074
+ function createCloudRuntimeState(key, baseUrl = resolveCloudBaseUrl(), authScheme = "api-key") {
8075
+ const normalizedBaseUrl = normalizeCloudBaseUrl(baseUrl);
8055
8076
  return {
8056
- sessionClient: new CloudSessionClient(baseUrl, key, authScheme),
8077
+ sessionClient: new CloudSessionClient(
8078
+ normalizedBaseUrl,
8079
+ key,
8080
+ authScheme
8081
+ ),
8057
8082
  cdpClient: new CloudCdpClient(),
8058
- appUrl: normalizeCloudAppUrl(appUrl),
8083
+ baseUrl: normalizedBaseUrl,
8059
8084
  actionClient: null,
8060
8085
  sessionId: null,
8061
8086
  localRunId: null,
@@ -8065,15 +8090,9 @@ function createCloudRuntimeState(key, baseUrl = resolveCloudBaseUrl(), authSchem
8065
8090
  function resolveCloudBaseUrl() {
8066
8091
  const value = process.env.OPENSTEER_BASE_URL?.trim();
8067
8092
  if (!value) return DEFAULT_CLOUD_BASE_URL;
8068
- return value.replace(/\/+$/, "");
8069
- }
8070
- function resolveCloudAppUrl() {
8071
- const value = process.env.OPENSTEER_APP_URL?.trim();
8072
- if (!value) return DEFAULT_CLOUD_APP_URL;
8073
- return normalizeCloudAppUrl(value);
8093
+ return normalizeCloudBaseUrl(value);
8074
8094
  }
8075
- function normalizeCloudAppUrl(value) {
8076
- if (!value) return null;
8095
+ function normalizeCloudBaseUrl(value) {
8077
8096
  return value.replace(/\/+$/, "");
8078
8097
  }
8079
8098
  function readCloudActionDescription(payload) {
@@ -8131,8 +8150,7 @@ var Opensteer = class _Opensteer {
8131
8150
  this.cloud = createCloudRuntimeState(
8132
8151
  apiKey,
8133
8152
  cloudConfig?.baseUrl,
8134
- cloudConfig?.authScheme,
8135
- cloudConfig?.appUrl
8153
+ cloudConfig?.authScheme
8136
8154
  );
8137
8155
  } else {
8138
8156
  this.cloud = null;
@@ -8324,10 +8342,7 @@ var Opensteer = class _Opensteer {
8324
8342
  this.snapshotCache = null;
8325
8343
  this.cloud.actionClient = actionClient;
8326
8344
  this.cloud.sessionId = sessionId;
8327
- this.cloud.cloudSessionUrl = buildCloudSessionUrl(
8328
- this.cloud.appUrl,
8329
- session3.cloudSession.sessionId
8330
- );
8345
+ this.cloud.cloudSessionUrl = session3.cloudSessionUrl;
8331
8346
  this.announceCloudSession({
8332
8347
  sessionId: session3.sessionId,
8333
8348
  workspaceId: session3.cloudSession.workspaceId,
@@ -10384,12 +10399,6 @@ function buildLocalRunId(namespace) {
10384
10399
  const normalized = namespace.trim() || "default";
10385
10400
  return `${normalized}-${Date.now().toString(36)}-${(0, import_crypto2.randomUUID)().slice(0, 8)}`;
10386
10401
  }
10387
- function buildCloudSessionUrl(appUrl, sessionId) {
10388
- if (!appUrl) {
10389
- return null;
10390
- }
10391
- return `${appUrl}/browser/${encodeURIComponent(sessionId)}`;
10392
- }
10393
10402
 
10394
10403
  // src/cli/paths.ts
10395
10404
  var import_os2 = require("os");
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Opensteer
3
- } from "../chunk-U6YJI5GO.js";
3
+ } from "../chunk-C3NM6XZH.js";
4
4
  import "../chunk-3H5RRIMZ.js";
5
5
 
6
6
  // src/cli/server.ts
package/dist/index.cjs CHANGED
@@ -1111,6 +1111,11 @@ function resolveOpensteerApiKey(env) {
1111
1111
  if (!value) return void 0;
1112
1112
  return value;
1113
1113
  }
1114
+ function resolveOpensteerBaseUrl(env) {
1115
+ const value = env.OPENSTEER_BASE_URL?.trim();
1116
+ if (!value) return void 0;
1117
+ return value;
1118
+ }
1114
1119
  function resolveOpensteerAuthScheme(env) {
1115
1120
  return parseAuthScheme(env.OPENSTEER_AUTH_SCHEME, "OPENSTEER_AUTH_SCHEME");
1116
1121
  }
@@ -1189,6 +1194,7 @@ function resolveConfig(input = {}) {
1189
1194
  const mergedWithEnv = mergeDeep(mergedWithFile, envConfig);
1190
1195
  const resolved = mergeDeep(mergedWithEnv, input);
1191
1196
  const envApiKey = resolveOpensteerApiKey(env);
1197
+ const envBaseUrl = resolveOpensteerBaseUrl(env);
1192
1198
  const envAuthScheme = resolveOpensteerAuthScheme(env);
1193
1199
  const envCloudAnnounce = parseCloudAnnounce(
1194
1200
  env.OPENSTEER_REMOTE_ANNOUNCE,
@@ -1206,6 +1212,9 @@ function resolveConfig(input = {}) {
1206
1212
  const inputHasCloudApiKey = Boolean(
1207
1213
  inputCloudOptions && Object.prototype.hasOwnProperty.call(inputCloudOptions, "apiKey")
1208
1214
  );
1215
+ const inputHasCloudBaseUrl = Boolean(
1216
+ inputCloudOptions && Object.prototype.hasOwnProperty.call(inputCloudOptions, "baseUrl")
1217
+ );
1209
1218
  const cloudSelection = resolveCloudSelection({
1210
1219
  cloud: resolved.cloud
1211
1220
  }, env);
@@ -1225,6 +1234,12 @@ function resolveConfig(input = {}) {
1225
1234
  apiKey: envApiKey
1226
1235
  };
1227
1236
  }
1237
+ if (envBaseUrl && cloudSelection.cloud && !inputHasCloudBaseUrl) {
1238
+ resolved.cloud = {
1239
+ ...normalizeCloudOptions(resolved.cloud) ?? {},
1240
+ baseUrl: envBaseUrl
1241
+ };
1242
+ }
1228
1243
  return resolved;
1229
1244
  }
1230
1245
  function resolveNamespace(config, rootDir) {
@@ -1265,6 +1280,7 @@ function getCallerFilePath() {
1265
1280
  var DEFAULT_TIMEOUT = 3e4;
1266
1281
  var DEFAULT_SETTLE_MS = 750;
1267
1282
  var FRAME_EVALUATE_GRACE_MS = 200;
1283
+ var TRANSIENT_CONTEXT_RETRY_DELAY_MS = 25;
1268
1284
  var STEALTH_WORLD_NAME = "__opensteer_wait__";
1269
1285
  var StealthWaitUnavailableError = class extends Error {
1270
1286
  constructor(cause) {
@@ -1568,9 +1584,12 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
1568
1584
  const frameRecords = await this.getFrameRecords();
1569
1585
  const mainFrame = frameRecords[0];
1570
1586
  if (!mainFrame) return;
1571
- await this.waitForFrameVisualStability(mainFrame.frameId, timeout, settleMs, {
1572
- retryTransientContextErrors: true
1573
- });
1587
+ await this.waitForFrameVisualStability(
1588
+ mainFrame.frameId,
1589
+ timeout,
1590
+ settleMs,
1591
+ true
1592
+ );
1574
1593
  }
1575
1594
  async collectVisibleFrameIds() {
1576
1595
  const frameRecords = await this.getFrameRecords();
@@ -1599,16 +1618,15 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
1599
1618
  }
1600
1619
  return visibleFrameIds;
1601
1620
  }
1602
- async waitForFrameVisualStability(frameId, timeout, settleMs, options = {}) {
1621
+ async waitForFrameVisualStability(frameId, timeout, settleMs, retryTransientContextErrors = true) {
1603
1622
  if (timeout <= 0) return;
1604
1623
  const script = buildStabilityScript(timeout, settleMs);
1605
- const retryTransientContextErrors = options.retryTransientContextErrors ?? true;
1606
1624
  if (!retryTransientContextErrors) {
1607
1625
  let contextId = await this.ensureFrameContextId(frameId);
1608
1626
  try {
1609
1627
  await this.evaluateWithGuard(contextId, script, timeout);
1610
1628
  } catch (error) {
1611
- if (!isMissingExecutionContextReferenceError(error)) {
1629
+ if (!isMissingExecutionContextError(error)) {
1612
1630
  throw error;
1613
1631
  }
1614
1632
  this.contextsByFrame.delete(frameId);
@@ -1632,6 +1650,11 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
1632
1650
  throw error;
1633
1651
  }
1634
1652
  this.contextsByFrame.delete(frameId);
1653
+ const retryDelay = Math.min(
1654
+ TRANSIENT_CONTEXT_RETRY_DELAY_MS,
1655
+ Math.max(0, deadline - Date.now())
1656
+ );
1657
+ await sleep(retryDelay);
1635
1658
  }
1636
1659
  }
1637
1660
  }
@@ -1753,9 +1776,7 @@ async function waitForVisualStabilityAcrossFrames(page, options = {}) {
1753
1776
  frameId,
1754
1777
  remaining,
1755
1778
  settleMs,
1756
- {
1757
- retryTransientContextErrors: false
1758
- }
1779
+ false
1759
1780
  );
1760
1781
  } catch (error) {
1761
1782
  if (isIgnorableFrameError(error)) return;
@@ -1798,7 +1819,7 @@ function isTransientExecutionContextError(error) {
1798
1819
  const message = error.message;
1799
1820
  return message.includes("Execution context was destroyed") || message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context");
1800
1821
  }
1801
- function isMissingExecutionContextReferenceError(error) {
1822
+ function isMissingExecutionContextError(error) {
1802
1823
  if (!(error instanceof Error)) return false;
1803
1824
  const message = error.message;
1804
1825
  return message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context");
@@ -8135,12 +8156,16 @@ function toCloudErrorCode(code) {
8135
8156
 
8136
8157
  // src/cloud/runtime.ts
8137
8158
  var DEFAULT_CLOUD_BASE_URL = "https://remote.opensteer.com";
8138
- var DEFAULT_CLOUD_APP_URL = "https://opensteer.com";
8139
- function createCloudRuntimeState(key, baseUrl = resolveCloudBaseUrl(), authScheme = "api-key", appUrl = resolveCloudAppUrl()) {
8159
+ function createCloudRuntimeState(key, baseUrl = resolveCloudBaseUrl(), authScheme = "api-key") {
8160
+ const normalizedBaseUrl = normalizeCloudBaseUrl(baseUrl);
8140
8161
  return {
8141
- sessionClient: new CloudSessionClient(baseUrl, key, authScheme),
8162
+ sessionClient: new CloudSessionClient(
8163
+ normalizedBaseUrl,
8164
+ key,
8165
+ authScheme
8166
+ ),
8142
8167
  cdpClient: new CloudCdpClient(),
8143
- appUrl: normalizeCloudAppUrl(appUrl),
8168
+ baseUrl: normalizedBaseUrl,
8144
8169
  actionClient: null,
8145
8170
  sessionId: null,
8146
8171
  localRunId: null,
@@ -8150,15 +8175,9 @@ function createCloudRuntimeState(key, baseUrl = resolveCloudBaseUrl(), authSchem
8150
8175
  function resolveCloudBaseUrl() {
8151
8176
  const value = process.env.OPENSTEER_BASE_URL?.trim();
8152
8177
  if (!value) return DEFAULT_CLOUD_BASE_URL;
8153
- return value.replace(/\/+$/, "");
8154
- }
8155
- function resolveCloudAppUrl() {
8156
- const value = process.env.OPENSTEER_APP_URL?.trim();
8157
- if (!value) return DEFAULT_CLOUD_APP_URL;
8158
- return normalizeCloudAppUrl(value);
8178
+ return normalizeCloudBaseUrl(value);
8159
8179
  }
8160
- function normalizeCloudAppUrl(value) {
8161
- if (!value) return null;
8180
+ function normalizeCloudBaseUrl(value) {
8162
8181
  return value.replace(/\/+$/, "");
8163
8182
  }
8164
8183
  function readCloudActionDescription(payload) {
@@ -8216,8 +8235,7 @@ var Opensteer = class _Opensteer {
8216
8235
  this.cloud = createCloudRuntimeState(
8217
8236
  apiKey,
8218
8237
  cloudConfig?.baseUrl,
8219
- cloudConfig?.authScheme,
8220
- cloudConfig?.appUrl
8238
+ cloudConfig?.authScheme
8221
8239
  );
8222
8240
  } else {
8223
8241
  this.cloud = null;
@@ -8409,10 +8427,7 @@ var Opensteer = class _Opensteer {
8409
8427
  this.snapshotCache = null;
8410
8428
  this.cloud.actionClient = actionClient;
8411
8429
  this.cloud.sessionId = sessionId;
8412
- this.cloud.cloudSessionUrl = buildCloudSessionUrl(
8413
- this.cloud.appUrl,
8414
- session2.cloudSession.sessionId
8415
- );
8430
+ this.cloud.cloudSessionUrl = session2.cloudSessionUrl;
8416
8431
  this.announceCloudSession({
8417
8432
  sessionId: session2.sessionId,
8418
8433
  workspaceId: session2.cloudSession.workspaceId,
@@ -10469,12 +10484,6 @@ function buildLocalRunId(namespace) {
10469
10484
  const normalized = namespace.trim() || "default";
10470
10485
  return `${normalized}-${Date.now().toString(36)}-${(0, import_crypto2.randomUUID)().slice(0, 8)}`;
10471
10486
  }
10472
- function buildCloudSessionUrl(appUrl, sessionId) {
10473
- if (!appUrl) {
10474
- return null;
10475
- }
10476
- return `${appUrl}/browser/${encodeURIComponent(sessionId)}`;
10477
- }
10478
10487
 
10479
10488
  // src/ai/index.ts
10480
10489
  init_resolver();
package/dist/index.d.cts CHANGED
@@ -127,7 +127,6 @@ type OpensteerCloudAnnouncePolicy = 'always' | 'off' | 'tty';
127
127
  interface OpensteerCloudOptions {
128
128
  apiKey?: string;
129
129
  baseUrl?: string;
130
- appUrl?: string;
131
130
  authScheme?: OpensteerAuthScheme;
132
131
  announce?: OpensteerCloudAnnouncePolicy;
133
132
  }
package/dist/index.d.ts CHANGED
@@ -127,7 +127,6 @@ type OpensteerCloudAnnouncePolicy = 'always' | 'off' | 'tty';
127
127
  interface OpensteerCloudOptions {
128
128
  apiKey?: string;
129
129
  baseUrl?: string;
130
- appUrl?: string;
131
130
  authScheme?: OpensteerAuthScheme;
132
131
  announce?: OpensteerCloudAnnouncePolicy;
133
132
  }
package/dist/index.js CHANGED
@@ -68,7 +68,7 @@ import {
68
68
  switchTab,
69
69
  typeText,
70
70
  waitForVisualStability
71
- } from "./chunk-U6YJI5GO.js";
71
+ } from "./chunk-C3NM6XZH.js";
72
72
  import {
73
73
  createResolveCallback
74
74
  } from "./chunk-SRJLH34D.js";
package/package.json CHANGED
@@ -1,82 +1,75 @@
1
1
  {
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"
2
+ "name": "opensteer",
3
+ "version": "0.4.11",
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"
81
18
  }
82
- }
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
+ }