opensteer 0.9.4 → 0.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/{chunk-ZRF7WMS3.js → chunk-3I3A5OLB.js} +3 -3
  2. package/dist/{chunk-ZRF7WMS3.js.map → chunk-3I3A5OLB.js.map} +1 -1
  3. package/dist/{chunk-GSCQQKZZ.js → chunk-3XBQRZZC.js} +221 -6
  4. package/dist/chunk-3XBQRZZC.js.map +1 -0
  5. package/dist/{chunk-GEUHKPC2.js → chunk-BVRIPCWA.js} +878 -572
  6. package/dist/chunk-BVRIPCWA.js.map +1 -0
  7. package/dist/chunk-L4NF74KI.js +458 -0
  8. package/dist/chunk-L4NF74KI.js.map +1 -0
  9. package/dist/cli/bin.cjs +1313 -494
  10. package/dist/cli/bin.cjs.map +1 -1
  11. package/dist/cli/bin.js +235 -108
  12. package/dist/cli/bin.js.map +1 -1
  13. package/dist/index.cjs +1152 -647
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +37 -460
  16. package/dist/index.d.ts +37 -460
  17. package/dist/index.js +3 -4
  18. package/dist/local-view/serve-entry.cjs +5354 -4
  19. package/dist/local-view/serve-entry.cjs.map +1 -1
  20. package/dist/local-view/serve-entry.js +1 -1
  21. package/dist/opensteer-UGA6YBRN.js +6 -0
  22. package/dist/{opensteer-PJI7VUIT.js.map → opensteer-UGA6YBRN.js.map} +1 -1
  23. package/dist/{session-control-M3JD7ZKA.js → session-control-U3L5H2ZI.js} +3 -3
  24. package/dist/{session-control-M3JD7ZKA.js.map → session-control-U3L5H2ZI.js.map} +1 -1
  25. package/package.json +7 -7
  26. package/skills/opensteer/SKILL.md +134 -94
  27. package/dist/chunk-GEUHKPC2.js.map +0 -1
  28. package/dist/chunk-GSCQQKZZ.js.map +0 -1
  29. package/dist/chunk-HQCMXRBE.js +0 -335
  30. package/dist/chunk-HQCMXRBE.js.map +0 -1
  31. package/dist/chunk-KCINASQC.js +0 -3
  32. package/dist/chunk-KCINASQC.js.map +0 -1
  33. package/dist/opensteer-PJI7VUIT.js +0 -6
package/dist/cli/bin.cjs CHANGED
@@ -2598,10 +2598,8 @@ var init_cdp_dom_snapshot = __esm({
2598
2598
  });
2599
2599
 
2600
2600
  // ../browser-core/src/cdp-visual-stability.ts
2601
- var DEFAULT_VISUAL_STABILITY_SETTLE_MS;
2602
2601
  var init_cdp_visual_stability = __esm({
2603
2602
  "../browser-core/src/cdp-visual-stability.ts"() {
2604
- DEFAULT_VISUAL_STABILITY_SETTLE_MS = 750;
2605
2603
  }
2606
2604
  });
2607
2605
 
@@ -6679,7 +6677,7 @@ function assertValidSemanticOperationInput(name, input) {
6679
6677
  }
6680
6678
  );
6681
6679
  }
6682
- var opensteerComputerAnnotationNames, opensteerExposedSemanticOperationNames, opensteerPackageRunnableSemanticOperationNames, snapshotModeSchema, viewportSchema, opensteerBrowserLaunchOptionsSchema, attachBrowserOptionsSchema, opensteerBrowserOptionsSchema, opensteerBrowserContextOptionsSchema, targetByElementSchema2, targetByPersistSchema2, targetBySelectorSchema2, opensteerTargetInputSchema, opensteerResolvedTargetSchema, opensteerActionResultSchema, opensteerSnapshotCounterSchema, opensteerSessionStateSchema, opensteerOpenInputSchema, opensteerPageListInputSchema, opensteerPageListOutputSchema, opensteerPageNewInputSchema, opensteerPageActivateInputSchema, opensteerPageCloseInputSchema, opensteerPageCloseOutputSchema, opensteerPageGotoInputSchema, opensteerPageEvaluateInputSchema, opensteerPageEvaluateOutputSchema, opensteerAddInitScriptInputSchema, opensteerAddInitScriptOutputSchema, opensteerCapturedScriptSchema, opensteerCaptureScriptsInputSchema, opensteerCaptureScriptsOutputSchema, opensteerPageSnapshotInputSchema, opensteerPageSnapshotOutputSchema, opensteerComputerMouseButtonSchema, opensteerComputerKeyModifierSchema, opensteerDomClickInputSchema, opensteerDomHoverInputSchema, opensteerDomInputInputSchema, opensteerDomScrollInputSchema, opensteerExtractSchemaSchema, opensteerDomExtractInputSchema, jsonValueSchema2, opensteerDomExtractOutputSchema, opensteerSessionCloseInputSchema, opensteerSessionCloseOutputSchema, opensteerComputerAnnotationSchema, opensteerComputerClickActionSchema, opensteerComputerMoveActionSchema, opensteerComputerScrollActionSchema, opensteerComputerTypeActionSchema, opensteerComputerKeyActionSchema, opensteerComputerDragActionSchema, opensteerComputerScreenshotActionSchema, opensteerComputerWaitActionSchema, opensteerComputerActionSchema, opensteerComputerScreenshotOptionsSchema, opensteerComputerExecuteInputSchema, opensteerComputerTracePointSchema, opensteerComputerTraceEnrichmentSchema, opensteerComputerExecuteTimingSchema, opensteerComputerDisplayScaleSchema, opensteerComputerExecuteOutputSchema, opensteerSemanticOperationSpecificationsBase, exposedSemanticOperationNameSet, opensteerSemanticOperationSpecificationsInternal, opensteerSemanticOperationSpecifications, opensteerSemanticOperationSpecificationMap, semanticRestBasePath, opensteerSemanticRestEndpoints;
6680
+ var opensteerComputerAnnotationNames, opensteerExposedSemanticOperationNames, opensteerPackageRunnableSemanticOperationNames, snapshotModeSchema, viewportSchema, opensteerBrowserLaunchOptionsSchema, attachBrowserOptionsSchema, opensteerBrowserOptionsSchema, opensteerBrowserContextOptionsSchema, targetByElementSchema2, targetByPersistSchema2, targetBySelectorSchema2, opensteerTargetInputSchema, opensteerResolvedTargetSchema, opensteerActionResultSchema, opensteerSnapshotCounterSchema, opensteerSessionStateSchema, opensteerOpenInputSchema, opensteerPageListInputSchema, opensteerPageListOutputSchema, opensteerPageNewInputSchema, opensteerPageActivateInputSchema, opensteerPageCloseInputSchema, opensteerPageCloseOutputSchema, opensteerPageGotoInputSchema, opensteerPageEvaluateInputSchema, opensteerPageEvaluateOutputSchema, opensteerAddInitScriptInputSchema, opensteerAddInitScriptOutputSchema, opensteerCapturedScriptSchema, opensteerCaptureScriptsInputSchema, opensteerCaptureScriptsOutputSchema, opensteerPageSnapshotInputSchema, opensteerPageSnapshotOutputSchema, opensteerComputerMouseButtonSchema, opensteerComputerKeyModifierSchema, opensteerDomClickInputSchema, opensteerDomHoverInputSchema, opensteerDomInputInputSchema, opensteerDomScrollInputSchema, opensteerExtractTemplateSchema, opensteerDomExtractInputSchema, jsonValueSchema2, opensteerDomExtractOutputSchema, opensteerSessionCloseInputSchema, opensteerSessionCloseOutputSchema, opensteerComputerAnnotationSchema, opensteerComputerClickActionSchema, opensteerComputerMoveActionSchema, opensteerComputerScrollActionSchema, opensteerComputerTypeActionSchema, opensteerComputerKeyActionSchema, opensteerComputerDragActionSchema, opensteerComputerScreenshotActionSchema, opensteerComputerWaitActionSchema, opensteerComputerActionSchema, opensteerComputerScreenshotOptionsSchema, opensteerComputerExecuteInputSchema, opensteerComputerTracePointSchema, opensteerComputerTraceEnrichmentSchema, opensteerComputerExecuteTimingSchema, opensteerComputerDisplayScaleSchema, opensteerComputerExecuteOutputSchema, opensteerSemanticOperationSpecificationsBase, exposedSemanticOperationNameSet, opensteerSemanticOperationSpecificationsInternal, opensteerSemanticOperationSpecifications, opensteerSemanticOperationSpecificationMap, semanticRestBasePath, opensteerSemanticRestEndpoints;
6683
6681
  var init_semantic = __esm({
6684
6682
  "../protocol/src/semantic.ts"() {
6685
6683
  init_json2();
@@ -7261,10 +7259,10 @@ var init_semantic = __esm({
7261
7259
  required: ["target", "direction", "amount"]
7262
7260
  }
7263
7261
  );
7264
- opensteerExtractSchemaSchema = objectSchema(
7262
+ opensteerExtractTemplateSchema = objectSchema(
7265
7263
  {},
7266
7264
  {
7267
- title: "OpensteerExtractSchema",
7265
+ title: "OpensteerExtractTemplate",
7268
7266
  additionalProperties: true
7269
7267
  }
7270
7268
  );
@@ -7272,13 +7270,13 @@ var init_semantic = __esm({
7272
7270
  ...objectSchema(
7273
7271
  {
7274
7272
  persist: stringSchema(),
7275
- schema: opensteerExtractSchemaSchema
7273
+ template: opensteerExtractTemplateSchema
7276
7274
  },
7277
7275
  {
7278
7276
  title: "OpensteerDomExtractInput"
7279
7277
  }
7280
7278
  ),
7281
- anyOf: [defineSchema({ required: ["persist"] }), defineSchema({ required: ["schema"] })]
7279
+ anyOf: [defineSchema({ required: ["persist"] }), defineSchema({ required: ["template"] })]
7282
7280
  });
7283
7281
  jsonValueSchema2 = recordSchema({}, { title: "JsonValueRecord" });
7284
7282
  opensteerDomExtractOutputSchema = objectSchema(
@@ -10413,7 +10411,8 @@ function normalizeOpensteerEngineName(value, source = "engine") {
10413
10411
  if (normalized === "playwright" || normalized === "abp") {
10414
10412
  return normalized;
10415
10413
  }
10416
- throw new Error(
10414
+ throw new OpensteerProtocolError(
10415
+ "invalid-argument",
10417
10416
  `${source} must be one of ${OPENSTEER_ENGINE_NAMES.join(", ")}; received "${value}".`
10418
10417
  );
10419
10418
  }
@@ -10422,7 +10421,8 @@ function assertSupportedEngineOptions(input) {
10422
10421
  return;
10423
10422
  }
10424
10423
  if (typeof input.browser === "object" && input.browser !== null && input.browser.mode === "attach") {
10425
- throw new Error(
10424
+ throw new OpensteerProtocolError(
10425
+ "invalid-argument",
10426
10426
  'ABP engine does not support browser.mode="attach". Use the Playwright engine for attach flows.'
10427
10427
  );
10428
10428
  }
@@ -10430,7 +10430,8 @@ function assertSupportedEngineOptions(input) {
10430
10430
  if (unsupportedContextOptionNames.length === 0) {
10431
10431
  return;
10432
10432
  }
10433
- throw new Error(
10433
+ throw new OpensteerProtocolError(
10434
+ "invalid-argument",
10434
10435
  `ABP engine does not support ${unsupportedContextOptionNames.join(", ")}. Supported ABP context options: context.viewport.`
10435
10436
  );
10436
10437
  }
@@ -10488,6 +10489,7 @@ function stripWindowSizeArgs(args) {
10488
10489
  var OPENSTEER_ENGINE_NAMES, DEFAULT_OPENSTEER_ENGINE;
10489
10490
  var init_engine_selection = __esm({
10490
10491
  "../runtime-core/src/internal/engine-selection.ts"() {
10492
+ init_src2();
10491
10493
  OPENSTEER_ENGINE_NAMES = ["playwright", "abp"];
10492
10494
  DEFAULT_OPENSTEER_ENGINE = "playwright";
10493
10495
  }
@@ -11472,7 +11474,8 @@ function assertProviderSupportsEngine(provider, engine) {
11472
11474
  return;
11473
11475
  }
11474
11476
  if (provider === "cloud") {
11475
- throw new Error(
11477
+ throw new OpensteerProtocolError(
11478
+ "invalid-argument",
11476
11479
  "ABP is not supported for provider=cloud. Cloud provider currently requires Playwright."
11477
11480
  );
11478
11481
  }
@@ -11482,7 +11485,8 @@ function normalizeOpensteerProviderMode(value, source = "OPENSTEER_PROVIDER") {
11482
11485
  if (normalized === OPENSTEER_PROVIDER_MODES[0] || normalized === OPENSTEER_PROVIDER_MODES[1]) {
11483
11486
  return normalized;
11484
11487
  }
11485
- throw new Error(
11488
+ throw new OpensteerProtocolError(
11489
+ "invalid-argument",
11486
11490
  `${source} must be one of ${OPENSTEER_PROVIDER_MODES.join(", ")}; received "${value}".`
11487
11491
  );
11488
11492
  }
@@ -11507,6 +11511,7 @@ function resolveOpensteerProvider(input = {}) {
11507
11511
  var OPENSTEER_PROVIDER_MODES;
11508
11512
  var init_config = __esm({
11509
11513
  "src/provider/config.ts"() {
11514
+ init_src2();
11510
11515
  OPENSTEER_PROVIDER_MODES = ["local", "cloud"];
11511
11516
  }
11512
11517
  });
@@ -11968,12 +11973,60 @@ function wrapCloudFetchError(error, input) {
11968
11973
  wrapped.name = error.name;
11969
11974
  return wrapped;
11970
11975
  }
11971
- var CLOUD_CLOSE_TIMEOUT_MS, CLOUD_CLOSE_POLL_INTERVAL_MS, OpensteerCloudClient;
11976
+ async function createCloudRequestError(response, input) {
11977
+ const payload = await readCloudErrorPayload(response);
11978
+ return new OpensteerCloudRequestError({
11979
+ statusCode: response.status,
11980
+ method: input.method,
11981
+ pathname: input.pathname,
11982
+ url: input.url,
11983
+ message: payload?.error ?? `${input.method} ${input.pathname} failed with ${String(response.status)}.`,
11984
+ ...payload?.code === void 0 ? {} : { code: payload.code },
11985
+ ...payload?.details === void 0 ? {} : { details: payload.details }
11986
+ });
11987
+ }
11988
+ async function readCloudErrorPayload(response) {
11989
+ try {
11990
+ return asCloudErrorPayload(await response.json());
11991
+ } catch {
11992
+ return void 0;
11993
+ }
11994
+ }
11995
+ function asCloudErrorPayload(value) {
11996
+ if (value === null || typeof value !== "object") {
11997
+ return void 0;
11998
+ }
11999
+ const payload = value;
12000
+ return {
12001
+ ...typeof payload.error === "string" ? { error: payload.error } : {},
12002
+ ...typeof payload.code === "string" ? { code: payload.code } : {},
12003
+ ..."details" in payload ? { details: payload.details } : {}
12004
+ };
12005
+ }
12006
+ var CLOUD_CLOSE_TIMEOUT_MS, CLOUD_CLOSE_POLL_INTERVAL_MS, OpensteerCloudRequestError, OpensteerCloudClient;
11972
12007
  var init_client = __esm({
11973
12008
  "src/cloud/client.ts"() {
11974
12009
  init_profile_sync();
11975
12010
  CLOUD_CLOSE_TIMEOUT_MS = 6e4;
11976
12011
  CLOUD_CLOSE_POLL_INTERVAL_MS = 250;
12012
+ OpensteerCloudRequestError = class extends Error {
12013
+ statusCode;
12014
+ code;
12015
+ details;
12016
+ method;
12017
+ pathname;
12018
+ url;
12019
+ constructor(args) {
12020
+ super(args.message);
12021
+ this.name = "OpensteerCloudRequestError";
12022
+ this.statusCode = args.statusCode;
12023
+ this.code = args.code;
12024
+ this.details = args.details;
12025
+ this.method = args.method;
12026
+ this.pathname = args.pathname;
12027
+ this.url = args.url;
12028
+ }
12029
+ };
11977
12030
  OpensteerCloudClient = class {
11978
12031
  constructor(config) {
11979
12032
  this.config = config;
@@ -12149,7 +12202,11 @@ var init_client = __esm({
12149
12202
  });
12150
12203
  }
12151
12204
  if (!response.ok) {
12152
- throw new Error(`${init.method} ${pathname} failed with ${String(response.status)}.`);
12205
+ throw await createCloudRequestError(response, {
12206
+ method: init.method,
12207
+ pathname,
12208
+ url
12209
+ });
12153
12210
  }
12154
12211
  return response;
12155
12212
  }
@@ -12220,7 +12277,7 @@ var init_package = __esm({
12220
12277
  "../runtime-core/package.json"() {
12221
12278
  package_default2 = {
12222
12279
  name: "@opensteer/runtime-core",
12223
- version: "0.2.3",
12280
+ version: "0.2.5",
12224
12281
  description: "Shared semantic runtime for Opensteer local and cloud execution.",
12225
12282
  license: "MIT",
12226
12283
  type: "module",
@@ -12372,7 +12429,7 @@ var init_errors3 = __esm({
12372
12429
  function defaultPolicy() {
12373
12430
  return DEFAULT_POLICY;
12374
12431
  }
12375
- var DEFAULT_TIMEOUTS, DEFAULT_SETTLE_DELAYS, defaultSnapshotSettleObserver, DOM_ACTION_VISUAL_STABILITY_PROFILES, DEFAULT_DOM_ACTION_VISUAL_STABILITY_PROFILE, NAVIGATION_VISUAL_STABILITY_PROFILE, NAVIGATION_POST_LOAD_CAPTURE_WINDOW_MS, defaultDomActionSettleObserver, defaultNavigationSettleObserver, DEFAULT_SETTLE_OBSERVERS, defaultTimeoutPolicy, defaultSettlePolicy, defaultRetryPolicy, defaultFallbackPolicy, DEFAULT_POLICY;
12432
+ var DEFAULT_TIMEOUTS, DEFAULT_SETTLE_DELAYS, DOM_ACTION_VISUAL_STABILITY_PROFILES, DEFAULT_DOM_ACTION_VISUAL_STABILITY_PROFILE, NAVIGATION_VISUAL_STABILITY_PROFILE, NAVIGATION_POST_LOAD_CAPTURE_WINDOW_MS, defaultDomActionSettleObserver, defaultNavigationSettleObserver, DEFAULT_SETTLE_OBSERVERS, defaultTimeoutPolicy, defaultSettlePolicy, defaultRetryPolicy, defaultFallbackPolicy, DEFAULT_POLICY;
12376
12433
  var init_defaults = __esm({
12377
12434
  "../runtime-core/src/policy/defaults.ts"() {
12378
12435
  init_src();
@@ -12401,21 +12458,6 @@ var init_defaults = __esm({
12401
12458
  "dom-action": 100,
12402
12459
  snapshot: 0
12403
12460
  };
12404
- defaultSnapshotSettleObserver = {
12405
- async settle(input) {
12406
- if (input.trigger !== "snapshot") {
12407
- return false;
12408
- }
12409
- await input.engine.waitForVisualStability({
12410
- pageRef: input.pageRef,
12411
- ...input.remainingMs === void 0 ? {} : { timeoutMs: input.remainingMs },
12412
- settleMs: DEFAULT_VISUAL_STABILITY_SETTLE_MS,
12413
- scope: "visible-frames"
12414
- });
12415
- return true;
12416
- }
12417
- };
12418
- Object.freeze(defaultSnapshotSettleObserver);
12419
12461
  DOM_ACTION_VISUAL_STABILITY_PROFILES = {
12420
12462
  "dom.click": { settleMs: 750, scope: "visible-frames", timeoutMs: 7e3 },
12421
12463
  "dom.input": { settleMs: 750, scope: "visible-frames", timeoutMs: 7e3 },
@@ -12468,6 +12510,7 @@ var init_defaults = __esm({
12468
12510
  return false;
12469
12511
  }
12470
12512
  try {
12513
+ const startedAt = Date.now();
12471
12514
  await input.engine.waitForPostLoadQuiet({
12472
12515
  pageRef: input.pageRef,
12473
12516
  timeoutMs: effectiveTimeout,
@@ -12475,9 +12518,13 @@ var init_defaults = __esm({
12475
12518
  captureWindowMs: Math.min(NAVIGATION_POST_LOAD_CAPTURE_WINDOW_MS, effectiveTimeout),
12476
12519
  signal: input.signal
12477
12520
  });
12521
+ const visualTimeout = Math.max(0, effectiveTimeout - (Date.now() - startedAt));
12522
+ if (visualTimeout <= 0) {
12523
+ return true;
12524
+ }
12478
12525
  await input.engine.waitForVisualStability({
12479
12526
  pageRef: input.pageRef,
12480
- timeoutMs: effectiveTimeout,
12527
+ timeoutMs: visualTimeout,
12481
12528
  settleMs: profile.settleMs,
12482
12529
  scope: profile.scope
12483
12530
  });
@@ -12489,7 +12536,6 @@ var init_defaults = __esm({
12489
12536
  };
12490
12537
  Object.freeze(defaultNavigationSettleObserver);
12491
12538
  DEFAULT_SETTLE_OBSERVERS = Object.freeze([
12492
- defaultSnapshotSettleObserver,
12493
12539
  defaultDomActionSettleObserver,
12494
12540
  defaultNavigationSettleObserver
12495
12541
  ]);
@@ -12977,6 +13023,9 @@ function buildClauseSelector(node, clause) {
12977
13023
  if (!clause || typeof clause !== "object") {
12978
13024
  return "";
12979
13025
  }
13026
+ if (clause.kind === "text") {
13027
+ return "";
13028
+ }
12980
13029
  if (clause.kind === "position") {
12981
13030
  if (clause.axis === "nthOfType") {
12982
13031
  return `:nth-of-type(${Math.max(1, Number(node.position?.nthOfType || 1))})`;
@@ -13092,7 +13141,7 @@ function resolveExtractedValueInContext(normalizedValue, options) {
13092
13141
  function stripPositionClauses(nodes) {
13093
13142
  return (nodes || []).map((node) => ({
13094
13143
  ...node,
13095
- match: (node.match || []).filter((clause) => clause.kind !== "position")
13144
+ match: (node.match || []).filter((clause) => clause.kind !== "position" && clause.kind !== "text")
13096
13145
  }));
13097
13146
  }
13098
13147
  function dedupeSelectors(selectors) {
@@ -13680,9 +13729,17 @@ function resolveDomPathInScope(index, domPath, scope) {
13680
13729
  if (!candidates.length) {
13681
13730
  return null;
13682
13731
  }
13732
+ const lastNode = domPath[domPath.length - 1];
13733
+ const textClauses = lastNode?.match.filter((c) => c.kind === "text") ?? [];
13683
13734
  let fallback = null;
13684
13735
  for (const selector of candidates) {
13685
- const matches = querySelectorAllInScope(index, selector, scope);
13736
+ let matches = querySelectorAllInScope(index, selector, scope);
13737
+ if (textClauses.length > 0 && matches.length > 1) {
13738
+ const filtered = matches.filter((node) => matchesTextClauses(node, textClauses));
13739
+ if (filtered.length > 0) {
13740
+ matches = filtered;
13741
+ }
13742
+ }
13686
13743
  if (matches.length === 1) {
13687
13744
  return {
13688
13745
  node: matches[0],
@@ -13702,6 +13759,10 @@ function resolveDomPathInScope(index, domPath, scope) {
13702
13759
  }
13703
13760
  return fallback;
13704
13761
  }
13762
+ function matchesTextClauses(node, clauses) {
13763
+ const text = (node.textContent ?? "").replace(/\s+/g, " ").trim();
13764
+ return clauses.every((clause) => text.includes(clause.value));
13765
+ }
13705
13766
  function queryAllDomPathInScope(index, domPath, scope) {
13706
13767
  const selectors = buildPathCandidates(domPath);
13707
13768
  for (const selector of selectors) {
@@ -13860,7 +13921,13 @@ function clonePathNode(node) {
13860
13921
  };
13861
13922
  }
13862
13923
  function cloneMatchClause(clause) {
13863
- return clause.kind === "position" ? { kind: "position", axis: clause.axis } : {
13924
+ if (clause.kind === "position") {
13925
+ return { kind: "position", axis: clause.axis };
13926
+ }
13927
+ if (clause.kind === "text") {
13928
+ return { kind: "text", value: clause.value };
13929
+ }
13930
+ return {
13864
13931
  kind: "attr",
13865
13932
  key: clause.key,
13866
13933
  ...clause.op === void 0 ? {} : { op: clause.op },
@@ -13915,6 +13982,13 @@ function normalizeMatch(rawMatch, attrs, position, tag) {
13915
13982
  op,
13916
13983
  ...value === void 0 ? {} : { value }
13917
13984
  });
13985
+ continue;
13986
+ }
13987
+ if (record.kind === "text") {
13988
+ const textValue = typeof record.value === "string" ? record.value.trim() : "";
13989
+ if (textValue) {
13990
+ push({ kind: "text", value: textValue.slice(0, 80) });
13991
+ }
13918
13992
  }
13919
13993
  }
13920
13994
  }
@@ -14329,7 +14403,7 @@ var init_executor = __esm({
14329
14403
  init_action_boundary2();
14330
14404
  init_policy();
14331
14405
  init_bridge();
14332
- MAX_DOM_ACTION_ATTEMPTS = 3;
14406
+ MAX_DOM_ACTION_ATTEMPTS = 2;
14333
14407
  DEFAULT_SCROLL_OPTIONS = {
14334
14408
  block: "center",
14335
14409
  inline: "center"
@@ -14816,11 +14890,12 @@ var init_executor = __esm({
14816
14890
  throw this.createActionabilityError(
14817
14891
  operation,
14818
14892
  "obscured",
14819
- `hit test resolved ${hit.nodeRef} outside the target subtree rooted at ${resolved.nodeRef}`,
14893
+ `target is obscured by ${assessment.blockingDescription ?? "another element"} at the click point`,
14820
14894
  {
14821
14895
  ...details,
14822
14896
  hitRelation: assessment.relation,
14823
14897
  ...assessment.ambiguous === void 0 ? {} : { hitAmbiguous: assessment.ambiguous },
14898
+ ...assessment.blockingDescription === void 0 ? {} : { blockingDescription: assessment.blockingDescription },
14824
14899
  ...assessment.canonicalTarget === void 0 ? {} : {
14825
14900
  canonicalNodeRef: assessment.canonicalTarget.nodeRef,
14826
14901
  canonicalDocumentRef: assessment.canonicalTarget.documentRef,
@@ -14834,8 +14909,7 @@ var init_executor = __esm({
14834
14909
  hitMissingFromSnapshot: !resolved.snapshot.nodes.some(
14835
14910
  (node) => node.nodeRef === hit.nodeRef
14836
14911
  )
14837
- },
14838
- true
14912
+ }
14839
14913
  );
14840
14914
  }
14841
14915
  async resolveActionablePointerTarget(session, operation, resolved) {
@@ -15026,7 +15100,12 @@ var init_runtime = __esm({
15026
15100
  });
15027
15101
  }
15028
15102
  async buildPath(input) {
15029
- return sanitizeReplayElementPath(await this.requireBridge().buildReplayPath(input.locator));
15103
+ return sanitizeReplayElementPath(
15104
+ await this.requireBridge().buildReplayPath(
15105
+ input.locator,
15106
+ input.enableTextMatch ? { enableTextMatch: true } : void 0
15107
+ )
15108
+ );
15030
15109
  }
15031
15110
  async resolveTarget(input) {
15032
15111
  return this.withSnapshotSession((session) => this.resolveTargetWithSession(session, input));
@@ -15246,7 +15325,7 @@ var init_runtime = __esm({
15246
15325
  if (resolvedByLocator) {
15247
15326
  const { snapshot, node } = resolvedByLocator;
15248
15327
  const anchor = await this.buildAnchorFromSnapshotNode(session, snapshot, node);
15249
- const replayPath = await this.tryBuildPathFromNode(snapshot, node);
15328
+ const replayPath = await this.tryBuildPathFromNode(snapshot, node, { enableTextMatch: true });
15250
15329
  return this.createResolvedTarget("live", snapshot, node, anchor, {
15251
15330
  ...target.persist === void 0 ? {} : { persist: target.persist },
15252
15331
  ...replayPath === void 0 ? {} : { replayPath }
@@ -15264,11 +15343,12 @@ var init_runtime = __esm({
15264
15343
  const { snapshot, node } = resolved;
15265
15344
  const anchor = await this.buildAnchorFromSnapshotNode(session, snapshot, node);
15266
15345
  const writeDescriptor = descriptorWriter ?? ((input) => this.descriptors.write(input));
15267
- const replayPath = await this.tryBuildPathFromNode(snapshot, node);
15346
+ const enableTextMatch = method !== "extract";
15347
+ const replayPath = await this.tryBuildPathFromNode(snapshot, node, { enableTextMatch });
15268
15348
  const descriptor = target.persist === void 0 ? void 0 : await writeDescriptor({
15269
15349
  method,
15270
15350
  persist: target.persist,
15271
- path: replayPath ?? await this.buildPathForNode(snapshot, node),
15351
+ path: replayPath ?? await this.buildPathForNode(snapshot, node, { enableTextMatch }),
15272
15352
  sourceUrl: snapshot.url
15273
15353
  });
15274
15354
  return this.createResolvedTarget("selector", snapshot, node, anchor, {
@@ -15368,7 +15448,7 @@ var init_runtime = __esm({
15368
15448
  `Unable to resolve structural anchor "${buildPathSelectorHint(anchor)}" in the current session`
15369
15449
  );
15370
15450
  }
15371
- const replayPath = await this.tryBuildPathFromNode(context.snapshot, target.node);
15451
+ const replayPath = await this.tryBuildPathFromNode(context.snapshot, target.node, { enableTextMatch: true });
15372
15452
  return this.createResolvedTarget(source, context.snapshot, target.node, anchor, {
15373
15453
  ...persist === void 0 ? {} : { persist },
15374
15454
  ...replayPath === void 0 ? {} : { replayPath }
@@ -15539,19 +15619,20 @@ var init_runtime = __esm({
15539
15619
  }
15540
15620
  return this.bridge;
15541
15621
  }
15542
- async buildPathForNode(snapshot, node) {
15622
+ async buildPathForNode(snapshot, node, options) {
15543
15623
  if (node.nodeRef === void 0) {
15544
15624
  throw new Error(
15545
15625
  `snapshot node ${String(node.snapshotNodeId)} does not expose a live node reference`
15546
15626
  );
15547
15627
  }
15548
15628
  return this.buildPath({
15549
- locator: createNodeLocator(snapshot.documentRef, snapshot.documentEpoch, node.nodeRef)
15629
+ locator: createNodeLocator(snapshot.documentRef, snapshot.documentEpoch, node.nodeRef),
15630
+ ...options?.enableTextMatch ? { enableTextMatch: true } : {}
15550
15631
  });
15551
15632
  }
15552
- async tryBuildPathFromNode(snapshot, node) {
15633
+ async tryBuildPathFromNode(snapshot, node, options) {
15553
15634
  try {
15554
- return await this.buildPathForNode(snapshot, node);
15635
+ return await this.buildPathForNode(snapshot, node, options);
15555
15636
  } catch {
15556
15637
  return void 0;
15557
15638
  }
@@ -17121,6 +17202,9 @@ function relaxPathForSingleSample(path24, mode) {
17121
17202
  }
17122
17203
  return !isLast;
17123
17204
  }
17205
+ if (clause.kind === "text") {
17206
+ return false;
17207
+ }
17124
17208
  const key = String(clause.key || "").trim().toLowerCase();
17125
17209
  if (!key || !shouldKeepAttrForSingleSample(key)) {
17126
17210
  return false;
@@ -17223,9 +17307,11 @@ function buildNodeStructure(node) {
17223
17307
  }
17224
17308
  structuralAttrs[key] = value;
17225
17309
  }
17226
- const matchClauses = (node.match || []).map(
17227
- (clause) => clause.kind === "position" ? `position:${clause.axis}` : `attr:${String(clause.key || "").trim().toLowerCase()}`
17228
- ).sort();
17310
+ const matchClauses = (node.match || []).map((clause) => {
17311
+ if (clause.kind === "position") return `position:${clause.axis}`;
17312
+ if (clause.kind === "text") return `text:${clause.value}`;
17313
+ return `attr:${String(clause.key || "").trim().toLowerCase()}`;
17314
+ }).sort();
17229
17315
  return {
17230
17316
  tag,
17231
17317
  attrs: structuralAttrs,
@@ -17631,6 +17717,9 @@ function mergeMatchByMajority(matchLists, attrs, threshold, positionFlags = {
17631
17717
  });
17632
17718
  continue;
17633
17719
  }
17720
+ if (clause.kind === "text") {
17721
+ continue;
17722
+ }
17634
17723
  if (clause.axis === "nthOfType") {
17635
17724
  if (positionFlags.hasNthOfType) {
17636
17725
  merged.push({ kind: "position", axis: "nthOfType" });
@@ -17809,9 +17898,9 @@ var init_extraction_consolidation = __esm({
17809
17898
  CLUSTER_FALLBACK_PREFIX = "variant";
17810
17899
  }
17811
17900
  });
17812
- function assertValidOpensteerExtractionSchemaRoot(schema) {
17813
- if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
17814
- throw new Error("Invalid extraction schema: expected a JSON object at the top level.");
17901
+ function assertValidOpensteerExtractionTemplateRoot(template) {
17902
+ if (!template || typeof template !== "object" || Array.isArray(template)) {
17903
+ throw new Error("Invalid extraction template: expected a JSON object at the top level.");
17815
17904
  }
17816
17905
  }
17817
17906
  function isPersistedOpensteerExtractionValueNode2(value) {
@@ -17833,12 +17922,12 @@ function isPersistedOpensteerExtractionArrayNode2(value) {
17833
17922
  return "$array" in value;
17834
17923
  }
17835
17924
  async function compileOpensteerExtractionFieldTargets(options) {
17836
- assertValidOpensteerExtractionSchemaRoot(options.schema);
17925
+ assertValidOpensteerExtractionTemplateRoot(options.template);
17837
17926
  const fields = [];
17838
- await collectFieldTargetsFromSchemaObject({
17927
+ await collectFieldTargetsFromTemplateObject({
17839
17928
  dom: options.dom,
17840
17929
  pageRef: options.pageRef,
17841
- value: options.schema,
17930
+ value: options.template,
17842
17931
  path: "",
17843
17932
  fields,
17844
17933
  insideArray: false
@@ -17890,13 +17979,13 @@ function createOpensteerExtractionDescriptorStore(options) {
17890
17979
  }
17891
17980
  return new MemoryOpensteerExtractionDescriptorStore(namespace);
17892
17981
  }
17893
- async function collectFieldTargetsFromSchemaObject(options) {
17982
+ async function collectFieldTargetsFromTemplateObject(options) {
17894
17983
  for (const [key, childValue] of Object.entries(options.value)) {
17895
17984
  const normalizedKey = normalizeKey(key);
17896
17985
  if (!normalizedKey) {
17897
17986
  continue;
17898
17987
  }
17899
- await collectFieldTargetsFromSchemaValue({
17988
+ await collectFieldTargetsFromTemplateValue({
17900
17989
  dom: options.dom,
17901
17990
  pageRef: options.pageRef,
17902
17991
  value: childValue,
@@ -17906,8 +17995,8 @@ async function collectFieldTargetsFromSchemaObject(options) {
17906
17995
  });
17907
17996
  }
17908
17997
  }
17909
- async function collectFieldTargetsFromSchemaValue(options) {
17910
- const normalizedField = normalizeSchemaField(options.value);
17998
+ async function collectFieldTargetsFromTemplateValue(options) {
17999
+ const normalizedField = normalizeTemplateField(options.value);
17911
18000
  if (normalizedField !== null) {
17912
18001
  options.fields.push(
17913
18002
  await compileFieldTarget({
@@ -17922,12 +18011,12 @@ async function collectFieldTargetsFromSchemaValue(options) {
17922
18011
  if (Array.isArray(options.value)) {
17923
18012
  if (options.insideArray) {
17924
18013
  throw new Error(
17925
- `Nested arrays are not supported in extraction schema at "${labelForPath(options.path)}".`
18014
+ `Nested arrays are not supported in extraction template at "${labelForPath(options.path)}".`
17926
18015
  );
17927
18016
  }
17928
18017
  if (options.value.length === 0) {
17929
18018
  throw new Error(
17930
- `Extraction array "${labelForPath(options.path)}" must include at least one representative item.`
18019
+ `Extraction array "${labelForPath(options.path)}" must include at least one representative template item.`
17931
18020
  );
17932
18021
  }
17933
18022
  for (let index = 0; index < options.value.length; index += 1) {
@@ -17938,7 +18027,7 @@ async function collectFieldTargetsFromSchemaValue(options) {
17938
18027
  );
17939
18028
  }
17940
18029
  const fieldCountBeforeItem = options.fields.length;
17941
- await collectFieldTargetsFromSchemaObject({
18030
+ await collectFieldTargetsFromTemplateObject({
17942
18031
  dom: options.dom,
17943
18032
  pageRef: options.pageRef,
17944
18033
  value: itemValue,
@@ -17949,7 +18038,7 @@ async function collectFieldTargetsFromSchemaValue(options) {
17949
18038
  const itemFields = options.fields.slice(fieldCountBeforeItem);
17950
18039
  if (!itemFields.some((field) => !("source" in field))) {
17951
18040
  throw new Error(
17952
- `Extraction array "${labelForPath(options.path)}" item ${String(index)} must include at least one element- or selector-backed field.`
18041
+ `Extraction array "${labelForPath(options.path)}" item ${String(index)} must include at least one element number or selector field.`
17953
18042
  );
17954
18043
  }
17955
18044
  }
@@ -17957,10 +18046,10 @@ async function collectFieldTargetsFromSchemaValue(options) {
17957
18046
  }
17958
18047
  if (!options.value || typeof options.value !== "object") {
17959
18048
  throw new Error(
17960
- `Invalid extraction schema value at "${labelForPath(options.path)}": expected an object, array, or field descriptor.`
18049
+ `Invalid extraction template value at "${labelForPath(options.path)}": expected an object, array, or field descriptor.`
17961
18050
  );
17962
18051
  }
17963
- await collectFieldTargetsFromSchemaObject({
18052
+ await collectFieldTargetsFromTemplateObject({
17964
18053
  dom: options.dom,
17965
18054
  pageRef: options.pageRef,
17966
18055
  value: options.value,
@@ -17992,7 +18081,7 @@ async function compileFieldTarget(options) {
17992
18081
  path: await resolveSelectorFieldPath({
17993
18082
  dom: options.dom,
17994
18083
  pageRef: options.pageRef,
17995
- selector: `[c="${String(options.field.element)}"]`
18084
+ selector: `[c="${String(options.field.c)}"]`
17996
18085
  }),
17997
18086
  ...options.field.attribute === void 0 ? {} : { attribute: options.field.attribute }
17998
18087
  };
@@ -18293,24 +18382,29 @@ function countNonNullLeaves(value) {
18293
18382
  }
18294
18383
  return Object.values(value).reduce((sum, item) => sum + countNonNullLeaves(item), 0);
18295
18384
  }
18296
- function normalizeSchemaField(value) {
18385
+ function normalizeTemplateField(value) {
18386
+ if (typeof value === "number") {
18387
+ return {
18388
+ c: normalizeExtractionCounter(value)
18389
+ };
18390
+ }
18297
18391
  if (!value || typeof value !== "object" || Array.isArray(value)) {
18298
18392
  return null;
18299
18393
  }
18300
18394
  const raw = value;
18301
- const hasElement = raw.element !== void 0;
18395
+ const hasCounter = raw.c !== void 0 || raw.element !== void 0;
18302
18396
  const hasSelector = raw.selector !== void 0;
18303
18397
  const hasSource = raw.source !== void 0;
18304
- const targetCount = Number(hasElement) + Number(hasSelector) + Number(hasSource);
18398
+ const targetCount = Number(hasCounter) + Number(hasSelector) + Number(hasSource);
18305
18399
  if (targetCount === 0) {
18306
18400
  return null;
18307
18401
  }
18308
18402
  if (targetCount !== 1) {
18309
18403
  throw new Error(
18310
- "Extraction field descriptors must specify exactly one of element, selector, or source."
18404
+ "Extraction field descriptors must specify exactly one of c/element, selector, or source."
18311
18405
  );
18312
18406
  }
18313
- const attribute = raw.attribute === void 0 ? void 0 : normalizeNonEmptyString2("attribute", raw.attribute);
18407
+ const attribute = raw.attr !== void 0 ? normalizeNonEmptyString2("attr", raw.attr) : raw.attribute === void 0 ? void 0 : normalizeNonEmptyString2("attribute", raw.attribute);
18314
18408
  if (hasSource) {
18315
18409
  if (raw.source !== "current_url") {
18316
18410
  throw new Error(`Unsupported extraction source "${String(raw.source)}".`);
@@ -18325,17 +18419,20 @@ function normalizeSchemaField(value) {
18325
18419
  ...attribute === void 0 ? {} : { attribute }
18326
18420
  };
18327
18421
  }
18328
- const element = Number(raw.element);
18329
- if (!Number.isInteger(element) || element < 1) {
18330
- throw new Error(
18331
- `Extraction field element must be a positive integer, received ${String(raw.element)}.`
18332
- );
18333
- }
18334
18422
  return {
18335
- element,
18423
+ c: normalizeExtractionCounter(raw.c ?? raw.element),
18336
18424
  ...attribute === void 0 ? {} : { attribute }
18337
18425
  };
18338
18426
  }
18427
+ function normalizeExtractionCounter(value) {
18428
+ const counter = Number(value);
18429
+ if (!Number.isInteger(counter) || counter < 1) {
18430
+ throw new Error(
18431
+ `Extraction element number must be a positive integer, received ${String(value)}.`
18432
+ );
18433
+ }
18434
+ return counter;
18435
+ }
18339
18436
  function normalizeNamespace(namespace) {
18340
18437
  const normalized = String(namespace ?? "default").trim();
18341
18438
  return normalized.length === 0 ? "default" : normalized;
@@ -18366,7 +18463,7 @@ function parseExtractionDescriptorRecord(record) {
18366
18463
  kind: "dom-extraction",
18367
18464
  persist: raw.persist,
18368
18465
  root,
18369
- ...typeof raw.schemaHash === "string" ? { schemaHash: raw.schemaHash } : {},
18466
+ ...typeof raw.templateHash === "string" ? { templateHash: raw.templateHash } : typeof raw.schemaHash === "string" ? { templateHash: raw.schemaHash } : {},
18370
18467
  ...typeof raw.sourceUrl === "string" ? { sourceUrl: raw.sourceUrl } : {}
18371
18468
  }
18372
18469
  };
@@ -18475,7 +18572,7 @@ var init_extraction2 = __esm({
18475
18572
  kind: "dom-extraction",
18476
18573
  persist: input.persist,
18477
18574
  root: input.root,
18478
- ...input.schemaHash === void 0 ? {} : { schemaHash: input.schemaHash },
18575
+ ...input.templateHash === void 0 ? {} : { templateHash: input.templateHash },
18479
18576
  ...input.sourceUrl === void 0 ? {} : { sourceUrl: input.sourceUrl }
18480
18577
  };
18481
18578
  const key = persistKey(this.namespace, input.persist);
@@ -18524,7 +18621,7 @@ var init_extraction2 = __esm({
18524
18621
  kind: "dom-extraction",
18525
18622
  persist: input.persist,
18526
18623
  root: input.root,
18527
- ...input.schemaHash === void 0 ? {} : { schemaHash: input.schemaHash },
18624
+ ...input.templateHash === void 0 ? {} : { templateHash: input.templateHash },
18528
18625
  ...input.sourceUrl === void 0 ? {} : { sourceUrl: input.sourceUrl }
18529
18626
  };
18530
18627
  const key = persistKey(this.namespace, input.persist);
@@ -18701,19 +18798,260 @@ function truncateValue(value, max) {
18701
18798
  }
18702
18799
  return `${head}${TRUNCATION_SUFFIX}`;
18703
18800
  }
18801
+ function takeValueWithinSerializedLengthFromEnd(value, max) {
18802
+ let serializedLength = 0;
18803
+ const chars = [];
18804
+ for (let index = value.length - 1; index >= 0; index -= 1) {
18805
+ const char = value[index];
18806
+ let nextLength = 1;
18807
+ if (char === "&") {
18808
+ nextLength = 5;
18809
+ } else if (char === "<" || char === ">") {
18810
+ nextLength = 4;
18811
+ } else if (char === '"') {
18812
+ nextLength = 6;
18813
+ }
18814
+ if (serializedLength + nextLength > max) {
18815
+ break;
18816
+ }
18817
+ chars.push(char);
18818
+ serializedLength += nextLength;
18819
+ }
18820
+ return chars.reverse().join("");
18821
+ }
18822
+ function truncateValueInMiddle(value, headMax, tailMax, marker = MIDDLE_TRUNCATION_MARKER) {
18823
+ const markerLength = getSerializedLength(marker);
18824
+ const max = headMax + markerLength + tailMax;
18825
+ if (getSerializedLength(value) <= max) {
18826
+ return value;
18827
+ }
18828
+ const head = takeValueWithinSerializedLength(value, headMax).replace(/\s+$/u, "");
18829
+ const tail = takeValueWithinSerializedLengthFromEnd(value, tailMax).replace(/^\s+/u, "");
18830
+ if (head.length === 0) {
18831
+ return tail.length === 0 ? marker : `${marker}${tail}`;
18832
+ }
18833
+ if (tail.length === 0) {
18834
+ return `${head}${marker}`;
18835
+ }
18836
+ return `${head}${marker}${tail}`;
18837
+ }
18704
18838
  function getAttrLimit(attr) {
18705
- if (URL_ATTRS.has(attr)) {
18706
- return URL_ATTR_MAX;
18839
+ if (attr === "srcset") {
18840
+ return SRCSET_ATTR_MAX;
18707
18841
  }
18708
18842
  if (TEXT_ATTRS.has(attr)) {
18709
18843
  return TEXT_ATTR_MAX;
18710
18844
  }
18711
18845
  return void 0;
18712
18846
  }
18847
+ function shouldBoundAttr(attr) {
18848
+ return MIDDLE_TRUNCATED_URL_ATTRS.has(attr) || getAttrLimit(attr) !== void 0;
18849
+ }
18713
18850
  function setBoundedAttr(el, attr, value) {
18851
+ if (MIDDLE_TRUNCATED_URL_ATTRS.has(attr)) {
18852
+ el.attr(
18853
+ attr,
18854
+ truncateValueInMiddle(value, MIDDLE_TRUNCATION_HEAD_MAX, MIDDLE_TRUNCATION_TAIL_MAX)
18855
+ );
18856
+ return;
18857
+ }
18714
18858
  const limit = getAttrLimit(attr);
18859
+ if (attr === "srcset" && limit !== void 0) {
18860
+ el.attr(attr, truncateSrcsetValue(value, limit));
18861
+ return;
18862
+ }
18715
18863
  el.attr(attr, limit === void 0 ? value : truncateValue(value, limit));
18716
18864
  }
18865
+ function truncateSrcsetValue(value, max) {
18866
+ if (getSerializedLength(value) <= max) {
18867
+ return value;
18868
+ }
18869
+ const candidates = parseSrcsetCandidates2(value);
18870
+ if (candidates.length === 0) {
18871
+ return truncateValueInMiddle(value, SRCSET_FALLBACK_HEAD_MAX, SRCSET_FALLBACK_TAIL_MAX);
18872
+ }
18873
+ for (const [headMax, tailMax, includeBest] of [
18874
+ [SRCSET_CANDIDATE_HEAD_MAX, SRCSET_CANDIDATE_TAIL_MAX, true],
18875
+ [SRCSET_COMPACT_CANDIDATE_HEAD_MAX, SRCSET_COMPACT_CANDIDATE_TAIL_MAX, true],
18876
+ [SRCSET_COMPACT_CANDIDATE_HEAD_MAX, SRCSET_COMPACT_CANDIDATE_TAIL_MAX, false]
18877
+ ]) {
18878
+ const compact = buildTruncatedSrcsetValue(candidates, headMax, tailMax, includeBest);
18879
+ if (getSerializedLength(compact) <= max) {
18880
+ return compact;
18881
+ }
18882
+ }
18883
+ return truncateValueInMiddle(value, SRCSET_FALLBACK_HEAD_MAX, SRCSET_FALLBACK_TAIL_MAX);
18884
+ }
18885
+ function buildTruncatedSrcsetValue(candidates, headMax, tailMax, includeBest) {
18886
+ const kept = getPreferredSrcsetCandidateIndices(candidates, includeBest);
18887
+ const parts = [];
18888
+ let previousIndex;
18889
+ for (const candidateIndex of kept) {
18890
+ if (previousIndex !== void 0 && candidateIndex - previousIndex > 1) {
18891
+ parts.push(MIDDLE_TRUNCATION_MARKER);
18892
+ }
18893
+ parts.push(formatSrcsetCandidate(candidates[candidateIndex], headMax, tailMax));
18894
+ previousIndex = candidateIndex;
18895
+ }
18896
+ return parts.join(", ");
18897
+ }
18898
+ function getPreferredSrcsetCandidateIndices(candidates, includeBest) {
18899
+ if (candidates.length === 0) {
18900
+ return [];
18901
+ }
18902
+ const kept = /* @__PURE__ */ new Set([0, candidates.length - 1]);
18903
+ if (includeBest) {
18904
+ kept.add(pickBestSrcsetCandidateIndex(candidates));
18905
+ }
18906
+ return [...kept].filter((index) => index >= 0 && index < candidates.length).sort((a, b) => a - b);
18907
+ }
18908
+ function pickBestSrcsetCandidateIndex(candidates) {
18909
+ let bestWidthIndex = -1;
18910
+ let bestWidth = -1;
18911
+ let bestDensityIndex = -1;
18912
+ let bestDensity = -1;
18913
+ for (let index = 0; index < candidates.length; index += 1) {
18914
+ const candidate = candidates[index];
18915
+ if (typeof candidate.width === "number" && Number.isFinite(candidate.width) && candidate.width > bestWidth) {
18916
+ bestWidth = candidate.width;
18917
+ bestWidthIndex = index;
18918
+ }
18919
+ if (typeof candidate.density === "number" && Number.isFinite(candidate.density) && candidate.density > bestDensity) {
18920
+ bestDensity = candidate.density;
18921
+ bestDensityIndex = index;
18922
+ }
18923
+ }
18924
+ if (bestWidthIndex >= 0) {
18925
+ return bestWidthIndex;
18926
+ }
18927
+ if (bestDensityIndex >= 0) {
18928
+ return bestDensityIndex;
18929
+ }
18930
+ return candidates.length - 1;
18931
+ }
18932
+ function formatSrcsetCandidate(candidate, headMax, tailMax) {
18933
+ const url = truncateValueInMiddle(candidate.url, headMax, tailMax);
18934
+ return candidate.descriptorText ? `${url} ${candidate.descriptorText}` : url;
18935
+ }
18936
+ function parseSrcsetCandidates2(raw) {
18937
+ const text = raw.trim();
18938
+ if (!text) {
18939
+ return [];
18940
+ }
18941
+ const out = [];
18942
+ let index = 0;
18943
+ while (index < text.length) {
18944
+ index = skipSrcsetSeparators(text, index);
18945
+ if (index >= text.length) {
18946
+ break;
18947
+ }
18948
+ const urlToken = readSrcsetUrlToken(text, index);
18949
+ index = urlToken.nextIndex;
18950
+ const url = urlToken.value.trim();
18951
+ if (!url) {
18952
+ continue;
18953
+ }
18954
+ index = skipSrcsetWhitespace(text, index);
18955
+ const descriptors = [];
18956
+ while (index < text.length && text[index] !== ",") {
18957
+ const descriptorToken = readSrcsetDescriptorToken(text, index);
18958
+ if (!descriptorToken.value) {
18959
+ index = descriptorToken.nextIndex;
18960
+ continue;
18961
+ }
18962
+ descriptors.push(descriptorToken.value);
18963
+ index = descriptorToken.nextIndex;
18964
+ index = skipSrcsetWhitespace(text, index);
18965
+ }
18966
+ if (index < text.length && text[index] === ",") {
18967
+ index += 1;
18968
+ }
18969
+ let width = null;
18970
+ let density = null;
18971
+ for (const descriptor of descriptors) {
18972
+ const token = descriptor.trim().toLowerCase();
18973
+ if (!token) {
18974
+ continue;
18975
+ }
18976
+ const widthMatch = token.match(/^(\d+)w$/);
18977
+ if (widthMatch) {
18978
+ const parsed = Number.parseInt(widthMatch[1], 10);
18979
+ if (Number.isFinite(parsed)) {
18980
+ width = parsed;
18981
+ }
18982
+ continue;
18983
+ }
18984
+ const densityMatch = token.match(/^(\d*\.?\d+)x$/);
18985
+ if (densityMatch) {
18986
+ const parsed = Number.parseFloat(densityMatch[1]);
18987
+ if (Number.isFinite(parsed)) {
18988
+ density = parsed;
18989
+ }
18990
+ }
18991
+ }
18992
+ out.push({
18993
+ url,
18994
+ descriptorText: descriptors.join(" "),
18995
+ width,
18996
+ density
18997
+ });
18998
+ }
18999
+ return out;
19000
+ }
19001
+ function skipSrcsetWhitespace(value, index) {
19002
+ let cursor = index;
19003
+ while (cursor < value.length && /\s/u.test(value[cursor])) {
19004
+ cursor += 1;
19005
+ }
19006
+ return cursor;
19007
+ }
19008
+ function skipSrcsetSeparators(value, index) {
19009
+ let cursor = skipSrcsetWhitespace(value, index);
19010
+ while (cursor < value.length && value[cursor] === ",") {
19011
+ cursor += 1;
19012
+ cursor = skipSrcsetWhitespace(value, cursor);
19013
+ }
19014
+ return cursor;
19015
+ }
19016
+ function readSrcsetUrlToken(value, index) {
19017
+ let cursor = index;
19018
+ let out = "";
19019
+ const isDataUrl = value.slice(index, index + 5).toLowerCase().startsWith("data:");
19020
+ while (cursor < value.length) {
19021
+ const char = value[cursor];
19022
+ if (/\s/u.test(char)) {
19023
+ break;
19024
+ }
19025
+ if (char === "," && !isDataUrl) {
19026
+ break;
19027
+ }
19028
+ out += char;
19029
+ cursor += 1;
19030
+ }
19031
+ if (isDataUrl && out.endsWith(",") && cursor < value.length) {
19032
+ out = out.slice(0, -1);
19033
+ }
19034
+ return {
19035
+ value: out,
19036
+ nextIndex: cursor
19037
+ };
19038
+ }
19039
+ function readSrcsetDescriptorToken(value, index) {
19040
+ let cursor = skipSrcsetWhitespace(value, index);
19041
+ let out = "";
19042
+ while (cursor < value.length) {
19043
+ const char = value[cursor];
19044
+ if (char === "," || /\s/u.test(char)) {
19045
+ break;
19046
+ }
19047
+ out += char;
19048
+ cursor += 1;
19049
+ }
19050
+ return {
19051
+ value: out.trim(),
19052
+ nextIndex: cursor
19053
+ };
19054
+ }
18717
19055
  function removeNoise($) {
18718
19056
  for (const tag of STRIP_TAGS) {
18719
19057
  $(tag).remove();
@@ -18738,46 +19076,76 @@ function markInlineSelfHiddenFallback($) {
18738
19076
  });
18739
19077
  }
18740
19078
  function pruneSelfHiddenNodes($) {
18741
- const nodes = [];
18742
- $(`[${OPENSTEER_SELF_HIDDEN_ATTR}]`).each(function collectSelfHiddenNodes() {
18743
- nodes.push($(this));
18744
- });
18745
- nodes.sort((left, right) => right.parents().length - left.parents().length);
18746
- for (const el of nodes) {
18747
- if (!el[0]) {
19079
+ for (const node of getElementsInReverseDocumentOrder($)) {
19080
+ if (node.attribs?.[OPENSTEER_SELF_HIDDEN_ATTR] === void 0) {
18748
19081
  continue;
18749
19082
  }
19083
+ const el = $(node);
18750
19084
  el.contents().each(function removeSelfHiddenText() {
18751
19085
  if (this.type === "text") {
18752
19086
  $(this).remove();
18753
19087
  }
18754
19088
  });
18755
- if (el.children().length === 0) {
19089
+ if (!hasElementChildren(node)) {
18756
19090
  el.remove();
18757
19091
  }
18758
19092
  }
18759
19093
  }
18760
- function hasDirectText($, el) {
18761
- return el.contents().filter(function hasDirectNodeText() {
18762
- return this.type === "text" && $(this).text().trim() !== "";
18763
- }).length > 0;
18764
- }
18765
- function hasTextDeep(el) {
18766
- return el.text().trim().length > 0;
19094
+ function getChildNodes(node) {
19095
+ return node?.children ?? [];
18767
19096
  }
18768
- function hasActionLabel(attrs) {
18769
- return typeof attrs["aria-label"] === "string" && attrs["aria-label"].trim() !== "" || typeof attrs["aria-labelledby"] === "string" && attrs["aria-labelledby"].trim() !== "" || typeof attrs["aria-describedby"] === "string" && attrs["aria-describedby"].trim() !== "" || typeof attrs.title === "string" && attrs.title.trim() !== "" || typeof attrs.placeholder === "string" && attrs.placeholder.trim() !== "" || typeof attrs.value === "string" && attrs.value.trim() !== "";
19097
+ function isElementLikeNode(node) {
19098
+ return node?.type === "tag" || node?.type === "script" || node?.type === "style";
18770
19099
  }
18771
- function unwrapActionNode($, el) {
18772
- if (hasTextDeep(el)) {
18773
- if (el.prev().length > 0) {
18774
- el.before(" ");
19100
+ function hasDirectText(node) {
19101
+ if (!node) {
19102
+ return false;
19103
+ }
19104
+ for (const child of getChildNodes(node)) {
19105
+ if (child.type === "text" && (child.data || "").trim() !== "") {
19106
+ return true;
18775
19107
  }
18776
- if (el.next().length > 0) {
18777
- el.after(" ");
19108
+ }
19109
+ return false;
19110
+ }
19111
+ function hasElementChildren(node) {
19112
+ if (!node) {
19113
+ return false;
19114
+ }
19115
+ for (const child of getChildNodes(node)) {
19116
+ if (isElementLikeNode(child)) {
19117
+ return true;
18778
19118
  }
18779
19119
  }
18780
- el.replaceWith(el.contents());
19120
+ return false;
19121
+ }
19122
+ function hasTextDeepNode(node) {
19123
+ if (!node) {
19124
+ return false;
19125
+ }
19126
+ if (node.type === "text") {
19127
+ return (node.data || "").trim() !== "";
19128
+ }
19129
+ for (const child of getChildNodes(node)) {
19130
+ if (hasTextDeepNode(child)) {
19131
+ return true;
19132
+ }
19133
+ }
19134
+ return false;
19135
+ }
19136
+ function hasActionLabel(attrs) {
19137
+ return typeof attrs["aria-label"] === "string" && attrs["aria-label"].trim() !== "" || typeof attrs["aria-labelledby"] === "string" && attrs["aria-labelledby"].trim() !== "" || typeof attrs["aria-describedby"] === "string" && attrs["aria-describedby"].trim() !== "" || typeof attrs.title === "string" && attrs.title.trim() !== "" || typeof attrs.placeholder === "string" && attrs.placeholder.trim() !== "" || typeof attrs.value === "string" && attrs.value.trim() !== "";
19138
+ }
19139
+ function unwrapActionNode($, el) {
19140
+ if (hasTextDeepNode(el[0])) {
19141
+ if (el.prev().length > 0) {
19142
+ el.before(" ");
19143
+ }
19144
+ if (el.next().length > 0) {
19145
+ el.after(" ");
19146
+ }
19147
+ }
19148
+ el.replaceWith(el.contents());
18781
19149
  }
18782
19150
  function stripToAttrs(el, keep) {
18783
19151
  const attrs = el.attr() || {};
@@ -18790,7 +19158,7 @@ function stripToAttrs(el, keep) {
18790
19158
  if (typeof value !== "string") {
18791
19159
  continue;
18792
19160
  }
18793
- if (getAttrLimit(attr) !== void 0) {
19161
+ if (shouldBoundAttr(attr)) {
18794
19162
  setBoundedAttr(el, attr, value);
18795
19163
  }
18796
19164
  }
@@ -18808,6 +19176,9 @@ function restoreBoundedAttr(el, attr, value) {
18808
19176
  function deduplicateImages(html) {
18809
19177
  const seen = /* @__PURE__ */ new Set();
18810
19178
  return html.replace(/<img\b([^>]*)>/gi, (full, attrContent) => {
19179
+ if (/\bc\s*=/.test(attrContent)) {
19180
+ return full;
19181
+ }
18811
19182
  const srcMatch = attrContent.match(/\bsrc\s*=\s*(["']?)(.*?)\1/);
18812
19183
  const srcsetMatch = attrContent.match(/\bsrcset\s*=\s*(["'])(.*?)\1/);
18813
19184
  let src = null;
@@ -18826,59 +19197,155 @@ function deduplicateImages(html) {
18826
19197
  return full;
18827
19198
  });
18828
19199
  }
18829
- function isPreservedImageElement($, el) {
18830
- const tag = (el[0]?.tagName || "").toLowerCase();
19200
+ function hasAttribute2(node, attr) {
19201
+ return node?.attribs?.[attr] !== void 0;
19202
+ }
19203
+ function hasPictureAncestor(node) {
19204
+ let current = node?.parent;
19205
+ while (current) {
19206
+ if (isElementLikeNode(current) && (current.tagName || "").toLowerCase() === "picture") {
19207
+ return true;
19208
+ }
19209
+ current = current.parent;
19210
+ }
19211
+ return false;
19212
+ }
19213
+ function pictureHasPreservedDescendant(node) {
19214
+ if (!node) {
19215
+ return false;
19216
+ }
19217
+ for (const child of getChildNodes(node)) {
19218
+ if (!isElementLikeNode(child)) {
19219
+ continue;
19220
+ }
19221
+ const tag = (child.tagName || "").toLowerCase();
19222
+ if (tag === "img") {
19223
+ return true;
19224
+ }
19225
+ if (tag === "source" && typeof child.attribs?.src === "string" && child.attribs.src.trim() !== "") {
19226
+ return true;
19227
+ }
19228
+ if (tag === "source" && typeof child.attribs?.srcset === "string" && child.attribs.srcset.trim() !== "") {
19229
+ return true;
19230
+ }
19231
+ if (pictureHasPreservedDescendant(child)) {
19232
+ return true;
19233
+ }
19234
+ }
19235
+ return false;
19236
+ }
19237
+ function isPreservedImageElement(node) {
19238
+ const tag = (node?.tagName || "").toLowerCase();
18831
19239
  if (tag === "img") {
18832
19240
  return true;
18833
19241
  }
18834
19242
  if (tag === "picture") {
18835
- const hasImg = el.find("img").length > 0;
18836
- const hasSource = el.find("source[src], source[srcset]").length > 0;
18837
- return hasImg || hasSource;
19243
+ return pictureHasPreservedDescendant(node);
18838
19244
  }
18839
19245
  if (tag === "source") {
18840
- const inPicture = el.parents("picture").length > 0;
18841
- const hasSrc = el.attr("src") != null && el.attr("src").trim() !== "" || el.attr("srcset") != null && el.attr("srcset").trim() !== "";
19246
+ const inPicture = hasPictureAncestor(node);
19247
+ const hasSrc = typeof node?.attribs?.src === "string" && node.attribs.src.trim() !== "" || typeof node?.attribs?.srcset === "string" && node.attribs.srcset.trim() !== "";
18842
19248
  return inPicture && hasSrc;
18843
19249
  }
18844
19250
  return false;
18845
19251
  }
19252
+ function getElementsInReverseDocumentOrder($) {
19253
+ return $.root().find("*").toArray().reverse().filter((node) => node.type === "tag");
19254
+ }
19255
+ function getNodeDepth(node) {
19256
+ let depth = 0;
19257
+ let current = node.parent;
19258
+ while (current) {
19259
+ depth++;
19260
+ current = current.parent;
19261
+ }
19262
+ return depth;
19263
+ }
19264
+ function getElementsByDepthDescending($) {
19265
+ const elements = $.root().find("*").toArray().filter((node) => node.type === "tag");
19266
+ const depths = /* @__PURE__ */ new Map();
19267
+ for (const el of elements) {
19268
+ depths.set(el, getNodeDepth(el));
19269
+ }
19270
+ return elements.sort((a, b) => (depths.get(b) ?? 0) - (depths.get(a) ?? 0));
19271
+ }
18846
19272
  function flattenExtractionTree($) {
18847
- const flatten = (root) => {
18848
- root.find("*").each(function flattenNode() {
18849
- const el = $(this);
18850
- const node = el[0];
18851
- if (!node) {
18852
- return;
18853
- }
18854
- const tag = (node.tagName || "").toLowerCase();
18855
- if (ROOT_TAGS.has(tag) || isBoundaryTag(tag)) {
18856
- return;
18857
- }
18858
- if (isPreservedImageElement($, el)) {
18859
- return;
18860
- }
18861
- if (tag === "a") {
18862
- el.children().each(function flattenAnchorChild() {
18863
- flatten($(this));
18864
- });
18865
- return;
18866
- }
18867
- const hasText = hasDirectText($, el);
18868
- if (hasText) {
18869
- return;
18870
- }
18871
- if (el.children().length === 0) {
18872
- el.remove();
18873
- return;
19273
+ for (const node of getElementsInReverseDocumentOrder($)) {
19274
+ const el = $(node);
19275
+ const tag = (node.tagName || "").toLowerCase();
19276
+ if (ROOT_TAGS.has(tag) || isBoundaryTag(tag) || isPreservedImageElement(node)) {
19277
+ continue;
19278
+ }
19279
+ if (tag === "a" || hasDirectText(node)) {
19280
+ continue;
19281
+ }
19282
+ if (!hasElementChildren(node)) {
19283
+ el.remove();
19284
+ continue;
19285
+ }
19286
+ el.replaceWith(el.contents());
19287
+ }
19288
+ }
19289
+ function hasMarkedAncestor(el, attr) {
19290
+ let current = el[0]?.parent;
19291
+ while (current) {
19292
+ if (!isElementLikeNode(current)) {
19293
+ return false;
19294
+ }
19295
+ if (current.attribs?.[attr] !== void 0) {
19296
+ return true;
19297
+ }
19298
+ current = current.parent;
19299
+ }
19300
+ return false;
19301
+ }
19302
+ function isIndicatorImage(node) {
19303
+ return (node?.tagName || "").toLowerCase() === "img" && (hasAttribute2(node, "alt") || hasAttribute2(node, "src") || hasAttribute2(node, "srcset"));
19304
+ }
19305
+ function isIndicatorPictureSource(node) {
19306
+ return (node?.tagName || "").toLowerCase() === "source" && hasPictureAncestor(node) && (hasAttribute2(node, "src") || hasAttribute2(node, "srcset"));
19307
+ }
19308
+ function isSemanticIndicator(node) {
19309
+ const tag = (node?.tagName || "").toLowerCase();
19310
+ if (tag === "svg") {
19311
+ return true;
19312
+ }
19313
+ return hasAttribute2(node, "aria-label") || hasAttribute2(node, "title") || hasAttribute2(node, "data-icon") || node?.attribs?.role === "img";
19314
+ }
19315
+ function findIndicatorDescendant(root) {
19316
+ if (!root) {
19317
+ return void 0;
19318
+ }
19319
+ let firstImage;
19320
+ let firstSource;
19321
+ let firstSemantic;
19322
+ const visit = (node) => {
19323
+ if (!isElementLikeNode(node)) {
19324
+ return false;
19325
+ }
19326
+ if (isIndicatorImage(node)) {
19327
+ firstImage = node;
19328
+ return true;
19329
+ }
19330
+ if (firstSource === void 0 && isIndicatorPictureSource(node)) {
19331
+ firstSource = node;
19332
+ }
19333
+ if (firstSemantic === void 0 && isSemanticIndicator(node)) {
19334
+ firstSemantic = node;
19335
+ }
19336
+ for (const child of getChildNodes(node)) {
19337
+ if (visit(child)) {
19338
+ return true;
18874
19339
  }
18875
- el.children().each(function flattenChild() {
18876
- flatten($(this));
18877
- });
18878
- el.replaceWith(el.contents());
18879
- });
19340
+ }
19341
+ return false;
18880
19342
  };
18881
- flatten($.root());
19343
+ for (const child of getChildNodes(root)) {
19344
+ if (visit(child)) {
19345
+ return firstImage;
19346
+ }
19347
+ }
19348
+ return firstImage ?? firstSource ?? firstSemantic;
18882
19349
  }
18883
19350
  function serializeForExtraction($, root) {
18884
19351
  const lines = [];
@@ -18947,7 +19414,7 @@ function serializeForExtraction($, root) {
18947
19414
  lines.push(`${" ".repeat(depth)}</${tagName}>`);
18948
19415
  }
18949
19416
  traverse(root, 0);
18950
- return lines.join("\n");
19417
+ return lines.map((l) => l.trim()).filter((l) => l.length > 0).join("");
18951
19418
  }
18952
19419
  function isClickable($, el, context) {
18953
19420
  if (context.hasPreMarked) {
@@ -19039,7 +19506,11 @@ function cleanForExtraction(html) {
19039
19506
  }
19040
19507
  });
19041
19508
  flattenExtractionTree($clean);
19042
- return deduplicateImages(serializeForExtraction($clean, $clean.root()[0]));
19509
+ const root = $clean.root()[0];
19510
+ if (root === void 0) {
19511
+ return "";
19512
+ }
19513
+ return deduplicateImages(serializeForExtraction($clean, root));
19043
19514
  }
19044
19515
  function cleanForAction(html) {
19045
19516
  if (!html.trim()) {
@@ -19065,22 +19536,12 @@ function cleanForAction(html) {
19065
19536
  $(`[${clickableMark}]`).each(function markIndicators() {
19066
19537
  const el = $(this);
19067
19538
  const wrapperAttrs = el.attr() || {};
19068
- if (hasTextDeep(el) || hasActionLabel(wrapperAttrs)) {
19539
+ if (hasTextDeepNode(el[0]) || hasActionLabel(wrapperAttrs)) {
19069
19540
  return;
19070
19541
  }
19071
- const imageIndicator = el.find("img[alt], img[src], img[srcset]").first();
19072
- if (imageIndicator.length) {
19073
- imageIndicator.attr(indicatorMark, "1");
19074
- return;
19075
- }
19076
- const pictureSourceIndicator = el.find("picture source[src], picture source[srcset]").first();
19077
- if (pictureSourceIndicator.length) {
19078
- pictureSourceIndicator.attr(indicatorMark, "1");
19079
- return;
19080
- }
19081
- const semanticIndicator = el.find('[aria-label], [title], [data-icon], [role="img"], svg').first();
19082
- if (semanticIndicator.length) {
19083
- semanticIndicator.attr(indicatorMark, "1");
19542
+ const indicatorNode = findIndicatorDescendant(el[0]);
19543
+ if (indicatorNode !== void 0) {
19544
+ $(indicatorNode).attr(indicatorMark, "1");
19084
19545
  }
19085
19546
  });
19086
19547
  $(`[${clickableMark}]`).each(function removeEmptyClickable() {
@@ -19090,7 +19551,7 @@ function cleanForAction(html) {
19090
19551
  if (NATIVE_INTERACTIVE_TAGS.has(tag) || tag === "a") {
19091
19552
  return;
19092
19553
  }
19093
- if (el.children().length > 0 || hasDirectText($, el)) {
19554
+ if (hasElementChildren(node) || hasDirectText(node)) {
19094
19555
  return;
19095
19556
  }
19096
19557
  const wrapperAttrs = el.attr() || {};
@@ -19116,46 +19577,31 @@ function cleanForAction(html) {
19116
19577
  current = ancestor.parent();
19117
19578
  }
19118
19579
  });
19119
- let changed = true;
19120
- while (changed) {
19121
- changed = false;
19122
- const nodes = [];
19123
- $("*").each(function collectNodes() {
19124
- nodes.push($(this));
19125
- });
19126
- nodes.sort((left, right) => right.parents().length - left.parents().length);
19127
- for (const el of nodes) {
19128
- const node = el[0];
19129
- if (!node) {
19130
- continue;
19131
- }
19132
- const tag = (node.tagName || "").toLowerCase();
19133
- if (ROOT_TAGS.has(tag) || isBoundaryTag(tag)) {
19134
- continue;
19135
- }
19136
- if (el.attr(clickableMark) !== void 0 || el.attr(indicatorMark) !== void 0) {
19137
- continue;
19138
- }
19139
- const insideClickable = el.parents(`[${clickableMark}]`).length > 0;
19140
- const preserveBranch = el.attr(branchMark) !== void 0;
19141
- const hasContent = el.children().length > 0 || hasDirectText($, el);
19142
- if (insideClickable || preserveBranch) {
19143
- if (!hasContent) {
19144
- el.remove();
19145
- } else {
19146
- unwrapActionNode($, el);
19147
- }
19148
- changed = true;
19149
- continue;
19150
- }
19580
+ for (const node of getElementsByDepthDescending($)) {
19581
+ const el = $(node);
19582
+ const tag = (node.tagName || "").toLowerCase();
19583
+ if (ROOT_TAGS.has(tag) || isBoundaryTag(tag)) {
19584
+ continue;
19585
+ }
19586
+ if (el.attr(clickableMark) !== void 0 || el.attr(indicatorMark) !== void 0) {
19587
+ continue;
19588
+ }
19589
+ const insideClickable = hasMarkedAncestor(el, clickableMark);
19590
+ const preserveBranch = el.attr(branchMark) !== void 0;
19591
+ const hasContent = hasElementChildren(node) || hasDirectText(node);
19592
+ if (insideClickable || preserveBranch) {
19151
19593
  if (!hasContent) {
19152
19594
  el.remove();
19153
- changed = true;
19154
- continue;
19595
+ } else {
19596
+ unwrapActionNode($, el);
19155
19597
  }
19156
- unwrapActionNode($, el);
19157
- changed = true;
19598
+ continue;
19599
+ }
19600
+ if (!hasContent) {
19601
+ el.remove();
19602
+ continue;
19158
19603
  }
19604
+ unwrapActionNode($, el);
19159
19605
  }
19160
19606
  $.root().find("*").contents().each(function normalizeActionTextNodes() {
19161
19607
  if (this.type !== "text") {
@@ -19235,21 +19681,7 @@ function cleanForAction(html) {
19235
19681
  OPENSTEER_SPARSE_COUNTER_ATTR
19236
19682
  ]);
19237
19683
  if (clickable) {
19238
- for (const attr of [
19239
- "href",
19240
- "role",
19241
- "type",
19242
- "title",
19243
- "placeholder",
19244
- "value",
19245
- "aria-label",
19246
- "aria-labelledby",
19247
- "aria-describedby",
19248
- "aria-expanded",
19249
- "aria-pressed",
19250
- "aria-selected",
19251
- "aria-haspopup"
19252
- ]) {
19684
+ for (const attr of ["href", "role", "type", "title", "placeholder", "value", "aria-label"]) {
19253
19685
  keep.add(attr);
19254
19686
  }
19255
19687
  }
@@ -19273,16 +19705,25 @@ function cleanForAction(html) {
19273
19705
  });
19274
19706
  return compactHtml(deduplicateImages($.html()));
19275
19707
  }
19276
- var STRIP_TAGS, TEXT_ATTR_MAX, URL_ATTR_MAX, URL_ATTRS, TEXT_ATTRS, TRUNCATION_SUFFIX, NOISE_SELECTORS, VOID_TAGS2;
19708
+ var STRIP_TAGS, TEXT_ATTR_MAX, SRCSET_ATTR_MAX, MIDDLE_TRUNCATED_URL_ATTRS, TEXT_ATTRS, TRUNCATION_SUFFIX, MIDDLE_TRUNCATION_MARKER, MIDDLE_TRUNCATION_HEAD_MAX, MIDDLE_TRUNCATION_TAIL_MAX, SRCSET_CANDIDATE_HEAD_MAX, SRCSET_CANDIDATE_TAIL_MAX, SRCSET_COMPACT_CANDIDATE_HEAD_MAX, SRCSET_COMPACT_CANDIDATE_TAIL_MAX, SRCSET_FALLBACK_HEAD_MAX, SRCSET_FALLBACK_TAIL_MAX, NOISE_SELECTORS, VOID_TAGS2;
19277
19709
  var init_cleaner = __esm({
19278
19710
  "../runtime-core/src/sdk/snapshot/cleaner.ts"() {
19279
19711
  init_constants();
19280
19712
  STRIP_TAGS = /* @__PURE__ */ new Set(["script", "style", "noscript", "meta", "link", "template"]);
19281
19713
  TEXT_ATTR_MAX = 150;
19282
- URL_ATTR_MAX = 500;
19283
- URL_ATTRS = /* @__PURE__ */ new Set(["href", "src", "srcset"]);
19714
+ SRCSET_ATTR_MAX = 160;
19715
+ MIDDLE_TRUNCATED_URL_ATTRS = /* @__PURE__ */ new Set(["href", "src"]);
19284
19716
  TEXT_ATTRS = /* @__PURE__ */ new Set(["alt", "title", "aria-label", "placeholder", "value"]);
19285
- TRUNCATION_SUFFIX = " [truncated]";
19717
+ TRUNCATION_SUFFIX = "...";
19718
+ MIDDLE_TRUNCATION_MARKER = "...";
19719
+ MIDDLE_TRUNCATION_HEAD_MAX = 40;
19720
+ MIDDLE_TRUNCATION_TAIL_MAX = 20;
19721
+ SRCSET_CANDIDATE_HEAD_MAX = 36;
19722
+ SRCSET_CANDIDATE_TAIL_MAX = 12;
19723
+ SRCSET_COMPACT_CANDIDATE_HEAD_MAX = 20;
19724
+ SRCSET_COMPACT_CANDIDATE_TAIL_MAX = 8;
19725
+ SRCSET_FALLBACK_HEAD_MAX = 56;
19726
+ SRCSET_FALLBACK_TAIL_MAX = 20;
19286
19727
  NOISE_SELECTORS = [
19287
19728
  `[${OPENSTEER_HIDDEN_ATTR}]`,
19288
19729
  "[hidden]",
@@ -19821,9 +20262,9 @@ function renderNode(snapshot, node, nodesById, snapshotsByDocumentRef, snapshotI
19821
20262
  const snapshotAttributes = normalizeNodeAttributes(node.attributes);
19822
20263
  const authoredAttributes = stripInternalSnapshotAttributes(snapshotAttributes);
19823
20264
  const attributes = [...authoredAttributes];
19824
- const subtreeHidden = hasAttribute2(snapshotAttributes, OPENSTEER_HIDDEN_ATTR) || isLikelySubtreeHidden(node);
19825
- const selfHidden = !subtreeHidden && (hasAttribute2(snapshotAttributes, OPENSTEER_SELF_HIDDEN_ATTR) || isLikelySelfHidden(node, nodesById));
19826
- const interactive = !subtreeHidden && !selfHidden && (hasAttribute2(snapshotAttributes, OPENSTEER_INTERACTIVE_ATTR) || isLikelyInteractive(tagName, node, authoredAttributes));
20265
+ const subtreeHidden = hasAttribute3(snapshotAttributes, OPENSTEER_HIDDEN_ATTR) || isLikelySubtreeHidden(node);
20266
+ const selfHidden = !subtreeHidden && (hasAttribute3(snapshotAttributes, OPENSTEER_SELF_HIDDEN_ATTR) || isLikelySelfHidden(node, nodesById));
20267
+ const interactive = !subtreeHidden && !selfHidden && (hasAttribute3(snapshotAttributes, OPENSTEER_INTERACTIVE_ATTR) || isLikelyInteractive(tagName, node, authoredAttributes));
19827
20268
  if (interactive) {
19828
20269
  attributes.push({ name: OPENSTEER_INTERACTIVE_ATTR, value: "1" });
19829
20270
  }
@@ -20141,7 +20582,7 @@ function parseOpacity(value) {
20141
20582
  const parsed = Number.parseFloat(value);
20142
20583
  return Number.isFinite(parsed) ? parsed : Number.NaN;
20143
20584
  }
20144
- function hasAttribute2(attributes, name) {
20585
+ function hasAttribute3(attributes, name) {
20145
20586
  const normalizedName = name.toLowerCase();
20146
20587
  return attributes.some((attribute) => attribute.name.toLowerCase() === normalizedName);
20147
20588
  }
@@ -23471,12 +23912,12 @@ var init_runtime3 = __esm({
23471
23912
  async (timeout) => {
23472
23913
  let descriptor2;
23473
23914
  let data;
23474
- if (input.schema !== void 0) {
23475
- assertValidOpensteerExtractionSchemaRoot(input.schema);
23915
+ if (input.template !== void 0) {
23916
+ assertValidOpensteerExtractionTemplateRoot(input.template);
23476
23917
  const fieldTargets = await timeout.runStep(
23477
23918
  () => compileOpensteerExtractionFieldTargets({
23478
23919
  pageRef,
23479
- schema: input.schema,
23920
+ template: input.template,
23480
23921
  dom: this.requireDom()
23481
23922
  })
23482
23923
  );
@@ -23508,7 +23949,7 @@ var init_runtime3 = __esm({
23508
23949
  () => descriptors.write({
23509
23950
  persist,
23510
23951
  root: payload,
23511
- schemaHash: canonicalJsonString(input.schema),
23952
+ templateHash: canonicalJsonString(input.template),
23512
23953
  sourceUrl: pageInfo.url
23513
23954
  })
23514
23955
  );
@@ -23568,7 +24009,7 @@ var init_runtime3 = __esm({
23568
24009
  artifacts,
23569
24010
  data: {
23570
24011
  ...input.persist === void 0 ? {} : { persist: input.persist },
23571
- ...descriptor?.payload.schemaHash === void 0 ? {} : { schemaHash: descriptor.payload.schemaHash },
24012
+ ...descriptor?.payload.templateHash === void 0 ? {} : { templateHash: descriptor.payload.templateHash },
23572
24013
  data: output.data
23573
24014
  },
23574
24015
  context: buildRuntimeTraceContext({
@@ -29171,8 +29612,29 @@ function assertSupportedCloudBrowserMode(browser) {
29171
29612
  }
29172
29613
  }
29173
29614
  function isMissingCloudSessionError(error) {
29615
+ if (error instanceof OpensteerCloudRequestError) {
29616
+ return error.statusCode === 404 && (error.code === void 0 || error.code === "CLOUD_SESSION_NOT_FOUND");
29617
+ }
29174
29618
  return error instanceof Error && /\b404\b/.test(error.message);
29175
29619
  }
29620
+ function isRecoverableCloudSessionError(error) {
29621
+ if (!(error instanceof OpensteerCloudRequestError)) {
29622
+ return false;
29623
+ }
29624
+ if (error.statusCode === 404) {
29625
+ return error.code === void 0 || error.code === "CLOUD_SESSION_NOT_FOUND";
29626
+ }
29627
+ return error.statusCode === 409 && error.code === "CLOUD_SESSION_STALE";
29628
+ }
29629
+ function isBootstrapRecoveryOperation(operation) {
29630
+ return operation === "session.open" || operation === "instrumentation.route" || operation === "instrumentation.intercept-script";
29631
+ }
29632
+ function isReusableCloudSessionState(session) {
29633
+ if (session.status === "closing" || session.status === "closed" || session.status === "failed") {
29634
+ return false;
29635
+ }
29636
+ return !(typeof session.expiresAt === "number" && session.expiresAt <= Date.now() + CLOUD_SESSION_REUSE_EXPIRY_SKEW_MS);
29637
+ }
29176
29638
  function isLoopbackBaseUrl(baseUrl) {
29177
29639
  let url;
29178
29640
  try {
@@ -29182,7 +29644,7 @@ function isLoopbackBaseUrl(baseUrl) {
29182
29644
  }
29183
29645
  return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]";
29184
29646
  }
29185
- var TEMPORARY_CLOUD_WORKSPACE_PREFIX, CloudSessionProxy;
29647
+ var TEMPORARY_CLOUD_WORKSPACE_PREFIX, CLOUD_SESSION_REUSE_EXPIRY_SKEW_MS, CloudSessionProxy;
29186
29648
  var init_session_proxy = __esm({
29187
29649
  "src/cloud/session-proxy.ts"() {
29188
29650
  init_src3();
@@ -29192,8 +29654,10 @@ var init_session_proxy = __esm({
29192
29654
  init_policy2();
29193
29655
  init_semantic_rest_client();
29194
29656
  init_automation_client();
29657
+ init_client();
29195
29658
  init_workspace_sync();
29196
29659
  TEMPORARY_CLOUD_WORKSPACE_PREFIX = "opensteer-cloud-workspace-";
29660
+ CLOUD_SESSION_REUSE_EXPIRY_SKEW_MS = 1e4;
29197
29661
  CloudSessionProxy = class {
29198
29662
  rootPath;
29199
29663
  workspace;
@@ -29207,6 +29671,8 @@ var init_session_proxy = __esm({
29207
29671
  automation;
29208
29672
  workspaceStore;
29209
29673
  syncWorkspaceOnClose = false;
29674
+ liveSessionStateEstablished = false;
29675
+ storedInstrumentation = [];
29210
29676
  constructor(cloud, options = {}) {
29211
29677
  this.cloud = cloud;
29212
29678
  this.workspace = options.workspace;
@@ -29236,15 +29702,12 @@ var init_session_proxy = __esm({
29236
29702
  if (this.client === void 0 && this.sessionId === void 0 && persisted !== void 0 && await this.isReusableCloudSession(persisted.sessionId)) {
29237
29703
  this.bindClient(persisted);
29238
29704
  }
29239
- if (this.automation) {
29240
- try {
29241
- const sessionInfo = await this.automation.getSessionInfo();
29242
- return {
29243
- ...sessionInfo,
29244
- ...this.workspace === void 0 ? {} : { workspace: this.workspace }
29245
- };
29246
- } catch {
29247
- }
29705
+ const sessionInfo = this.automation ? await this.automation.getSessionInfo().catch(() => void 0) : void 0;
29706
+ if (sessionInfo !== void 0) {
29707
+ return {
29708
+ ...sessionInfo,
29709
+ ...this.workspace === void 0 ? {} : { workspace: this.workspace }
29710
+ };
29248
29711
  }
29249
29712
  return {
29250
29713
  provider: {
@@ -29356,12 +29819,26 @@ var init_session_proxy = __esm({
29356
29819
  return this.invokeSemanticOperation("session.cookies", input);
29357
29820
  }
29358
29821
  async route(input) {
29359
- await this.ensureSession();
29360
- return this.requireAutomation().route(input);
29822
+ const registration = await this.invokeBootstrapInstrumentationOperation(
29823
+ "instrumentation.route",
29824
+ (automation) => automation.route(input)
29825
+ );
29826
+ this.storedInstrumentation.push({
29827
+ kind: "route",
29828
+ input
29829
+ });
29830
+ return registration;
29361
29831
  }
29362
29832
  async interceptScript(input) {
29363
- await this.ensureSession();
29364
- return this.requireAutomation().interceptScript(input);
29833
+ const registration = await this.invokeBootstrapInstrumentationOperation(
29834
+ "instrumentation.intercept-script",
29835
+ (automation) => automation.interceptScript(input)
29836
+ );
29837
+ this.storedInstrumentation.push({
29838
+ kind: "intercept-script",
29839
+ input
29840
+ });
29841
+ return registration;
29365
29842
  }
29366
29843
  async getStorageSnapshot(input = {}) {
29367
29844
  return this.invokeSemanticOperation("session.storage", input);
@@ -29409,6 +29886,8 @@ var init_session_proxy = __esm({
29409
29886
  this.client = void 0;
29410
29887
  this.sessionId = void 0;
29411
29888
  this.semanticGrant = void 0;
29889
+ this.liveSessionStateEstablished = false;
29890
+ this.storedInstrumentation.length = 0;
29412
29891
  if (this.cleanupRootOnClose) {
29413
29892
  await promises.rm(this.rootPath, { recursive: true, force: true }).catch(() => void 0);
29414
29893
  }
@@ -29436,6 +29915,8 @@ var init_session_proxy = __esm({
29436
29915
  this.automation = void 0;
29437
29916
  this.sessionId = void 0;
29438
29917
  this.semanticGrant = void 0;
29918
+ this.liveSessionStateEstablished = false;
29919
+ this.storedInstrumentation.length = 0;
29439
29920
  if (syncError !== void 0) {
29440
29921
  throw syncError;
29441
29922
  }
@@ -29483,6 +29964,7 @@ var init_session_proxy = __esm({
29483
29964
  };
29484
29965
  await this.writePersistedSession(record);
29485
29966
  this.bindClient(record, session.initialGrants?.semantic);
29967
+ await this.restoreStoredInstrumentation();
29486
29968
  }
29487
29969
  async syncWorkspaceToCloud() {
29488
29970
  if (this.workspace === void 0) {
@@ -29494,6 +29976,7 @@ var init_session_proxy = __esm({
29494
29976
  bindClient(record, initialSemanticGrant) {
29495
29977
  this.sessionId = record.sessionId;
29496
29978
  this.semanticGrant = initialSemanticGrant?.kind === "semantic" ? initialSemanticGrant : void 0;
29979
+ this.liveSessionStateEstablished = false;
29497
29980
  this.client = new OpensteerSemanticRestClient({
29498
29981
  getBaseUrl: async () => (await this.ensureSemanticGrant()).url,
29499
29982
  getAuthorizationHeader: async () => `Bearer ${(await this.ensureSemanticGrant()).token}`,
@@ -29501,6 +29984,19 @@ var init_session_proxy = __esm({
29501
29984
  });
29502
29985
  this.automation = new OpensteerCloudAutomationClient(this.cloud, record.sessionId);
29503
29986
  }
29987
+ async restoreStoredInstrumentation() {
29988
+ if (this.storedInstrumentation.length === 0) {
29989
+ return;
29990
+ }
29991
+ const automation = this.requireAutomation();
29992
+ for (const registration of this.storedInstrumentation) {
29993
+ if (registration.kind === "route") {
29994
+ await automation.route(registration.input);
29995
+ } else {
29996
+ await automation.interceptScript(registration.input);
29997
+ }
29998
+ }
29999
+ }
29504
30000
  async ensureWorkspaceStore() {
29505
30001
  if (this.workspaceStore !== void 0) {
29506
30002
  return this.workspaceStore;
@@ -29523,13 +30019,22 @@ var init_session_proxy = __esm({
29523
30019
  async clearPersistedSession() {
29524
30020
  await clearPersistedSessionRecord(this.rootPath, "cloud").catch(() => void 0);
29525
30021
  }
30022
+ async invalidateSession() {
30023
+ await this.automation?.close().catch(() => void 0);
30024
+ this.automation = void 0;
30025
+ this.client = void 0;
30026
+ this.sessionId = void 0;
30027
+ this.semanticGrant = void 0;
30028
+ this.liveSessionStateEstablished = false;
30029
+ await this.clearPersistedSession();
30030
+ }
29526
30031
  async isReusableCloudSession(sessionId, timeout) {
29527
30032
  try {
29528
30033
  const session = await this.cloud.getSession(sessionId, {
29529
30034
  signal: timeout?.signal,
29530
30035
  timeoutMs: timeout?.remainingMs()
29531
30036
  });
29532
- return session.status !== "closed" && session.status !== "failed";
30037
+ return isReusableCloudSessionState(session);
29533
30038
  } catch (error) {
29534
30039
  if (isMissingCloudSessionError(error)) {
29535
30040
  return false;
@@ -29578,26 +30083,79 @@ var init_session_proxy = __esm({
29578
30083
  try {
29579
30084
  await this.ensureSemanticGrant(true);
29580
30085
  return true;
29581
- } catch {
30086
+ } catch (refreshError) {
30087
+ if (await this.resetStaleSession(refreshError)) {
30088
+ throw refreshError;
30089
+ }
29582
30090
  return false;
29583
30091
  }
29584
30092
  }
29585
30093
  async invokeSemanticOperation(operation, input, sessionInit = {}) {
29586
- return this.runOperationWithPolicy(operation, async (timeout) => {
30094
+ return this.runOperationWithSessionRecovery(operation, async (timeout) => {
29587
30095
  await this.ensureSession(sessionInit, timeout);
29588
30096
  await this.ensureSemanticGrant(false, timeout);
29589
- return this.requireClient().invoke(operation, input, {
30097
+ const output = await this.requireClient().invoke(operation, input, {
29590
30098
  signal: timeout.signal,
29591
30099
  timeoutMs: timeout.remainingMs()
29592
30100
  });
30101
+ this.noteSuccessfulLiveOperation(operation);
30102
+ return output;
29593
30103
  });
29594
30104
  }
29595
30105
  async invokeAutomationOperation(operation, invoke, sessionInit = {}) {
29596
- return this.runOperationWithPolicy(operation, async (timeout) => {
30106
+ return this.runOperationWithSessionRecovery(operation, async (timeout) => {
29597
30107
  await this.ensureSession(sessionInit, timeout);
29598
- return invoke(this.requireAutomation());
30108
+ const output = await invoke(this.requireAutomation());
30109
+ this.noteSuccessfulLiveOperation(operation);
30110
+ return output;
29599
30111
  });
29600
30112
  }
30113
+ async invokeBootstrapInstrumentationOperation(operation, invoke) {
30114
+ let recovered = false;
30115
+ while (true) {
30116
+ try {
30117
+ await this.ensureSession();
30118
+ return await invoke(this.requireAutomation());
30119
+ } catch (error) {
30120
+ const stale = await this.resetStaleSession(error);
30121
+ if (!stale || recovered || !this.canRecoverWithFreshSession(operation)) {
30122
+ throw error;
30123
+ }
30124
+ recovered = true;
30125
+ }
30126
+ }
30127
+ }
30128
+ async runOperationWithSessionRecovery(operation, invoke) {
30129
+ return this.runOperationWithPolicy(operation, async (timeout) => {
30130
+ let recovered = false;
30131
+ while (true) {
30132
+ try {
30133
+ return await invoke(timeout);
30134
+ } catch (error) {
30135
+ const stale = await this.resetStaleSession(error);
30136
+ if (!stale || recovered || !this.canRecoverWithFreshSession(operation)) {
30137
+ throw error;
30138
+ }
30139
+ recovered = true;
30140
+ }
30141
+ }
30142
+ });
30143
+ }
30144
+ async resetStaleSession(error) {
30145
+ if (!isRecoverableCloudSessionError(error)) {
30146
+ return false;
30147
+ }
30148
+ await this.invalidateSession();
30149
+ return true;
30150
+ }
30151
+ canRecoverWithFreshSession(operation) {
30152
+ return !this.liveSessionStateEstablished && isBootstrapRecoveryOperation(operation);
30153
+ }
30154
+ noteSuccessfulLiveOperation(operation) {
30155
+ if (operation === "session.open" || operation === "page.list" || operation === "page.new" || operation === "page.activate" || operation === "page.close" || operation === "page.goto" || operation === "page.evaluate" || operation === "page.add-init-script" || operation === "page.snapshot" || operation === "dom.click" || operation === "dom.hover" || operation === "dom.input" || operation === "dom.scroll" || operation === "dom.extract" || operation === "network.query" || operation === "network.detail" || operation === "interaction.capture" || operation === "interaction.get" || operation === "interaction.diff" || operation === "interaction.replay" || operation === "scripts.capture" || operation === "artifact.read" || operation === "scripts.beautify" || operation === "scripts.deobfuscate" || operation === "scripts.sandbox" || operation === "captcha.solve" || operation === "session.cookies" || operation === "session.storage" || operation === "session.state" || operation === "session.fetch" || operation === "computer.execute") {
30156
+ this.liveSessionStateEstablished = true;
30157
+ }
30158
+ }
29601
30159
  async runOperationWithPolicy(operation, invoke) {
29602
30160
  return runWithPolicyTimeout(this.policy.timeout, { operation }, invoke);
29603
30161
  }
@@ -29912,14 +30470,85 @@ var init_session_control = __esm({
29912
30470
  }
29913
30471
  });
29914
30472
 
30473
+ // src/internal/errors.ts
30474
+ function normalizeThrownOpensteerError2(error, fallbackMessage) {
30475
+ if (isOpensteerProtocolError(error)) {
30476
+ return toOpensteerError(error);
30477
+ }
30478
+ if (isBrowserCoreError(error)) {
30479
+ return createOpensteerError(error.code, error.message, {
30480
+ retriable: error.retriable,
30481
+ ...error.details === void 0 ? {} : { details: error.details }
30482
+ });
30483
+ }
30484
+ if (error instanceof OpensteerAttachAmbiguousError) {
30485
+ return createOpensteerError("conflict", error.message, {
30486
+ details: {
30487
+ candidates: error.candidates,
30488
+ code: error.code,
30489
+ name: error.name
30490
+ }
30491
+ });
30492
+ }
30493
+ if (error instanceof Error && "opensteerError" in error && typeof error.opensteerError === "object" && error.opensteerError !== null) {
30494
+ const oe = error.opensteerError;
30495
+ return createOpensteerError(oe.code, oe.message, {
30496
+ retriable: oe.retriable,
30497
+ ...oe.capability === void 0 ? {} : { capability: oe.capability },
30498
+ ...oe.details === void 0 ? {} : { details: oe.details }
30499
+ });
30500
+ }
30501
+ if (error instanceof Error) {
30502
+ return createOpensteerError("operation-failed", error.message, {
30503
+ details: {
30504
+ name: error.name
30505
+ }
30506
+ });
30507
+ }
30508
+ return createOpensteerError("internal", fallbackMessage, {
30509
+ details: {
30510
+ value: error
30511
+ }
30512
+ });
30513
+ }
30514
+ var init_errors5 = __esm({
30515
+ "src/internal/errors.ts"() {
30516
+ init_src();
30517
+ init_src2();
30518
+ init_cdp_discovery();
30519
+ }
30520
+ });
30521
+
29915
30522
  // src/sdk/opensteer.ts
29916
30523
  var opensteer_exports = {};
29917
30524
  __export(opensteer_exports, {
29918
30525
  Opensteer: () => Opensteer
29919
30526
  });
30527
+ function createSdkProtocolError(error, fallbackMessage) {
30528
+ const normalized = normalizeThrownOpensteerError2(error, fallbackMessage);
30529
+ return new OpensteerProtocolError(normalized.code, normalized.message, {
30530
+ cause: error,
30531
+ retriable: normalized.retriable,
30532
+ ...normalized.capability === void 0 ? {} : { capability: normalized.capability },
30533
+ ...normalized.details === void 0 ? {} : { details: normalized.details }
30534
+ });
30535
+ }
30536
+ async function wrapSdkError(operation, fn) {
30537
+ try {
30538
+ return await fn();
30539
+ } catch (error) {
30540
+ if (isOpensteerProtocolError(error)) {
30541
+ throw error;
30542
+ }
30543
+ throw createSdkProtocolError(error, `${operation} failed`);
30544
+ }
30545
+ }
29920
30546
  function createUnsupportedBrowserController() {
29921
30547
  const fail = async () => {
29922
- throw new Error("browser.* helpers are only available in local mode.");
30548
+ throw new OpensteerProtocolError(
30549
+ "unsupported-operation",
30550
+ "browser.* helpers are only available in local mode."
30551
+ );
29923
30552
  };
29924
30553
  return {
29925
30554
  status: fail,
@@ -29932,7 +30561,10 @@ function normalizeTargetOptions(input) {
29932
30561
  const hasElement = input.element !== void 0;
29933
30562
  const hasSelector = input.selector !== void 0;
29934
30563
  if (hasElement && hasSelector) {
29935
- throw new Error("Specify exactly one of element, selector, or persist.");
30564
+ throw new OpensteerProtocolError(
30565
+ "invalid-argument",
30566
+ "Specify exactly one of element, selector, or persist."
30567
+ );
29936
30568
  }
29937
30569
  if (hasElement) {
29938
30570
  return {
@@ -29955,7 +30587,10 @@ function normalizeTargetOptions(input) {
29955
30587
  };
29956
30588
  }
29957
30589
  if (input.persist === void 0) {
29958
- throw new Error("Specify exactly one of element, selector, or persist.");
30590
+ throw new OpensteerProtocolError(
30591
+ "invalid-argument",
30592
+ "Specify exactly one of element, selector, or persist."
30593
+ );
29959
30594
  }
29960
30595
  return {
29961
30596
  target: {
@@ -30017,8 +30652,10 @@ function delay5(ms) {
30017
30652
  var SessionCookieJar, Opensteer;
30018
30653
  var init_opensteer = __esm({
30019
30654
  "src/sdk/opensteer.ts"() {
30655
+ init_src2();
30020
30656
  init_browser_manager();
30021
30657
  init_env();
30658
+ init_errors5();
30022
30659
  init_runtime_resolution();
30023
30660
  SessionCookieJar = class {
30024
30661
  domain;
@@ -30049,205 +30686,257 @@ var init_opensteer = __esm({
30049
30686
  dom;
30050
30687
  network;
30051
30688
  constructor(options = {}) {
30052
- const environment = resolveOpensteerEnvironment(options.rootDir);
30053
- const { provider, engineName, ...runtimeOptions } = options;
30054
- const runtimeConfig = resolveOpensteerRuntimeConfig({
30055
- ...provider === void 0 ? {} : { provider },
30056
- environment
30057
- });
30058
- if (runtimeConfig.provider.mode === "cloud") {
30059
- this.browserManager = void 0;
30060
- this.runtime = createOpensteerSemanticRuntime({
30061
- ...provider === void 0 ? {} : { provider },
30062
- ...engineName === void 0 ? {} : { engine: engineName },
30063
- environment,
30064
- runtimeOptions
30065
- });
30066
- this.browser = createUnsupportedBrowserController();
30067
- } else {
30068
- this.browserManager = new OpensteerBrowserManager({
30069
- ...runtimeOptions.rootDir === void 0 ? {} : { rootDir: runtimeOptions.rootDir },
30070
- ...runtimeOptions.rootPath === void 0 ? {} : { rootPath: runtimeOptions.rootPath },
30071
- ...runtimeOptions.workspace === void 0 ? {} : { workspace: runtimeOptions.workspace },
30072
- ...engineName === void 0 ? {} : { engineName },
30073
- environment,
30074
- ...runtimeOptions.browser === void 0 ? {} : { browser: runtimeOptions.browser },
30075
- ...runtimeOptions.launch === void 0 ? {} : { launch: runtimeOptions.launch },
30076
- ...runtimeOptions.context === void 0 ? {} : { context: runtimeOptions.context }
30077
- });
30078
- this.runtime = createOpensteerSemanticRuntime({
30689
+ try {
30690
+ const environment = resolveOpensteerEnvironment(options.rootDir);
30691
+ const { provider, engineName, ...runtimeOptions } = options;
30692
+ const runtimeConfig = resolveOpensteerRuntimeConfig({
30079
30693
  ...provider === void 0 ? {} : { provider },
30080
- ...engineName === void 0 ? {} : { engine: engineName },
30081
- environment,
30082
- runtimeOptions: {
30083
- ...runtimeOptions,
30084
- rootPath: this.browserManager.rootPath,
30085
- cleanupRootOnClose: this.browserManager.cleanupRootOnDisconnect
30086
- }
30694
+ environment
30087
30695
  });
30088
- this.browser = {
30089
- status: () => this.browserManager.status(),
30090
- clone: (input) => this.browserManager.clonePersistentBrowser(input),
30091
- reset: () => this.browserManager.reset(),
30092
- delete: () => this.browserManager.delete()
30696
+ if (runtimeConfig.provider.mode === "cloud") {
30697
+ this.browserManager = void 0;
30698
+ this.runtime = createOpensteerSemanticRuntime({
30699
+ ...provider === void 0 ? {} : { provider },
30700
+ ...engineName === void 0 ? {} : { engine: engineName },
30701
+ environment,
30702
+ runtimeOptions
30703
+ });
30704
+ this.browser = createUnsupportedBrowserController();
30705
+ } else {
30706
+ this.browserManager = new OpensteerBrowserManager({
30707
+ ...runtimeOptions.rootDir === void 0 ? {} : { rootDir: runtimeOptions.rootDir },
30708
+ ...runtimeOptions.rootPath === void 0 ? {} : { rootPath: runtimeOptions.rootPath },
30709
+ ...runtimeOptions.workspace === void 0 ? {} : { workspace: runtimeOptions.workspace },
30710
+ ...engineName === void 0 ? {} : { engineName },
30711
+ environment,
30712
+ ...runtimeOptions.browser === void 0 ? {} : { browser: runtimeOptions.browser },
30713
+ ...runtimeOptions.launch === void 0 ? {} : { launch: runtimeOptions.launch },
30714
+ ...runtimeOptions.context === void 0 ? {} : { context: runtimeOptions.context }
30715
+ });
30716
+ this.runtime = createOpensteerSemanticRuntime({
30717
+ ...provider === void 0 ? {} : { provider },
30718
+ ...engineName === void 0 ? {} : { engine: engineName },
30719
+ environment,
30720
+ runtimeOptions: {
30721
+ ...runtimeOptions,
30722
+ rootPath: this.browserManager.rootPath,
30723
+ cleanupRootOnClose: this.browserManager.cleanupRootOnDisconnect
30724
+ }
30725
+ });
30726
+ this.browser = {
30727
+ status: () => wrapSdkError("browser.status", () => this.browserManager.status()),
30728
+ clone: (input) => wrapSdkError("browser.clone", () => this.browserManager.clonePersistentBrowser(input)),
30729
+ reset: () => wrapSdkError("browser.reset", () => this.browserManager.reset()),
30730
+ delete: () => wrapSdkError("browser.delete", () => this.browserManager.delete())
30731
+ };
30732
+ }
30733
+ this.dom = {
30734
+ click: (input) => this.click(input),
30735
+ hover: (input) => this.hover(input),
30736
+ input: (input) => this.input(input),
30737
+ scroll: (input) => this.scroll(input)
30738
+ };
30739
+ this.network = {
30740
+ query: (input = {}) => wrapSdkError("network.query", () => this.runtime.queryNetwork(input)),
30741
+ detail: (recordId, options2) => wrapSdkError(
30742
+ "network.detail",
30743
+ () => this.runtime.getNetworkDetail({ recordId, ...options2 })
30744
+ )
30093
30745
  };
30746
+ } catch (error) {
30747
+ if (isOpensteerProtocolError(error)) {
30748
+ throw error;
30749
+ }
30750
+ throw createSdkProtocolError(error, "Failed to initialize Opensteer");
30094
30751
  }
30095
- this.dom = {
30096
- click: (input) => this.click(input),
30097
- hover: (input) => this.hover(input),
30098
- input: (input) => this.input(input),
30099
- scroll: (input) => this.scroll(input)
30100
- };
30101
- this.network = {
30102
- query: (input = {}) => this.runtime.queryNetwork(input),
30103
- detail: (recordId, options2) => this.runtime.getNetworkDetail({ recordId, ...options2 })
30104
- };
30105
30752
  }
30106
30753
  async open(input = {}) {
30107
- return this.runtime.open(typeof input === "string" ? { url: input } : input);
30754
+ return wrapSdkError(
30755
+ "session.open",
30756
+ () => this.runtime.open(typeof input === "string" ? { url: input } : input)
30757
+ );
30108
30758
  }
30109
30759
  async info() {
30110
- return this.runtime.info();
30760
+ return wrapSdkError("session.info", () => this.runtime.info());
30111
30761
  }
30112
30762
  async listPages(input = {}) {
30113
- return this.runtime.listPages(input);
30763
+ return wrapSdkError("page.list", () => this.runtime.listPages(input));
30114
30764
  }
30115
30765
  async newPage(input = {}) {
30116
- return this.runtime.newPage(input);
30766
+ return wrapSdkError("page.new", () => this.runtime.newPage(input));
30117
30767
  }
30118
30768
  async activatePage(input) {
30119
- return this.runtime.activatePage(input);
30769
+ return wrapSdkError("page.activate", () => this.runtime.activatePage(input));
30120
30770
  }
30121
30771
  async closePage(input = {}) {
30122
- return this.runtime.closePage(input);
30772
+ return wrapSdkError("page.close", () => this.runtime.closePage(input));
30123
30773
  }
30124
30774
  async goto(url, options = {}) {
30125
- return this.runtime.goto({
30126
- url,
30127
- ...options
30128
- });
30775
+ return wrapSdkError(
30776
+ "page.goto",
30777
+ () => this.runtime.goto({
30778
+ url,
30779
+ ...options
30780
+ })
30781
+ );
30129
30782
  }
30130
30783
  async evaluate(input) {
30131
- const normalized = typeof input === "string" ? {
30132
- script: input
30133
- } : input;
30134
- return (await this.runtime.evaluate(normalized)).value;
30784
+ return wrapSdkError("page.evaluate", async () => {
30785
+ const normalized = typeof input === "string" ? {
30786
+ script: input
30787
+ } : input;
30788
+ return (await this.runtime.evaluate(normalized)).value;
30789
+ });
30135
30790
  }
30136
30791
  async addInitScript(input) {
30137
- return this.runtime.addInitScript(
30138
- typeof input === "string" ? {
30139
- script: input
30140
- } : input
30792
+ return wrapSdkError(
30793
+ "page.addInitScript",
30794
+ () => this.runtime.addInitScript(
30795
+ typeof input === "string" ? {
30796
+ script: input
30797
+ } : input
30798
+ )
30141
30799
  );
30142
30800
  }
30143
30801
  async click(input) {
30144
- const { button, clickCount, modifiers, ...target } = input;
30145
- return this.runtime.click({
30146
- ...normalizeTargetOptions(target),
30147
- ...button === void 0 ? {} : { button },
30148
- ...clickCount === void 0 ? {} : { clickCount },
30149
- ...modifiers === void 0 ? {} : { modifiers }
30802
+ return wrapSdkError("dom.click", () => {
30803
+ const { button, clickCount, modifiers, ...target } = input;
30804
+ return this.runtime.click({
30805
+ ...normalizeTargetOptions(target),
30806
+ ...button === void 0 ? {} : { button },
30807
+ ...clickCount === void 0 ? {} : { clickCount },
30808
+ ...modifiers === void 0 ? {} : { modifiers }
30809
+ });
30150
30810
  });
30151
30811
  }
30152
30812
  async hover(input) {
30153
- return this.runtime.hover(normalizeTargetOptions(input));
30813
+ return wrapSdkError("dom.hover", () => this.runtime.hover(normalizeTargetOptions(input)));
30154
30814
  }
30155
30815
  async input(input) {
30156
- return this.runtime.input({
30157
- ...normalizeTargetOptions(input),
30158
- text: input.text,
30159
- ...input.pressEnter === void 0 ? {} : { pressEnter: input.pressEnter }
30160
- });
30816
+ return wrapSdkError(
30817
+ "dom.input",
30818
+ () => this.runtime.input({
30819
+ ...normalizeTargetOptions(input),
30820
+ text: input.text,
30821
+ ...input.pressEnter === void 0 ? {} : { pressEnter: input.pressEnter }
30822
+ })
30823
+ );
30161
30824
  }
30162
30825
  async scroll(input) {
30163
- return this.runtime.scroll({
30164
- ...normalizeTargetOptions(input),
30165
- direction: input.direction,
30166
- amount: input.amount
30167
- });
30826
+ return wrapSdkError(
30827
+ "dom.scroll",
30828
+ () => this.runtime.scroll({
30829
+ ...normalizeTargetOptions(input),
30830
+ direction: input.direction,
30831
+ amount: input.amount
30832
+ })
30833
+ );
30168
30834
  }
30169
30835
  async extract(input) {
30170
- return (await this.runtime.extract(input)).data;
30836
+ return wrapSdkError("extract", async () => (await this.runtime.extract(input)).data);
30171
30837
  }
30172
30838
  async waitForPage(input = {}) {
30173
- const baseline = new Set((await this.runtime.listPages()).pages.map((page) => page.pageRef));
30174
- const timeoutAt = Date.now() + (input.timeoutMs ?? 3e4);
30175
- const pollIntervalMs = input.pollIntervalMs ?? 100;
30176
- while (true) {
30177
- const match = (await this.runtime.listPages()).pages.find((page) => {
30178
- if (baseline.has(page.pageRef)) {
30179
- return false;
30180
- }
30181
- if (input.openerPageRef !== void 0 && page.openerPageRef !== input.openerPageRef) {
30182
- return false;
30839
+ return wrapSdkError("page.waitForPage", async () => {
30840
+ const baseline = new Set((await this.runtime.listPages()).pages.map((page) => page.pageRef));
30841
+ const timeoutAt = Date.now() + (input.timeoutMs ?? 3e4);
30842
+ const pollIntervalMs = input.pollIntervalMs ?? 100;
30843
+ while (true) {
30844
+ const match = (await this.runtime.listPages()).pages.find((page) => {
30845
+ if (baseline.has(page.pageRef)) {
30846
+ return false;
30847
+ }
30848
+ if (input.openerPageRef !== void 0 && page.openerPageRef !== input.openerPageRef) {
30849
+ return false;
30850
+ }
30851
+ if (input.urlIncludes !== void 0 && !page.url.includes(input.urlIncludes)) {
30852
+ return false;
30853
+ }
30854
+ return true;
30855
+ });
30856
+ if (match !== void 0) {
30857
+ return match;
30183
30858
  }
30184
- if (input.urlIncludes !== void 0 && !page.url.includes(input.urlIncludes)) {
30185
- return false;
30859
+ if (Date.now() >= timeoutAt) {
30860
+ throw new OpensteerProtocolError("timeout", "waitForPage timed out");
30186
30861
  }
30187
- return true;
30188
- });
30189
- if (match !== void 0) {
30190
- return match;
30191
- }
30192
- if (Date.now() >= timeoutAt) {
30193
- throw new Error("waitForPage timed out");
30862
+ await delay5(pollIntervalMs);
30194
30863
  }
30195
- await delay5(pollIntervalMs);
30196
- }
30197
- }
30198
- async snapshot(mode = "action") {
30199
- return (await this.runtime.snapshot({ mode })).html;
30864
+ });
30200
30865
  }
30201
30866
  async cookies(domain) {
30202
- return new SessionCookieJar(
30203
- await this.runtime.getCookies(domain === void 0 ? {} : { domain })
30867
+ return wrapSdkError(
30868
+ "session.cookies",
30869
+ async () => new SessionCookieJar(await this.runtime.getCookies(domain === void 0 ? {} : { domain }))
30204
30870
  );
30205
30871
  }
30206
30872
  async storage(domain, type = "local") {
30207
- const snapshot = await this.runtime.getStorageSnapshot(domain === void 0 ? {} : { domain });
30208
- const domainSnapshot = pickStorageDomainSnapshot(snapshot, domain);
30209
- if (domainSnapshot === void 0) {
30210
- return {};
30211
- }
30212
- const entries = type === "local" ? domainSnapshot.localStorage : domainSnapshot.sessionStorage;
30213
- return Object.fromEntries(entries.map((entry) => [entry.key, entry.value]));
30873
+ return wrapSdkError("session.storage", async () => {
30874
+ const snapshot = await this.runtime.getStorageSnapshot(
30875
+ domain === void 0 ? {} : { domain }
30876
+ );
30877
+ const domainSnapshot = pickStorageDomainSnapshot(snapshot, domain);
30878
+ if (domainSnapshot === void 0) {
30879
+ return {};
30880
+ }
30881
+ const entries = type === "local" ? domainSnapshot.localStorage : domainSnapshot.sessionStorage;
30882
+ return Object.fromEntries(entries.map((entry) => [entry.key, entry.value]));
30883
+ });
30214
30884
  }
30215
30885
  async state(domain) {
30216
- return this.runtime.getBrowserState(domain === void 0 ? {} : { domain });
30886
+ return wrapSdkError(
30887
+ "session.state",
30888
+ () => this.runtime.getBrowserState(domain === void 0 ? {} : { domain })
30889
+ );
30217
30890
  }
30218
30891
  async fetch(url, options = {}) {
30219
- const input = buildFetchInput(url, options);
30220
- const result = await this.runtime.fetch(input);
30221
- if (result.response === void 0) {
30222
- throw new Error(result.note ?? `session.fetch did not produce a response for ${url}`);
30223
- }
30224
- return toResponse(result.response);
30892
+ return wrapSdkError("session.fetch", async () => {
30893
+ const input = buildFetchInput(url, options);
30894
+ const result = await this.runtime.fetch(input);
30895
+ if (result.response === void 0) {
30896
+ throw new OpensteerProtocolError(
30897
+ "operation-failed",
30898
+ result.note ?? `session.fetch did not produce a response for ${url}`
30899
+ );
30900
+ }
30901
+ return toResponse(result.response);
30902
+ });
30225
30903
  }
30226
30904
  async computerExecute(input) {
30227
- return this.runtime.computerExecute(input);
30905
+ return wrapSdkError("session.computerExecute", () => this.runtime.computerExecute(input));
30228
30906
  }
30229
30907
  async route(input) {
30230
- return this.requireOwnedInstrumentationRuntime("route").route(input);
30908
+ return wrapSdkError(
30909
+ "session.route",
30910
+ () => this.requireOwnedInstrumentationRuntime("route").route(input)
30911
+ );
30231
30912
  }
30232
30913
  async interceptScript(input) {
30233
- return this.requireOwnedInstrumentationRuntime("interceptScript").interceptScript(input);
30914
+ return wrapSdkError(
30915
+ "session.interceptScript",
30916
+ () => this.requireOwnedInstrumentationRuntime("interceptScript").interceptScript(input)
30917
+ );
30234
30918
  }
30235
30919
  async close() {
30236
- if (this.browserManager === void 0 || this.browserManager.mode === "temporary") {
30237
- return this.runtime.close();
30238
- }
30239
- const output = await this.runtime.close();
30240
- await this.browserManager.close();
30241
- return output;
30920
+ return wrapSdkError("session.close", async () => {
30921
+ if (this.browserManager === void 0 || this.browserManager.mode === "temporary") {
30922
+ return this.runtime.close();
30923
+ }
30924
+ const output = await this.runtime.close();
30925
+ await this.browserManager.close();
30926
+ return output;
30927
+ });
30242
30928
  }
30243
30929
  async disconnect() {
30244
- await this.runtime.disconnect();
30930
+ return wrapSdkError("session.disconnect", () => this.runtime.disconnect());
30245
30931
  }
30246
30932
  requireOwnedInstrumentationRuntime(method) {
30247
30933
  if (typeof this.runtime.route === "function" && typeof this.runtime.interceptScript === "function") {
30248
30934
  return this.runtime;
30249
30935
  }
30250
- throw new Error(`${method}() is not available for this session runtime.`);
30936
+ throw new OpensteerProtocolError(
30937
+ "unsupported-operation",
30938
+ `${method}() is not available for this session runtime.`
30939
+ );
30251
30940
  }
30252
30941
  };
30253
30942
  }
@@ -30255,7 +30944,7 @@ var init_opensteer = __esm({
30255
30944
 
30256
30945
  // package.json
30257
30946
  var package_default = {
30258
- version: "0.9.4"};
30947
+ version: "0.9.6"};
30259
30948
 
30260
30949
  // src/cli/bin.ts
30261
30950
  init_browser_manager();
@@ -30270,6 +30959,110 @@ async function loadCliEnvironment(cwd) {
30270
30959
  loadEnvironment(cwd);
30271
30960
  }
30272
30961
 
30962
+ // src/cli/errors.ts
30963
+ init_src();
30964
+ init_src2();
30965
+ var CliError = class extends Error {
30966
+ type;
30967
+ usage;
30968
+ constructor(type, message, usage) {
30969
+ super(message);
30970
+ this.name = "CliError";
30971
+ this.type = type;
30972
+ this.usage = usage;
30973
+ }
30974
+ format() {
30975
+ return this.usage === void 0 ? this.message : `${this.message}
30976
+ Usage: ${this.usage}`;
30977
+ }
30978
+ };
30979
+ function isCliError(value) {
30980
+ return value instanceof CliError;
30981
+ }
30982
+ function formatCliErrorOutput(error) {
30983
+ if (isCliError(error)) {
30984
+ return {
30985
+ success: false,
30986
+ error: error.format(),
30987
+ type: error.type
30988
+ };
30989
+ }
30990
+ if (isOpensteerProtocolError(error)) {
30991
+ return {
30992
+ success: false,
30993
+ error: error.message,
30994
+ code: error.code,
30995
+ retriable: error.retriable
30996
+ };
30997
+ }
30998
+ if (isBrowserCoreError(error)) {
30999
+ return {
31000
+ success: false,
31001
+ error: error.message,
31002
+ code: error.code,
31003
+ retriable: error.retriable
31004
+ };
31005
+ }
31006
+ if (error instanceof Error && "opensteerError" in error) {
31007
+ const oe = error.opensteerError;
31008
+ return {
31009
+ success: false,
31010
+ error: oe.message,
31011
+ code: oe.code,
31012
+ retriable: oe.retriable
31013
+ };
31014
+ }
31015
+ if (error instanceof SyntaxError) {
31016
+ return {
31017
+ success: false,
31018
+ error: error.message,
31019
+ type: "invalid_value"
31020
+ };
31021
+ }
31022
+ return {
31023
+ success: false,
31024
+ error: error instanceof Error ? error.message : String(error)
31025
+ };
31026
+ }
31027
+ function emitWarning(warning) {
31028
+ process.stderr.write(`${JSON.stringify({ warning })}
31029
+ `);
31030
+ }
31031
+ var CLI_USAGE_HINTS = {
31032
+ "session.open": "opensteer open <url> [--workspace <id>]",
31033
+ "page.goto": "opensteer goto <url>",
31034
+ "page.evaluate": "opensteer evaluate <script>",
31035
+ "page.add-init-script": "opensteer init-script <script>",
31036
+ "dom.click": "opensteer click <element> --persist <key>",
31037
+ "dom.hover": "opensteer hover <element> --persist <key>",
31038
+ "dom.input": "opensteer input <element> <text> --persist <key>",
31039
+ "dom.scroll": "opensteer scroll <direction> <amount> --persist <key>",
31040
+ "dom.extract": "opensteer extract <template> --persist <key>",
31041
+ "network.detail": "opensteer network detail <recordId>",
31042
+ "session.fetch": "opensteer fetch <url>",
31043
+ "captcha.solve": "opensteer captcha solve --provider <name> --api-key <key>",
31044
+ "scripts.capture": "opensteer scripts capture",
31045
+ "scripts.beautify": "opensteer scripts beautify <artifactId>",
31046
+ "scripts.deobfuscate": "opensteer scripts deobfuscate <artifactId>",
31047
+ "scripts.sandbox": "opensteer scripts sandbox <artifactId>",
31048
+ "interaction.capture": "opensteer interaction capture",
31049
+ "interaction.get": "opensteer interaction get <traceId>",
31050
+ "interaction.replay": "opensteer interaction replay <traceId>",
31051
+ "interaction.diff": "opensteer interaction diff <leftTraceId> <rightTraceId>",
31052
+ "artifact.read": "opensteer artifact read <artifactId>",
31053
+ "computer.click": "opensteer computer click <x> <y>",
31054
+ "computer.type": "opensteer computer type <text>",
31055
+ "computer.key": "opensteer computer key <key>",
31056
+ "computer.scroll": "opensteer computer scroll <x> <y> --dx <n> --dy <n>",
31057
+ "computer.move": "opensteer computer move <x> <y>",
31058
+ "computer.drag": "opensteer computer drag <x1> <y1> <x2> <y2>",
31059
+ "computer.screenshot": "opensteer computer screenshot",
31060
+ exec: "opensteer exec <expression>",
31061
+ record: "opensteer record --url <url> --workspace <id>",
31062
+ "browser.inspect": "opensteer browser inspect <endpoint>",
31063
+ "browser.clone": "opensteer browser clone --source-user-data-dir <path>"
31064
+ };
31065
+
30273
31066
  // src/cli/help.ts
30274
31067
  function getHelpText() {
30275
31068
  return `Opensteer CLI
@@ -30288,11 +31081,11 @@ Navigation:
30288
31081
 
30289
31082
  DOM:
30290
31083
  snapshot [action|extraction]
30291
- click <element> [--button left|middle|right] [--persist <key>] [--capture-network <label>]
30292
- hover <element> [--persist <key>] [--capture-network <label>]
30293
- input <element> <text> [--press-enter] [--persist <key>] [--capture-network <label>]
30294
- scroll <direction> <amount> [--element <n>] [--persist <key>] [--capture-network <label>]
30295
- extract <schema> [--persist <key>]
31084
+ click <element> --persist <key> [--button left|middle|right] [--capture-network <label>]
31085
+ hover <element> --persist <key> [--capture-network <label>]
31086
+ input <element> <text> --persist <key> [--press-enter] [--capture-network <label>]
31087
+ scroll <direction> <amount> --persist <key> [--element <n>] [--capture-network <label>]
31088
+ extract <template> --persist <key>
30296
31089
  evaluate <script>
30297
31090
  init-script <script>
30298
31091
 
@@ -30531,7 +31324,7 @@ function parseCommandLine(argv) {
30531
31324
  const key = token.slice(2, separator === -1 ? void 0 : separator);
30532
31325
  const spec = CLI_OPTION_SPECS[key];
30533
31326
  if (spec === void 0) {
30534
- throw new Error(`Unknown option: --${key}.`);
31327
+ throw new CliError("unknown_option", `Unknown option: --${key}.`);
30535
31328
  }
30536
31329
  if (separator !== -1) {
30537
31330
  rawOptions.set(key, [...rawOptions.get(key) ?? [], token.slice(separator + 1)]);
@@ -30560,7 +31353,8 @@ function parseCommandLine(argv) {
30560
31353
  continue;
30561
31354
  }
30562
31355
  if (next === void 0 || next.startsWith("--")) {
30563
- throw new Error(
31356
+ throw new CliError(
31357
+ "missing_arguments",
30564
31358
  `Option "--${key}" requires a value.${next?.startsWith("--") === true ? ` Use "--${key}=<value>" when the value begins with "--".` : ``}`
30565
31359
  );
30566
31360
  }
@@ -30606,10 +31400,14 @@ function parseCommandLine(argv) {
30606
31400
  const autoLocalView = readOptionalBoolean(rawOptions, "auto");
30607
31401
  const noAutoLocalView = readOptionalBoolean(rawOptions, "no-auto");
30608
31402
  if (autoLocalView === true && noAutoLocalView === true) {
30609
- throw new Error('Options "--auto" and "--no-auto" cannot be combined.');
31403
+ throw new CliError("invalid_option", 'Options "--auto" and "--no-auto" cannot be combined.');
30610
31404
  }
30611
31405
  if (command[0] !== "view" && (autoLocalView !== void 0 || noAutoLocalView !== void 0)) {
30612
- throw new Error('Options "--auto" and "--no-auto" are only supported with "view".');
31406
+ throw new CliError(
31407
+ "invalid_option",
31408
+ 'Options "--auto" and "--no-auto" are only supported with "view".',
31409
+ "opensteer view --auto"
31410
+ );
30613
31411
  }
30614
31412
  const global = readOptionalBoolean(rawOptions, "global");
30615
31413
  const yes = readOptionalBoolean(rawOptions, "yes");
@@ -30665,7 +31463,7 @@ function parseKeyValueList(values) {
30665
31463
  values.map((entry) => {
30666
31464
  const separator = entry.indexOf("=");
30667
31465
  if (separator <= 0) {
30668
- throw new Error(`Expected NAME=VALUE, received "${entry}".`);
31466
+ throw new CliError("invalid_value", `Expected NAME=VALUE, received "${entry}".`);
30669
31467
  }
30670
31468
  return [entry.slice(0, separator), entry.slice(separator + 1)];
30671
31469
  })
@@ -30696,7 +31494,7 @@ function readOptionalBoolean(options, name) {
30696
31494
  if (value === "false") {
30697
31495
  return false;
30698
31496
  }
30699
- throw new Error(`Option "--${name}" must be true or false.`);
31497
+ throw new CliError("invalid_value", `Option "--${name}" must be true or false.`);
30700
31498
  }
30701
31499
  function readOptionalNumber(options, name) {
30702
31500
  const value = readSingle(options, name);
@@ -30705,13 +31503,20 @@ function readOptionalNumber(options, name) {
30705
31503
  }
30706
31504
  const parsed = Number(value);
30707
31505
  if (!Number.isFinite(parsed)) {
30708
- throw new Error(`Option "--${name}" must be a number.`);
31506
+ throw new CliError("invalid_value", `Option "--${name}" must be a number.`);
30709
31507
  }
30710
31508
  return parsed;
30711
31509
  }
30712
31510
  function readJsonValue(options, name) {
30713
31511
  const value = readSingle(options, name);
30714
- return value === void 0 ? void 0 : JSON.parse(value);
31512
+ if (value === void 0) {
31513
+ return void 0;
31514
+ }
31515
+ try {
31516
+ return JSON.parse(value);
31517
+ } catch {
31518
+ throw new CliError("invalid_value", `Option "--${name}" contains invalid JSON.`);
31519
+ }
30715
31520
  }
30716
31521
  function readJsonObject(options, name) {
30717
31522
  const parsed = readJsonValue(options, name);
@@ -30719,7 +31524,7 @@ function readJsonObject(options, name) {
30719
31524
  return void 0;
30720
31525
  }
30721
31526
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
30722
- throw new Error(`Option "--${name}" must be a JSON object.`);
31527
+ throw new CliError("invalid_value", `Option "--${name}" must be a JSON object.`);
30723
31528
  }
30724
31529
  return parsed;
30725
31530
  }
@@ -30729,7 +31534,7 @@ function readJsonArray(options, name) {
30729
31534
  return void 0;
30730
31535
  }
30731
31536
  if (!Array.isArray(parsed)) {
30732
- throw new Error(`Option "--${name}" must be a JSON array.`);
31537
+ throw new CliError("invalid_value", `Option "--${name}" must be a JSON array.`);
30733
31538
  }
30734
31539
  return parsed;
30735
31540
  }
@@ -30749,7 +31554,7 @@ async function buildOperationInput(operation, parsed, runtime) {
30749
31554
  case "session.open": {
30750
31555
  const url = parsed.rest[0];
30751
31556
  if (url === void 0) {
30752
- throw new Error("open requires a URL.");
31557
+ throw new CliError("missing_arguments", "open requires a URL.", CLI_USAGE_HINTS[operation]);
30753
31558
  }
30754
31559
  return {
30755
31560
  url,
@@ -30782,7 +31587,7 @@ async function buildOperationInput(operation, parsed, runtime) {
30782
31587
  }
30783
31588
  case "page.goto": {
30784
31589
  if (parsed.rest[0] === void 0) {
30785
- throw new Error("goto requires a URL.");
31590
+ throw new CliError("missing_arguments", "goto requires a URL.", CLI_USAGE_HINTS[operation]);
30786
31591
  }
30787
31592
  const captureNetwork = readSingle(parsed.rawOptions, "capture-network");
30788
31593
  return {
@@ -30794,14 +31599,14 @@ async function buildOperationInput(operation, parsed, runtime) {
30794
31599
  return parsed.rest[0] === void 0 ? {} : { mode: parsed.rest[0] };
30795
31600
  case "page.evaluate":
30796
31601
  if (parsed.rest[0] === void 0) {
30797
- throw new Error("evaluate requires a script.");
31602
+ throw new CliError("missing_arguments", "evaluate requires a script.", CLI_USAGE_HINTS[operation]);
30798
31603
  }
30799
31604
  return {
30800
31605
  script: joinRest(parsed.rest, 0)
30801
31606
  };
30802
31607
  case "page.add-init-script":
30803
31608
  if (parsed.rest[0] === void 0) {
30804
- throw new Error("init-script requires a script.");
31609
+ throw new CliError("missing_arguments", "init-script requires a script.", CLI_USAGE_HINTS[operation]);
30805
31610
  }
30806
31611
  return {
30807
31612
  script: joinRest(parsed.rest, 0)
@@ -30817,7 +31622,7 @@ async function buildOperationInput(operation, parsed, runtime) {
30817
31622
  return buildElementTargetInput(parsed, "hover");
30818
31623
  case "dom.input": {
30819
31624
  if (parsed.rest[1] === void 0) {
30820
- throw new Error("input requires an element number and text.");
31625
+ throw new CliError("missing_arguments", "input requires an element number and text.", CLI_USAGE_HINTS[operation]);
30821
31626
  }
30822
31627
  const pressEnter = readOptionalBoolean(parsed.rawOptions, "press-enter");
30823
31628
  return {
@@ -30842,18 +31647,18 @@ async function buildOperationInput(operation, parsed, runtime) {
30842
31647
  },
30843
31648
  direction,
30844
31649
  amount,
30845
- ...persist === void 0 ? {} : { persist },
31650
+ persist,
30846
31651
  ...captureNetwork === void 0 ? {} : { captureNetwork }
30847
31652
  };
30848
31653
  }
30849
31654
  case "dom.extract": {
30850
31655
  if (parsed.rest[0] === void 0) {
30851
- throw new Error("extract requires a schema.");
31656
+ throw new CliError("missing_arguments", "extract requires a template.", CLI_USAGE_HINTS[operation]);
30852
31657
  }
30853
- const persist = readExtractPersistKey(parsed);
31658
+ const persist = readPersistKey(parsed, "extract");
30854
31659
  return {
30855
- schema: parseRequiredJsonObjectArgument(joinRest(parsed.rest, 0), "extract schema"),
30856
- ...persist === void 0 ? {} : { persist }
31660
+ persist,
31661
+ template: parseRequiredJsonObjectArgument(joinRest(parsed.rest, 0), "extract template")
30857
31662
  };
30858
31663
  }
30859
31664
  case "network.query": {
@@ -30884,7 +31689,7 @@ async function buildOperationInput(operation, parsed, runtime) {
30884
31689
  }
30885
31690
  case "network.detail": {
30886
31691
  if (parsed.rest[0] === void 0) {
30887
- throw new Error("network detail requires a record id.");
31692
+ throw new CliError("missing_arguments", "network detail requires a record id.", CLI_USAGE_HINTS[operation]);
30888
31693
  }
30889
31694
  const probeFlag = readOptionalBoolean(parsed.rawOptions, "probe");
30890
31695
  return {
@@ -30895,7 +31700,7 @@ async function buildOperationInput(operation, parsed, runtime) {
30895
31700
  case "session.fetch": {
30896
31701
  const url = parsed.rest[0];
30897
31702
  if (url === void 0) {
30898
- throw new Error("fetch requires a URL.");
31703
+ throw new CliError("missing_arguments", "fetch requires a URL.", CLI_USAGE_HINTS[operation]);
30899
31704
  }
30900
31705
  const bodyJson = readJsonValue(parsed.rawOptions, "body");
30901
31706
  const bodyText = readSingle(parsed.rawOptions, "body-text");
@@ -30903,7 +31708,7 @@ async function buildOperationInput(operation, parsed, runtime) {
30903
31708
  const query = parseKeyValueList(parsed.rawOptions.get("query"));
30904
31709
  const headers = parseKeyValueList(parsed.rawOptions.get("header"));
30905
31710
  if (bodyJson !== void 0 && bodyText !== void 0) {
30906
- throw new Error('Use either "--body" or "--body-text", not both.');
31711
+ throw new CliError("invalid_option", 'Use either "--body" or "--body-text", not both.');
30907
31712
  }
30908
31713
  const transport = readSingle(parsed.rawOptions, "transport");
30909
31714
  const cookies = readOptionalBoolean(parsed.rawOptions, "cookies");
@@ -30934,7 +31739,7 @@ async function buildOperationInput(operation, parsed, runtime) {
30934
31739
  const siteKey = readSingle(parsed.rawOptions, "site-key");
30935
31740
  const pageUrl = readSingle(parsed.rawOptions, "page-url");
30936
31741
  if (provider === void 0 || apiKey === void 0) {
30937
- throw new Error('captcha solve requires "--provider" and "--api-key".');
31742
+ throw new CliError("missing_arguments", 'captcha solve requires "--provider" and "--api-key".', CLI_USAGE_HINTS[operation]);
30938
31743
  }
30939
31744
  return {
30940
31745
  provider: readCaptchaProvider(provider),
@@ -30964,7 +31769,7 @@ async function buildOperationInput(operation, parsed, runtime) {
30964
31769
  case "scripts.beautify":
30965
31770
  case "scripts.deobfuscate": {
30966
31771
  if (parsed.rest[0] === void 0) {
30967
- throw new Error(`${parsed.command.join(" ")} requires an artifact id.`);
31772
+ throw new CliError("missing_arguments", `${parsed.command.join(" ")} requires an artifact id.`, CLI_USAGE_HINTS[operation]);
30968
31773
  }
30969
31774
  const persist = readOptionalBoolean(parsed.rawOptions, "persist");
30970
31775
  return {
@@ -30974,7 +31779,7 @@ async function buildOperationInput(operation, parsed, runtime) {
30974
31779
  }
30975
31780
  case "scripts.sandbox":
30976
31781
  if (parsed.rest[0] === void 0) {
30977
- throw new Error("scripts sandbox requires an artifact id.");
31782
+ throw new CliError("missing_arguments", "scripts sandbox requires an artifact id.", CLI_USAGE_HINTS[operation]);
30978
31783
  }
30979
31784
  {
30980
31785
  const fidelity = readSingle(parsed.rawOptions, "fidelity");
@@ -31023,14 +31828,14 @@ async function buildOperationInput(operation, parsed, runtime) {
31023
31828
  case "interaction.get":
31024
31829
  case "interaction.replay":
31025
31830
  if (parsed.rest[0] === void 0) {
31026
- throw new Error(`${parsed.command.join(" ")} requires a trace id.`);
31831
+ throw new CliError("missing_arguments", `${parsed.command.join(" ")} requires a trace id.`, CLI_USAGE_HINTS[operation]);
31027
31832
  }
31028
31833
  return {
31029
31834
  traceId: parsed.rest[0]
31030
31835
  };
31031
31836
  case "interaction.diff":
31032
31837
  if (parsed.rest[0] === void 0 || parsed.rest[1] === void 0) {
31033
- throw new Error("interaction diff requires two trace ids.");
31838
+ throw new CliError("missing_arguments", "interaction diff requires two trace ids.", CLI_USAGE_HINTS[operation]);
31034
31839
  }
31035
31840
  return {
31036
31841
  leftTraceId: parsed.rest[0],
@@ -31038,7 +31843,7 @@ async function buildOperationInput(operation, parsed, runtime) {
31038
31843
  };
31039
31844
  case "artifact.read":
31040
31845
  if (parsed.rest[0] === void 0) {
31041
- throw new Error("artifact read requires an artifact id.");
31846
+ throw new CliError("missing_arguments", "artifact read requires an artifact id.", CLI_USAGE_HINTS[operation]);
31042
31847
  }
31043
31848
  return {
31044
31849
  artifactId: parsed.rest[0]
@@ -31046,7 +31851,8 @@ async function buildOperationInput(operation, parsed, runtime) {
31046
31851
  case "session.close":
31047
31852
  return {};
31048
31853
  default:
31049
- throw new Error(
31854
+ throw new CliError(
31855
+ "unsupported_operation",
31050
31856
  `${operation} does not have a direct CLI input shape. Use a supported command or the SDK.`
31051
31857
  );
31052
31858
  }
@@ -31063,7 +31869,7 @@ function buildElementTargetInput(parsed, verb) {
31063
31869
  kind: "element",
31064
31870
  element
31065
31871
  },
31066
- ...persist === void 0 ? {} : { persist },
31872
+ persist,
31067
31873
  ...captureNetwork === void 0 ? {} : { captureNetwork }
31068
31874
  };
31069
31875
  }
@@ -31093,7 +31899,7 @@ function buildComputerExecuteInput(parsed) {
31093
31899
  }
31094
31900
  case "type":
31095
31901
  if (parsed.rest[0] === void 0) {
31096
- throw new Error("computer type requires text.");
31902
+ throw new CliError("missing_arguments", "computer type requires text.");
31097
31903
  }
31098
31904
  return {
31099
31905
  action: {
@@ -31104,7 +31910,7 @@ function buildComputerExecuteInput(parsed) {
31104
31910
  };
31105
31911
  case "key": {
31106
31912
  if (parsed.rest[0] === void 0) {
31107
- throw new Error("computer key requires a key.");
31913
+ throw new CliError("missing_arguments", "computer key requires a key.");
31108
31914
  }
31109
31915
  const modifiers = readKeyModifiers(readSingle(parsed.rawOptions, "modifiers"));
31110
31916
  return {
@@ -31120,7 +31926,7 @@ function buildComputerExecuteInput(parsed) {
31120
31926
  const dx = readOptionalNumber(parsed.rawOptions, "dx");
31121
31927
  const dy = readOptionalNumber(parsed.rawOptions, "dy");
31122
31928
  if (dx === void 0 || dy === void 0) {
31123
- throw new Error('computer scroll requires "--dx" and "--dy".');
31929
+ throw new CliError("missing_arguments", 'computer scroll requires "--dx" and "--dy".');
31124
31930
  }
31125
31931
  return {
31126
31932
  action: {
@@ -31175,49 +31981,50 @@ function buildComputerExecuteInput(parsed) {
31175
31981
  }
31176
31982
  };
31177
31983
  default:
31178
- throw new Error(`Unknown computer command: ${parsed.command.join(" ")}`);
31984
+ throw new CliError("unknown_command", `Unknown computer command: ${parsed.command.join(" ")}`);
31179
31985
  }
31180
31986
  }
31181
31987
  async function resolvePageRefByIndex(runtime, index) {
31182
31988
  const pages = (await runtime.listPages({})).pages;
31183
31989
  const page = pages[index - 1];
31184
31990
  if (page === void 0) {
31185
- throw new Error(`tab ${String(index)} does not exist.`);
31991
+ throw new CliError("invalid_value", `tab ${String(index)} does not exist.`);
31186
31992
  }
31187
31993
  return page.pageRef;
31188
31994
  }
31189
31995
  function readRequiredPositiveInteger(value, message) {
31190
31996
  const parsed = readRequiredNumber(value, message);
31191
31997
  if (!Number.isInteger(parsed) || parsed < 1) {
31192
- throw new Error(message);
31998
+ throw new CliError("missing_arguments", message);
31193
31999
  }
31194
32000
  return parsed;
31195
32001
  }
31196
32002
  function readRequiredNumber(value, message) {
31197
32003
  if (value === void 0) {
31198
- throw new Error(message);
32004
+ throw new CliError("missing_arguments", message);
31199
32005
  }
31200
32006
  const parsed = Number(value);
31201
32007
  if (!Number.isFinite(parsed)) {
31202
- throw new Error(message);
32008
+ throw new CliError("missing_arguments", message);
31203
32009
  }
31204
32010
  return parsed;
31205
32011
  }
31206
32012
  function readSingleDirection(value) {
31207
32013
  if (value === void 0 || !SCROLL_DIRECTIONS.has(value)) {
31208
- throw new Error("scroll requires a direction: up, down, left, or right.");
32014
+ throw new CliError("missing_arguments", "scroll requires a direction: up, down, left, or right.");
31209
32015
  }
31210
32016
  return value;
31211
32017
  }
31212
32018
  function readClickButton(value) {
31213
32019
  if (value === void 0 || !CLICK_BUTTONS.has(value)) {
31214
- throw new Error('Expected "--button" to be one of: left, middle, right.');
32020
+ throw new CliError("invalid_value", 'Expected "--button" to be one of: left, middle, right.');
31215
32021
  }
31216
32022
  return value;
31217
32023
  }
31218
32024
  function readFetchTransport(value) {
31219
32025
  if (value === void 0 || !FETCH_TRANSPORTS.has(value)) {
31220
- throw new Error(
32026
+ throw new CliError(
32027
+ "invalid_value",
31221
32028
  'Expected "--transport" to be one of: auto, direct, matched-tls, context, page.'
31222
32029
  );
31223
32030
  }
@@ -31225,31 +32032,31 @@ function readFetchTransport(value) {
31225
32032
  }
31226
32033
  function readCaptchaProvider(value) {
31227
32034
  if (value === void 0 || !CAPTCHA_PROVIDERS.has(value)) {
31228
- throw new Error('Expected "--provider" to be one of: 2captcha, capsolver.');
32035
+ throw new CliError("invalid_value", 'Expected "--provider" to be one of: 2captcha, capsolver.');
31229
32036
  }
31230
32037
  return value;
31231
32038
  }
31232
32039
  function readCaptchaType(value) {
31233
32040
  if (value === void 0 || !CAPTCHA_TYPES.has(value)) {
31234
- throw new Error('Expected "--type" to be one of: recaptcha-v2, hcaptcha, turnstile.');
32041
+ throw new CliError("invalid_value", 'Expected "--type" to be one of: recaptcha-v2, hcaptcha, turnstile.');
31235
32042
  }
31236
32043
  return value;
31237
32044
  }
31238
32045
  function readSandboxFidelity(value) {
31239
32046
  if (value === void 0 || !SANDBOX_FIDELITIES.has(value)) {
31240
- throw new Error('Expected "--fidelity" to be one of: minimal, standard, full.');
32047
+ throw new CliError("invalid_value", 'Expected "--fidelity" to be one of: minimal, standard, full.');
31241
32048
  }
31242
32049
  return value;
31243
32050
  }
31244
32051
  function readSandboxClockMode(value) {
31245
32052
  if (value === void 0 || !SANDBOX_CLOCK_MODES.has(value)) {
31246
- throw new Error('Expected "--clock" to be one of: real, manual.');
32053
+ throw new CliError("invalid_value", 'Expected "--clock" to be one of: real, manual.');
31247
32054
  }
31248
32055
  return value;
31249
32056
  }
31250
32057
  function readScreenshotFormat(value) {
31251
32058
  if (value === void 0 || !SCREENSHOT_FORMATS.has(value)) {
31252
- throw new Error('Expected "--format" to be one of: png, jpeg, webp.');
32059
+ throw new CliError("invalid_value", 'Expected "--format" to be one of: png, jpeg, webp.');
31253
32060
  }
31254
32061
  return value;
31255
32062
  }
@@ -31260,7 +32067,7 @@ function readKeyModifiers(value) {
31260
32067
  }
31261
32068
  for (const modifier of modifiers) {
31262
32069
  if (!KEY_MODIFIERS.has(modifier)) {
31263
- throw new Error('Expected "--modifiers" to contain only: Shift, Control, Alt, Meta.');
32070
+ throw new CliError("invalid_value", 'Expected "--modifiers" to contain only: Shift, Control, Alt, Meta.');
31264
32071
  }
31265
32072
  }
31266
32073
  return [...new Set(modifiers)];
@@ -31268,30 +32075,22 @@ function readKeyModifiers(value) {
31268
32075
  function readPersistKey(parsed, verb) {
31269
32076
  const value = readSingle(parsed.rawOptions, "persist");
31270
32077
  if (value === void 0) {
31271
- return void 0;
31272
- }
31273
- if (value === "true" || value === "false") {
31274
- throw new Error(`${verb} requires "--persist <key>" when using --persist.`);
31275
- }
31276
- if (verb === "scroll" && readOptionalNumber(parsed.rawOptions, "element") === void 0) {
31277
- throw new Error('scroll requires "--element <n>" when using "--persist <key>".');
31278
- }
31279
- return value;
31280
- }
31281
- function readExtractPersistKey(parsed) {
31282
- const value = readSingle(parsed.rawOptions, "persist");
31283
- if (value === void 0) {
31284
- return void 0;
32078
+ throw new CliError("missing_arguments", `${verb} requires "--persist <key>".`);
31285
32079
  }
31286
32080
  if (value === "true" || value === "false") {
31287
- throw new Error('extract requires "--persist <key>" when using --persist.');
32081
+ throw new CliError("missing_arguments", `${verb} requires "--persist <key>".`);
31288
32082
  }
31289
32083
  return value;
31290
32084
  }
31291
32085
  function parseRequiredJsonObjectArgument(value, label) {
31292
- const parsed = JSON.parse(value);
32086
+ let parsed;
32087
+ try {
32088
+ parsed = JSON.parse(value);
32089
+ } catch {
32090
+ throw new CliError("invalid_value", `${label} contains invalid JSON.`);
32091
+ }
31293
32092
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
31294
- throw new Error(`${label} must be a JSON object.`);
32093
+ throw new CliError("invalid_value", `${label} must be a JSON object.`);
31295
32094
  }
31296
32095
  return parsed;
31297
32096
  }
@@ -34269,7 +35068,7 @@ async function handleViewCommand(parsed, options = {}) {
34269
35068
  return;
34270
35069
  }
34271
35070
  if (subcommand !== void 0) {
34272
- throw new Error(`Unknown view command: view ${subcommand}`);
35071
+ throw new CliError("unknown_command", `Unknown view command: view ${subcommand}`);
34273
35072
  }
34274
35073
  if (parsed.options.localViewMode !== void 0) {
34275
35074
  const preference = await setLocalViewMode(parsed.options.localViewMode);
@@ -34317,7 +35116,10 @@ async function resolveWorkspaceSessionId(input) {
34317
35116
  }
34318
35117
  function assertNoViewPreferenceFlag(parsed) {
34319
35118
  if (parsed.options.localViewMode !== void 0) {
34320
- throw new Error("View preference flags cannot be combined with this subcommand.");
35119
+ throw new CliError(
35120
+ "invalid_option",
35121
+ "View preference flags cannot be combined with this subcommand."
35122
+ );
34321
35123
  }
34322
35124
  }
34323
35125
  function writeViewOutput(parsed, value) {
@@ -34402,10 +35204,10 @@ async function main() {
34402
35204
  }
34403
35205
  const operation = resolveOperation(parsed.command);
34404
35206
  if (!operation) {
34405
- throw new Error(`Unknown command: ${parsed.command.join(" ")}`);
35207
+ throw new CliError("unknown_command", `Unknown command: ${parsed.command.join(" ")}`);
34406
35208
  }
34407
35209
  if (parsed.options.workspace === void 0) {
34408
- throw new Error('Stateful commands require "--workspace <id>" or OPENSTEER_WORKSPACE.');
35210
+ throw new CliError("missing_workspace", 'Stateful commands require "--workspace <id>" or OPENSTEER_WORKSPACE.');
34409
35211
  }
34410
35212
  const { engineName, provider, runtimeProvider } = resolveCliRuntimeSelection(parsed);
34411
35213
  if (operation === "session.close") {
@@ -34427,24 +35229,39 @@ async function main() {
34427
35229
  let renderOperation = operation;
34428
35230
  try {
34429
35231
  const input = await buildOperationInput(operation, parsed, runtime);
34430
- result = await dispatchSemanticOperation(runtime, operation, input);
35232
+ const rawResult = await dispatchSemanticOperation(runtime, operation, input);
34431
35233
  if (parsed.command[0] === "tab" && operation !== "page.list") {
34432
35234
  renderOperation = "page.list";
34433
35235
  result = await runtime.listPages({});
35236
+ } else {
35237
+ result = rawResult;
35238
+ }
35239
+ let warning;
35240
+ if (result !== null && typeof result === "object" && "warning" in result) {
35241
+ const { warning: w, ...rest } = result;
35242
+ if (typeof w === "string") {
35243
+ warning = w;
35244
+ result = rest;
35245
+ }
34434
35246
  }
34435
35247
  process4__default.default.stdout.write(renderOperationOutput(renderOperation, result, input));
35248
+ if (warning !== void 0) {
35249
+ emitWarning(warning);
35250
+ }
34436
35251
  } finally {
34437
35252
  await runtime.disconnect().catch(() => void 0);
34438
35253
  }
34439
35254
  }
34440
35255
  async function handleExecCommand(parsed) {
34441
35256
  if (parsed.options.workspace === void 0) {
34442
- throw new Error('exec requires "--workspace <id>" or OPENSTEER_WORKSPACE.');
35257
+ throw new CliError("missing_workspace", 'exec requires "--workspace <id>" or OPENSTEER_WORKSPACE.');
34443
35258
  }
34444
35259
  const expression = parsed.rest.join(" ");
34445
35260
  if (!expression) {
34446
- throw new Error(
34447
- `exec requires an expression. Example: exec "await this.evaluate('document.title')"`
35261
+ throw new CliError(
35262
+ "missing_arguments",
35263
+ "exec requires an expression.",
35264
+ "opensteer exec <expression>"
34448
35265
  );
34449
35266
  }
34450
35267
  const { engineName, runtimeProvider } = resolveCliRuntimeSelection(parsed);
@@ -34480,8 +35297,10 @@ async function handleBrowserCommand(parsed) {
34480
35297
  if (subcommand === "inspect") {
34481
35298
  const endpoint = parsed.options.attachEndpoint ?? parsed.rest[0];
34482
35299
  if (!endpoint) {
34483
- throw new Error(
34484
- 'browser inspect requires "--attach-endpoint <url>" or a positional endpoint.'
35300
+ throw new CliError(
35301
+ "missing_arguments",
35302
+ 'browser inspect requires "--attach-endpoint <url>" or a positional endpoint.',
35303
+ "opensteer browser inspect <endpoint>"
34485
35304
  );
34486
35305
  }
34487
35306
  const result = await inspectCdpEndpoint({
@@ -34494,7 +35313,8 @@ async function handleBrowserCommand(parsed) {
34494
35313
  return;
34495
35314
  }
34496
35315
  if (parsed.options.workspace === void 0) {
34497
- throw new Error(
35316
+ throw new CliError(
35317
+ "missing_workspace",
34498
35318
  'Browser workspace commands require "--workspace <id>" or OPENSTEER_WORKSPACE.'
34499
35319
  );
34500
35320
  }
@@ -34517,7 +35337,11 @@ async function handleBrowserCommand(parsed) {
34517
35337
  case "clone": {
34518
35338
  const sourceUserDataDir = parsed.options.sourceUserDataDir;
34519
35339
  if (!sourceUserDataDir) {
34520
- throw new Error('browser clone requires "--source-user-data-dir <path>".');
35340
+ throw new CliError(
35341
+ "missing_arguments",
35342
+ 'browser clone requires "--source-user-data-dir <path>".',
35343
+ "opensteer browser clone --source-user-data-dir <path>"
35344
+ );
34521
35345
  }
34522
35346
  const result = await manager.clonePersistentBrowser({
34523
35347
  sourceUserDataDir,
@@ -34540,7 +35364,7 @@ async function handleBrowserCommand(parsed) {
34540
35364
  return;
34541
35365
  }
34542
35366
  default:
34543
- throw new Error(`Unknown browser command: ${parsed.command.join(" ")}`);
35367
+ throw new CliError("unknown_command", `Unknown browser command: ${parsed.command.join(" ")}`);
34544
35368
  }
34545
35369
  }
34546
35370
  async function handleCloseCommand(parsed, engineName, providerMode, runtimeProvider) {
@@ -34573,23 +35397,27 @@ async function handleCloseCommand(parsed, engineName, providerMode, runtimeProvi
34573
35397
  }
34574
35398
  async function handleRecordCommandEntry(parsed) {
34575
35399
  if (parsed.options.workspace === void 0) {
34576
- throw new Error('record requires "--workspace <id>" or OPENSTEER_WORKSPACE.');
35400
+ throw new CliError("missing_workspace", 'record requires "--workspace <id>" or OPENSTEER_WORKSPACE.');
34577
35401
  }
34578
35402
  const url = parsed.options.url ?? parsed.rest[0];
34579
35403
  if (url === void 0) {
34580
- throw new Error('record requires "--url <value>" or a positional URL.');
35404
+ throw new CliError(
35405
+ "missing_arguments",
35406
+ 'record requires "--url <value>" or a positional URL.',
35407
+ "opensteer record --url <url> --workspace <id>"
35408
+ );
34581
35409
  }
34582
35410
  const provider = resolveCliProvider(parsed);
34583
35411
  assertCloudCliOptionsMatchProvider(parsed, provider.mode);
34584
35412
  const engineName = resolveCliEngineName(parsed);
34585
35413
  if (engineName !== "playwright") {
34586
- throw new Error("record requires engine=playwright.");
35414
+ throw new CliError("config_conflict", "record requires engine=playwright.");
34587
35415
  }
34588
35416
  const rootDir = process4__default.default.cwd();
34589
35417
  const recordBrowser = parsed.options.browser;
34590
35418
  if (provider.mode === "cloud") {
34591
35419
  if (typeof recordBrowser === "object") {
34592
- throw new Error('record does not support browser.mode="attach".');
35420
+ throw new CliError("config_conflict", 'record does not support browser.mode="attach".');
34593
35421
  }
34594
35422
  const runtimeProvider = buildCliRuntimeProvider(parsed, provider.mode);
34595
35423
  const runtimeConfig = resolveOpensteerRuntimeConfig({
@@ -34609,10 +35437,10 @@ async function handleRecordCommandEntry(parsed) {
34609
35437
  return;
34610
35438
  }
34611
35439
  if (parsed.options.launch?.headless === true) {
34612
- throw new Error('record requires a headed browser. Remove "--headless true".');
35440
+ throw new CliError("config_conflict", 'record requires a headed browser. Remove "--headless true".');
34613
35441
  }
34614
35442
  if (typeof recordBrowser === "object") {
34615
- throw new Error('record does not support browser.mode="attach".');
35443
+ throw new CliError("config_conflict", 'record does not support browser.mode="attach".');
34616
35444
  }
34617
35445
  const launch = {
34618
35446
  ...parsed.options.launch ?? {},
@@ -34681,7 +35509,7 @@ function resolveCliBootstrapAction(argv) {
34681
35509
  }
34682
35510
  function buildCliBrowserProfile(parsed) {
34683
35511
  if (parsed.options.cloudProfileReuseIfActive === true && parsed.options.cloudProfileId === void 0) {
34684
- throw new Error('"--cloud-profile-reuse-if-active" requires "--cloud-profile-id <id>".');
35512
+ throw new CliError("invalid_option", '"--cloud-profile-reuse-if-active" requires "--cloud-profile-id <id>".');
34685
35513
  }
34686
35514
  return parsed.options.cloudProfileId === void 0 ? void 0 : {
34687
35515
  profileId: parsed.options.cloudProfileId,
@@ -34742,7 +35570,8 @@ function buildCliRuntimeProvider(parsed, providerMode) {
34742
35570
  }
34743
35571
  function assertCloudCliOptionsMatchProvider(parsed, providerMode) {
34744
35572
  if (providerMode !== "cloud" && (parsed.options.cloudBaseUrl !== void 0 || parsed.options.cloudApiKey !== void 0 || parsed.options.cloudAppBaseUrl !== void 0 || parsed.options.cloudProfileId !== void 0 || parsed.options.cloudProfileReuseIfActive === true)) {
34745
- throw new Error(
35573
+ throw new CliError(
35574
+ "config_conflict",
34746
35575
  'Cloud-specific options require provider=cloud. Set "--provider cloud" or OPENSTEER_PROVIDER=cloud.'
34747
35576
  );
34748
35577
  }
@@ -34776,17 +35605,7 @@ function printVersion() {
34776
35605
  `);
34777
35606
  }
34778
35607
  main().catch((error) => {
34779
- const payload = error instanceof Error ? {
34780
- error: {
34781
- name: error.name,
34782
- message: error.message
34783
- }
34784
- } : {
34785
- error: {
34786
- name: "Error",
34787
- message: String(error)
34788
- }
34789
- };
35608
+ const payload = formatCliErrorOutput(error);
34790
35609
  process4__default.default.stderr.write(`${JSON.stringify(payload)}
34791
35610
  `);
34792
35611
  process4__default.default.exitCode = 1;