opensteer 0.9.5 → 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.
package/dist/cli/bin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { runLocalViewService } from '../chunk-ZRF7WMS3.js';
3
- import { createOpensteerSemanticRuntime, dispatchSemanticOperation, loadEnvironment, normalizeOpensteerProviderMode, resolveOpensteerRuntimeConfig, assertProviderSupportsEngine, resolveOpensteerProvider, requireCloudAppBaseUrl, CloudSessionProxy, FlowRecorderCollector, generateReplayScript, OpensteerCloudClient } from '../chunk-7LQL5YUR.js';
4
- import { OpensteerBrowserManager, stopLocalViewService, setLocalViewMode, ensureLocalViewServiceRunning, buildLocalViewSessionUrl, resolveOpensteerEngineName, resolveFilesystemWorkspacePath } from '../chunk-GSCQQKZZ.js';
2
+ import { runLocalViewService } from '../chunk-3I3A5OLB.js';
3
+ import { createOpensteerSemanticRuntime, dispatchSemanticOperation, isBrowserCoreError, loadEnvironment, normalizeOpensteerProviderMode, resolveOpensteerRuntimeConfig, assertProviderSupportsEngine, resolveOpensteerProvider, requireCloudAppBaseUrl, CloudSessionProxy, FlowRecorderCollector, generateReplayScript, OpensteerCloudClient } from '../chunk-BVRIPCWA.js';
4
+ import { isOpensteerProtocolError, OpensteerBrowserManager, stopLocalViewService, setLocalViewMode, ensureLocalViewServiceRunning, buildLocalViewSessionUrl, resolveOpensteerEngineName, resolveFilesystemWorkspacePath } from '../chunk-3XBQRZZC.js';
5
5
  import { discoverLocalCdpBrowsers, inspectCdpEndpoint, readPersistedLocalBrowserSessionRecord, isProcessRunning, buildLocalViewSessionId, pathExists, readPersistedCloudSessionRecord } from '../chunk-T5P2QGZ3.js';
6
6
  import process4 from 'process';
7
7
  import { mkdir, writeFile } from 'fs/promises';
@@ -14,13 +14,115 @@ import { fileURLToPath } from 'url';
14
14
 
15
15
  // package.json
16
16
  var package_default = {
17
- version: "0.9.5"};
17
+ version: "0.9.6"};
18
18
 
19
19
  // src/cli/env-loader.ts
20
20
  async function loadCliEnvironment(cwd) {
21
21
  loadEnvironment(cwd);
22
22
  }
23
23
 
24
+ // src/cli/errors.ts
25
+ var CliError = class extends Error {
26
+ type;
27
+ usage;
28
+ constructor(type, message, usage) {
29
+ super(message);
30
+ this.name = "CliError";
31
+ this.type = type;
32
+ this.usage = usage;
33
+ }
34
+ format() {
35
+ return this.usage === void 0 ? this.message : `${this.message}
36
+ Usage: ${this.usage}`;
37
+ }
38
+ };
39
+ function isCliError(value) {
40
+ return value instanceof CliError;
41
+ }
42
+ function formatCliErrorOutput(error) {
43
+ if (isCliError(error)) {
44
+ return {
45
+ success: false,
46
+ error: error.format(),
47
+ type: error.type
48
+ };
49
+ }
50
+ if (isOpensteerProtocolError(error)) {
51
+ return {
52
+ success: false,
53
+ error: error.message,
54
+ code: error.code,
55
+ retriable: error.retriable
56
+ };
57
+ }
58
+ if (isBrowserCoreError(error)) {
59
+ return {
60
+ success: false,
61
+ error: error.message,
62
+ code: error.code,
63
+ retriable: error.retriable
64
+ };
65
+ }
66
+ if (error instanceof Error && "opensteerError" in error) {
67
+ const oe = error.opensteerError;
68
+ return {
69
+ success: false,
70
+ error: oe.message,
71
+ code: oe.code,
72
+ retriable: oe.retriable
73
+ };
74
+ }
75
+ if (error instanceof SyntaxError) {
76
+ return {
77
+ success: false,
78
+ error: error.message,
79
+ type: "invalid_value"
80
+ };
81
+ }
82
+ return {
83
+ success: false,
84
+ error: error instanceof Error ? error.message : String(error)
85
+ };
86
+ }
87
+ function emitWarning(warning) {
88
+ process.stderr.write(`${JSON.stringify({ warning })}
89
+ `);
90
+ }
91
+ var CLI_USAGE_HINTS = {
92
+ "session.open": "opensteer open <url> [--workspace <id>]",
93
+ "page.goto": "opensteer goto <url>",
94
+ "page.evaluate": "opensteer evaluate <script>",
95
+ "page.add-init-script": "opensteer init-script <script>",
96
+ "dom.click": "opensteer click <element> --persist <key>",
97
+ "dom.hover": "opensteer hover <element> --persist <key>",
98
+ "dom.input": "opensteer input <element> <text> --persist <key>",
99
+ "dom.scroll": "opensteer scroll <direction> <amount> --persist <key>",
100
+ "dom.extract": "opensteer extract <template> --persist <key>",
101
+ "network.detail": "opensteer network detail <recordId>",
102
+ "session.fetch": "opensteer fetch <url>",
103
+ "captcha.solve": "opensteer captcha solve --provider <name> --api-key <key>",
104
+ "scripts.capture": "opensteer scripts capture",
105
+ "scripts.beautify": "opensteer scripts beautify <artifactId>",
106
+ "scripts.deobfuscate": "opensteer scripts deobfuscate <artifactId>",
107
+ "scripts.sandbox": "opensteer scripts sandbox <artifactId>",
108
+ "interaction.capture": "opensteer interaction capture",
109
+ "interaction.get": "opensteer interaction get <traceId>",
110
+ "interaction.replay": "opensteer interaction replay <traceId>",
111
+ "interaction.diff": "opensteer interaction diff <leftTraceId> <rightTraceId>",
112
+ "artifact.read": "opensteer artifact read <artifactId>",
113
+ "computer.click": "opensteer computer click <x> <y>",
114
+ "computer.type": "opensteer computer type <text>",
115
+ "computer.key": "opensteer computer key <key>",
116
+ "computer.scroll": "opensteer computer scroll <x> <y> --dx <n> --dy <n>",
117
+ "computer.move": "opensteer computer move <x> <y>",
118
+ "computer.drag": "opensteer computer drag <x1> <y1> <x2> <y2>",
119
+ "computer.screenshot": "opensteer computer screenshot",
120
+ exec: "opensteer exec <expression>",
121
+ record: "opensteer record --url <url> --workspace <id>",
122
+ "browser.inspect": "opensteer browser inspect <endpoint>",
123
+ "browser.clone": "opensteer browser clone --source-user-data-dir <path>"
124
+ };
125
+
24
126
  // src/cli/help.ts
25
127
  function getHelpText() {
26
128
  return `Opensteer CLI
@@ -279,7 +381,7 @@ function parseCommandLine(argv) {
279
381
  const key = token.slice(2, separator === -1 ? void 0 : separator);
280
382
  const spec = CLI_OPTION_SPECS[key];
281
383
  if (spec === void 0) {
282
- throw new Error(`Unknown option: --${key}.`);
384
+ throw new CliError("unknown_option", `Unknown option: --${key}.`);
283
385
  }
284
386
  if (separator !== -1) {
285
387
  rawOptions.set(key, [...rawOptions.get(key) ?? [], token.slice(separator + 1)]);
@@ -308,7 +410,8 @@ function parseCommandLine(argv) {
308
410
  continue;
309
411
  }
310
412
  if (next === void 0 || next.startsWith("--")) {
311
- throw new Error(
413
+ throw new CliError(
414
+ "missing_arguments",
312
415
  `Option "--${key}" requires a value.${next?.startsWith("--") === true ? ` Use "--${key}=<value>" when the value begins with "--".` : ``}`
313
416
  );
314
417
  }
@@ -354,10 +457,14 @@ function parseCommandLine(argv) {
354
457
  const autoLocalView = readOptionalBoolean(rawOptions, "auto");
355
458
  const noAutoLocalView = readOptionalBoolean(rawOptions, "no-auto");
356
459
  if (autoLocalView === true && noAutoLocalView === true) {
357
- throw new Error('Options "--auto" and "--no-auto" cannot be combined.');
460
+ throw new CliError("invalid_option", 'Options "--auto" and "--no-auto" cannot be combined.');
358
461
  }
359
462
  if (command[0] !== "view" && (autoLocalView !== void 0 || noAutoLocalView !== void 0)) {
360
- throw new Error('Options "--auto" and "--no-auto" are only supported with "view".');
463
+ throw new CliError(
464
+ "invalid_option",
465
+ 'Options "--auto" and "--no-auto" are only supported with "view".',
466
+ "opensteer view --auto"
467
+ );
361
468
  }
362
469
  const global = readOptionalBoolean(rawOptions, "global");
363
470
  const yes = readOptionalBoolean(rawOptions, "yes");
@@ -413,7 +520,7 @@ function parseKeyValueList(values) {
413
520
  values.map((entry) => {
414
521
  const separator = entry.indexOf("=");
415
522
  if (separator <= 0) {
416
- throw new Error(`Expected NAME=VALUE, received "${entry}".`);
523
+ throw new CliError("invalid_value", `Expected NAME=VALUE, received "${entry}".`);
417
524
  }
418
525
  return [entry.slice(0, separator), entry.slice(separator + 1)];
419
526
  })
@@ -444,7 +551,7 @@ function readOptionalBoolean(options, name) {
444
551
  if (value === "false") {
445
552
  return false;
446
553
  }
447
- throw new Error(`Option "--${name}" must be true or false.`);
554
+ throw new CliError("invalid_value", `Option "--${name}" must be true or false.`);
448
555
  }
449
556
  function readOptionalNumber(options, name) {
450
557
  const value = readSingle(options, name);
@@ -453,13 +560,20 @@ function readOptionalNumber(options, name) {
453
560
  }
454
561
  const parsed = Number(value);
455
562
  if (!Number.isFinite(parsed)) {
456
- throw new Error(`Option "--${name}" must be a number.`);
563
+ throw new CliError("invalid_value", `Option "--${name}" must be a number.`);
457
564
  }
458
565
  return parsed;
459
566
  }
460
567
  function readJsonValue(options, name) {
461
568
  const value = readSingle(options, name);
462
- return value === void 0 ? void 0 : JSON.parse(value);
569
+ if (value === void 0) {
570
+ return void 0;
571
+ }
572
+ try {
573
+ return JSON.parse(value);
574
+ } catch {
575
+ throw new CliError("invalid_value", `Option "--${name}" contains invalid JSON.`);
576
+ }
463
577
  }
464
578
  function readJsonObject(options, name) {
465
579
  const parsed = readJsonValue(options, name);
@@ -467,7 +581,7 @@ function readJsonObject(options, name) {
467
581
  return void 0;
468
582
  }
469
583
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
470
- throw new Error(`Option "--${name}" must be a JSON object.`);
584
+ throw new CliError("invalid_value", `Option "--${name}" must be a JSON object.`);
471
585
  }
472
586
  return parsed;
473
587
  }
@@ -477,7 +591,7 @@ function readJsonArray(options, name) {
477
591
  return void 0;
478
592
  }
479
593
  if (!Array.isArray(parsed)) {
480
- throw new Error(`Option "--${name}" must be a JSON array.`);
594
+ throw new CliError("invalid_value", `Option "--${name}" must be a JSON array.`);
481
595
  }
482
596
  return parsed;
483
597
  }
@@ -497,7 +611,7 @@ async function buildOperationInput(operation, parsed, runtime) {
497
611
  case "session.open": {
498
612
  const url = parsed.rest[0];
499
613
  if (url === void 0) {
500
- throw new Error("open requires a URL.");
614
+ throw new CliError("missing_arguments", "open requires a URL.", CLI_USAGE_HINTS[operation]);
501
615
  }
502
616
  return {
503
617
  url,
@@ -530,7 +644,7 @@ async function buildOperationInput(operation, parsed, runtime) {
530
644
  }
531
645
  case "page.goto": {
532
646
  if (parsed.rest[0] === void 0) {
533
- throw new Error("goto requires a URL.");
647
+ throw new CliError("missing_arguments", "goto requires a URL.", CLI_USAGE_HINTS[operation]);
534
648
  }
535
649
  const captureNetwork = readSingle(parsed.rawOptions, "capture-network");
536
650
  return {
@@ -542,14 +656,14 @@ async function buildOperationInput(operation, parsed, runtime) {
542
656
  return parsed.rest[0] === void 0 ? {} : { mode: parsed.rest[0] };
543
657
  case "page.evaluate":
544
658
  if (parsed.rest[0] === void 0) {
545
- throw new Error("evaluate requires a script.");
659
+ throw new CliError("missing_arguments", "evaluate requires a script.", CLI_USAGE_HINTS[operation]);
546
660
  }
547
661
  return {
548
662
  script: joinRest(parsed.rest, 0)
549
663
  };
550
664
  case "page.add-init-script":
551
665
  if (parsed.rest[0] === void 0) {
552
- throw new Error("init-script requires a script.");
666
+ throw new CliError("missing_arguments", "init-script requires a script.", CLI_USAGE_HINTS[operation]);
553
667
  }
554
668
  return {
555
669
  script: joinRest(parsed.rest, 0)
@@ -565,7 +679,7 @@ async function buildOperationInput(operation, parsed, runtime) {
565
679
  return buildElementTargetInput(parsed, "hover");
566
680
  case "dom.input": {
567
681
  if (parsed.rest[1] === void 0) {
568
- throw new Error("input requires an element number and text.");
682
+ throw new CliError("missing_arguments", "input requires an element number and text.", CLI_USAGE_HINTS[operation]);
569
683
  }
570
684
  const pressEnter = readOptionalBoolean(parsed.rawOptions, "press-enter");
571
685
  return {
@@ -596,7 +710,7 @@ async function buildOperationInput(operation, parsed, runtime) {
596
710
  }
597
711
  case "dom.extract": {
598
712
  if (parsed.rest[0] === void 0) {
599
- throw new Error("extract requires a template.");
713
+ throw new CliError("missing_arguments", "extract requires a template.", CLI_USAGE_HINTS[operation]);
600
714
  }
601
715
  const persist = readPersistKey(parsed, "extract");
602
716
  return {
@@ -632,7 +746,7 @@ async function buildOperationInput(operation, parsed, runtime) {
632
746
  }
633
747
  case "network.detail": {
634
748
  if (parsed.rest[0] === void 0) {
635
- throw new Error("network detail requires a record id.");
749
+ throw new CliError("missing_arguments", "network detail requires a record id.", CLI_USAGE_HINTS[operation]);
636
750
  }
637
751
  const probeFlag = readOptionalBoolean(parsed.rawOptions, "probe");
638
752
  return {
@@ -643,7 +757,7 @@ async function buildOperationInput(operation, parsed, runtime) {
643
757
  case "session.fetch": {
644
758
  const url = parsed.rest[0];
645
759
  if (url === void 0) {
646
- throw new Error("fetch requires a URL.");
760
+ throw new CliError("missing_arguments", "fetch requires a URL.", CLI_USAGE_HINTS[operation]);
647
761
  }
648
762
  const bodyJson = readJsonValue(parsed.rawOptions, "body");
649
763
  const bodyText = readSingle(parsed.rawOptions, "body-text");
@@ -651,7 +765,7 @@ async function buildOperationInput(operation, parsed, runtime) {
651
765
  const query = parseKeyValueList(parsed.rawOptions.get("query"));
652
766
  const headers = parseKeyValueList(parsed.rawOptions.get("header"));
653
767
  if (bodyJson !== void 0 && bodyText !== void 0) {
654
- throw new Error('Use either "--body" or "--body-text", not both.');
768
+ throw new CliError("invalid_option", 'Use either "--body" or "--body-text", not both.');
655
769
  }
656
770
  const transport = readSingle(parsed.rawOptions, "transport");
657
771
  const cookies = readOptionalBoolean(parsed.rawOptions, "cookies");
@@ -682,7 +796,7 @@ async function buildOperationInput(operation, parsed, runtime) {
682
796
  const siteKey = readSingle(parsed.rawOptions, "site-key");
683
797
  const pageUrl = readSingle(parsed.rawOptions, "page-url");
684
798
  if (provider === void 0 || apiKey === void 0) {
685
- throw new Error('captcha solve requires "--provider" and "--api-key".');
799
+ throw new CliError("missing_arguments", 'captcha solve requires "--provider" and "--api-key".', CLI_USAGE_HINTS[operation]);
686
800
  }
687
801
  return {
688
802
  provider: readCaptchaProvider(provider),
@@ -712,7 +826,7 @@ async function buildOperationInput(operation, parsed, runtime) {
712
826
  case "scripts.beautify":
713
827
  case "scripts.deobfuscate": {
714
828
  if (parsed.rest[0] === void 0) {
715
- throw new Error(`${parsed.command.join(" ")} requires an artifact id.`);
829
+ throw new CliError("missing_arguments", `${parsed.command.join(" ")} requires an artifact id.`, CLI_USAGE_HINTS[operation]);
716
830
  }
717
831
  const persist = readOptionalBoolean(parsed.rawOptions, "persist");
718
832
  return {
@@ -722,7 +836,7 @@ async function buildOperationInput(operation, parsed, runtime) {
722
836
  }
723
837
  case "scripts.sandbox":
724
838
  if (parsed.rest[0] === void 0) {
725
- throw new Error("scripts sandbox requires an artifact id.");
839
+ throw new CliError("missing_arguments", "scripts sandbox requires an artifact id.", CLI_USAGE_HINTS[operation]);
726
840
  }
727
841
  {
728
842
  const fidelity = readSingle(parsed.rawOptions, "fidelity");
@@ -771,14 +885,14 @@ async function buildOperationInput(operation, parsed, runtime) {
771
885
  case "interaction.get":
772
886
  case "interaction.replay":
773
887
  if (parsed.rest[0] === void 0) {
774
- throw new Error(`${parsed.command.join(" ")} requires a trace id.`);
888
+ throw new CliError("missing_arguments", `${parsed.command.join(" ")} requires a trace id.`, CLI_USAGE_HINTS[operation]);
775
889
  }
776
890
  return {
777
891
  traceId: parsed.rest[0]
778
892
  };
779
893
  case "interaction.diff":
780
894
  if (parsed.rest[0] === void 0 || parsed.rest[1] === void 0) {
781
- throw new Error("interaction diff requires two trace ids.");
895
+ throw new CliError("missing_arguments", "interaction diff requires two trace ids.", CLI_USAGE_HINTS[operation]);
782
896
  }
783
897
  return {
784
898
  leftTraceId: parsed.rest[0],
@@ -786,7 +900,7 @@ async function buildOperationInput(operation, parsed, runtime) {
786
900
  };
787
901
  case "artifact.read":
788
902
  if (parsed.rest[0] === void 0) {
789
- throw new Error("artifact read requires an artifact id.");
903
+ throw new CliError("missing_arguments", "artifact read requires an artifact id.", CLI_USAGE_HINTS[operation]);
790
904
  }
791
905
  return {
792
906
  artifactId: parsed.rest[0]
@@ -794,7 +908,8 @@ async function buildOperationInput(operation, parsed, runtime) {
794
908
  case "session.close":
795
909
  return {};
796
910
  default:
797
- throw new Error(
911
+ throw new CliError(
912
+ "unsupported_operation",
798
913
  `${operation} does not have a direct CLI input shape. Use a supported command or the SDK.`
799
914
  );
800
915
  }
@@ -841,7 +956,7 @@ function buildComputerExecuteInput(parsed) {
841
956
  }
842
957
  case "type":
843
958
  if (parsed.rest[0] === void 0) {
844
- throw new Error("computer type requires text.");
959
+ throw new CliError("missing_arguments", "computer type requires text.");
845
960
  }
846
961
  return {
847
962
  action: {
@@ -852,7 +967,7 @@ function buildComputerExecuteInput(parsed) {
852
967
  };
853
968
  case "key": {
854
969
  if (parsed.rest[0] === void 0) {
855
- throw new Error("computer key requires a key.");
970
+ throw new CliError("missing_arguments", "computer key requires a key.");
856
971
  }
857
972
  const modifiers = readKeyModifiers(readSingle(parsed.rawOptions, "modifiers"));
858
973
  return {
@@ -868,7 +983,7 @@ function buildComputerExecuteInput(parsed) {
868
983
  const dx = readOptionalNumber(parsed.rawOptions, "dx");
869
984
  const dy = readOptionalNumber(parsed.rawOptions, "dy");
870
985
  if (dx === void 0 || dy === void 0) {
871
- throw new Error('computer scroll requires "--dx" and "--dy".');
986
+ throw new CliError("missing_arguments", 'computer scroll requires "--dx" and "--dy".');
872
987
  }
873
988
  return {
874
989
  action: {
@@ -923,49 +1038,50 @@ function buildComputerExecuteInput(parsed) {
923
1038
  }
924
1039
  };
925
1040
  default:
926
- throw new Error(`Unknown computer command: ${parsed.command.join(" ")}`);
1041
+ throw new CliError("unknown_command", `Unknown computer command: ${parsed.command.join(" ")}`);
927
1042
  }
928
1043
  }
929
1044
  async function resolvePageRefByIndex(runtime, index) {
930
1045
  const pages = (await runtime.listPages({})).pages;
931
1046
  const page = pages[index - 1];
932
1047
  if (page === void 0) {
933
- throw new Error(`tab ${String(index)} does not exist.`);
1048
+ throw new CliError("invalid_value", `tab ${String(index)} does not exist.`);
934
1049
  }
935
1050
  return page.pageRef;
936
1051
  }
937
1052
  function readRequiredPositiveInteger(value, message) {
938
1053
  const parsed = readRequiredNumber(value, message);
939
1054
  if (!Number.isInteger(parsed) || parsed < 1) {
940
- throw new Error(message);
1055
+ throw new CliError("missing_arguments", message);
941
1056
  }
942
1057
  return parsed;
943
1058
  }
944
1059
  function readRequiredNumber(value, message) {
945
1060
  if (value === void 0) {
946
- throw new Error(message);
1061
+ throw new CliError("missing_arguments", message);
947
1062
  }
948
1063
  const parsed = Number(value);
949
1064
  if (!Number.isFinite(parsed)) {
950
- throw new Error(message);
1065
+ throw new CliError("missing_arguments", message);
951
1066
  }
952
1067
  return parsed;
953
1068
  }
954
1069
  function readSingleDirection(value) {
955
1070
  if (value === void 0 || !SCROLL_DIRECTIONS.has(value)) {
956
- throw new Error("scroll requires a direction: up, down, left, or right.");
1071
+ throw new CliError("missing_arguments", "scroll requires a direction: up, down, left, or right.");
957
1072
  }
958
1073
  return value;
959
1074
  }
960
1075
  function readClickButton(value) {
961
1076
  if (value === void 0 || !CLICK_BUTTONS.has(value)) {
962
- throw new Error('Expected "--button" to be one of: left, middle, right.');
1077
+ throw new CliError("invalid_value", 'Expected "--button" to be one of: left, middle, right.');
963
1078
  }
964
1079
  return value;
965
1080
  }
966
1081
  function readFetchTransport(value) {
967
1082
  if (value === void 0 || !FETCH_TRANSPORTS.has(value)) {
968
- throw new Error(
1083
+ throw new CliError(
1084
+ "invalid_value",
969
1085
  'Expected "--transport" to be one of: auto, direct, matched-tls, context, page.'
970
1086
  );
971
1087
  }
@@ -973,31 +1089,31 @@ function readFetchTransport(value) {
973
1089
  }
974
1090
  function readCaptchaProvider(value) {
975
1091
  if (value === void 0 || !CAPTCHA_PROVIDERS.has(value)) {
976
- throw new Error('Expected "--provider" to be one of: 2captcha, capsolver.');
1092
+ throw new CliError("invalid_value", 'Expected "--provider" to be one of: 2captcha, capsolver.');
977
1093
  }
978
1094
  return value;
979
1095
  }
980
1096
  function readCaptchaType(value) {
981
1097
  if (value === void 0 || !CAPTCHA_TYPES.has(value)) {
982
- throw new Error('Expected "--type" to be one of: recaptcha-v2, hcaptcha, turnstile.');
1098
+ throw new CliError("invalid_value", 'Expected "--type" to be one of: recaptcha-v2, hcaptcha, turnstile.');
983
1099
  }
984
1100
  return value;
985
1101
  }
986
1102
  function readSandboxFidelity(value) {
987
1103
  if (value === void 0 || !SANDBOX_FIDELITIES.has(value)) {
988
- throw new Error('Expected "--fidelity" to be one of: minimal, standard, full.');
1104
+ throw new CliError("invalid_value", 'Expected "--fidelity" to be one of: minimal, standard, full.');
989
1105
  }
990
1106
  return value;
991
1107
  }
992
1108
  function readSandboxClockMode(value) {
993
1109
  if (value === void 0 || !SANDBOX_CLOCK_MODES.has(value)) {
994
- throw new Error('Expected "--clock" to be one of: real, manual.');
1110
+ throw new CliError("invalid_value", 'Expected "--clock" to be one of: real, manual.');
995
1111
  }
996
1112
  return value;
997
1113
  }
998
1114
  function readScreenshotFormat(value) {
999
1115
  if (value === void 0 || !SCREENSHOT_FORMATS.has(value)) {
1000
- throw new Error('Expected "--format" to be one of: png, jpeg, webp.');
1116
+ throw new CliError("invalid_value", 'Expected "--format" to be one of: png, jpeg, webp.');
1001
1117
  }
1002
1118
  return value;
1003
1119
  }
@@ -1008,7 +1124,7 @@ function readKeyModifiers(value) {
1008
1124
  }
1009
1125
  for (const modifier of modifiers) {
1010
1126
  if (!KEY_MODIFIERS.has(modifier)) {
1011
- throw new Error('Expected "--modifiers" to contain only: Shift, Control, Alt, Meta.');
1127
+ throw new CliError("invalid_value", 'Expected "--modifiers" to contain only: Shift, Control, Alt, Meta.');
1012
1128
  }
1013
1129
  }
1014
1130
  return [...new Set(modifiers)];
@@ -1016,17 +1132,22 @@ function readKeyModifiers(value) {
1016
1132
  function readPersistKey(parsed, verb) {
1017
1133
  const value = readSingle(parsed.rawOptions, "persist");
1018
1134
  if (value === void 0) {
1019
- throw new Error(`${verb} requires "--persist <key>".`);
1135
+ throw new CliError("missing_arguments", `${verb} requires "--persist <key>".`);
1020
1136
  }
1021
1137
  if (value === "true" || value === "false") {
1022
- throw new Error(`${verb} requires "--persist <key>".`);
1138
+ throw new CliError("missing_arguments", `${verb} requires "--persist <key>".`);
1023
1139
  }
1024
1140
  return value;
1025
1141
  }
1026
1142
  function parseRequiredJsonObjectArgument(value, label) {
1027
- const parsed = JSON.parse(value);
1143
+ let parsed;
1144
+ try {
1145
+ parsed = JSON.parse(value);
1146
+ } catch {
1147
+ throw new CliError("invalid_value", `${label} contains invalid JSON.`);
1148
+ }
1028
1149
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1029
- throw new Error(`${label} must be a JSON object.`);
1150
+ throw new CliError("invalid_value", `${label} must be a JSON object.`);
1030
1151
  }
1031
1152
  return parsed;
1032
1153
  }
@@ -2182,7 +2303,7 @@ async function handleViewCommand(parsed, options = {}) {
2182
2303
  return;
2183
2304
  }
2184
2305
  if (subcommand !== void 0) {
2185
- throw new Error(`Unknown view command: view ${subcommand}`);
2306
+ throw new CliError("unknown_command", `Unknown view command: view ${subcommand}`);
2186
2307
  }
2187
2308
  if (parsed.options.localViewMode !== void 0) {
2188
2309
  const preference = await setLocalViewMode(parsed.options.localViewMode);
@@ -2230,7 +2351,10 @@ async function resolveWorkspaceSessionId(input) {
2230
2351
  }
2231
2352
  function assertNoViewPreferenceFlag(parsed) {
2232
2353
  if (parsed.options.localViewMode !== void 0) {
2233
- throw new Error("View preference flags cannot be combined with this subcommand.");
2354
+ throw new CliError(
2355
+ "invalid_option",
2356
+ "View preference flags cannot be combined with this subcommand."
2357
+ );
2234
2358
  }
2235
2359
  }
2236
2360
  function writeViewOutput(parsed, value) {
@@ -2315,10 +2439,10 @@ async function main() {
2315
2439
  }
2316
2440
  const operation = resolveOperation(parsed.command);
2317
2441
  if (!operation) {
2318
- throw new Error(`Unknown command: ${parsed.command.join(" ")}`);
2442
+ throw new CliError("unknown_command", `Unknown command: ${parsed.command.join(" ")}`);
2319
2443
  }
2320
2444
  if (parsed.options.workspace === void 0) {
2321
- throw new Error('Stateful commands require "--workspace <id>" or OPENSTEER_WORKSPACE.');
2445
+ throw new CliError("missing_workspace", 'Stateful commands require "--workspace <id>" or OPENSTEER_WORKSPACE.');
2322
2446
  }
2323
2447
  const { engineName, provider, runtimeProvider } = resolveCliRuntimeSelection(parsed);
2324
2448
  if (operation === "session.close") {
@@ -2340,28 +2464,43 @@ async function main() {
2340
2464
  let renderOperation = operation;
2341
2465
  try {
2342
2466
  const input = await buildOperationInput(operation, parsed, runtime);
2343
- result = await dispatchSemanticOperation(runtime, operation, input);
2467
+ const rawResult = await dispatchSemanticOperation(runtime, operation, input);
2344
2468
  if (parsed.command[0] === "tab" && operation !== "page.list") {
2345
2469
  renderOperation = "page.list";
2346
2470
  result = await runtime.listPages({});
2471
+ } else {
2472
+ result = rawResult;
2473
+ }
2474
+ let warning;
2475
+ if (result !== null && typeof result === "object" && "warning" in result) {
2476
+ const { warning: w, ...rest } = result;
2477
+ if (typeof w === "string") {
2478
+ warning = w;
2479
+ result = rest;
2480
+ }
2347
2481
  }
2348
2482
  process4.stdout.write(renderOperationOutput(renderOperation, result, input));
2483
+ if (warning !== void 0) {
2484
+ emitWarning(warning);
2485
+ }
2349
2486
  } finally {
2350
2487
  await runtime.disconnect().catch(() => void 0);
2351
2488
  }
2352
2489
  }
2353
2490
  async function handleExecCommand(parsed) {
2354
2491
  if (parsed.options.workspace === void 0) {
2355
- throw new Error('exec requires "--workspace <id>" or OPENSTEER_WORKSPACE.');
2492
+ throw new CliError("missing_workspace", 'exec requires "--workspace <id>" or OPENSTEER_WORKSPACE.');
2356
2493
  }
2357
2494
  const expression = parsed.rest.join(" ");
2358
2495
  if (!expression) {
2359
- throw new Error(
2360
- `exec requires an expression. Example: exec "await this.evaluate('document.title')"`
2496
+ throw new CliError(
2497
+ "missing_arguments",
2498
+ "exec requires an expression.",
2499
+ "opensteer exec <expression>"
2361
2500
  );
2362
2501
  }
2363
2502
  const { engineName, runtimeProvider } = resolveCliRuntimeSelection(parsed);
2364
- const { Opensteer } = await import('../opensteer-T2JENADR.js');
2503
+ const { Opensteer } = await import('../opensteer-UGA6YBRN.js');
2365
2504
  const opensteer = new Opensteer({
2366
2505
  workspace: parsed.options.workspace,
2367
2506
  rootDir: process4.cwd(),
@@ -2393,8 +2532,10 @@ async function handleBrowserCommand(parsed) {
2393
2532
  if (subcommand === "inspect") {
2394
2533
  const endpoint = parsed.options.attachEndpoint ?? parsed.rest[0];
2395
2534
  if (!endpoint) {
2396
- throw new Error(
2397
- 'browser inspect requires "--attach-endpoint <url>" or a positional endpoint.'
2535
+ throw new CliError(
2536
+ "missing_arguments",
2537
+ 'browser inspect requires "--attach-endpoint <url>" or a positional endpoint.',
2538
+ "opensteer browser inspect <endpoint>"
2398
2539
  );
2399
2540
  }
2400
2541
  const result = await inspectCdpEndpoint({
@@ -2407,7 +2548,8 @@ async function handleBrowserCommand(parsed) {
2407
2548
  return;
2408
2549
  }
2409
2550
  if (parsed.options.workspace === void 0) {
2410
- throw new Error(
2551
+ throw new CliError(
2552
+ "missing_workspace",
2411
2553
  'Browser workspace commands require "--workspace <id>" or OPENSTEER_WORKSPACE.'
2412
2554
  );
2413
2555
  }
@@ -2430,7 +2572,11 @@ async function handleBrowserCommand(parsed) {
2430
2572
  case "clone": {
2431
2573
  const sourceUserDataDir = parsed.options.sourceUserDataDir;
2432
2574
  if (!sourceUserDataDir) {
2433
- throw new Error('browser clone requires "--source-user-data-dir <path>".');
2575
+ throw new CliError(
2576
+ "missing_arguments",
2577
+ 'browser clone requires "--source-user-data-dir <path>".',
2578
+ "opensteer browser clone --source-user-data-dir <path>"
2579
+ );
2434
2580
  }
2435
2581
  const result = await manager.clonePersistentBrowser({
2436
2582
  sourceUserDataDir,
@@ -2453,7 +2599,7 @@ async function handleBrowserCommand(parsed) {
2453
2599
  return;
2454
2600
  }
2455
2601
  default:
2456
- throw new Error(`Unknown browser command: ${parsed.command.join(" ")}`);
2602
+ throw new CliError("unknown_command", `Unknown browser command: ${parsed.command.join(" ")}`);
2457
2603
  }
2458
2604
  }
2459
2605
  async function handleCloseCommand(parsed, engineName, providerMode, runtimeProvider) {
@@ -2486,23 +2632,27 @@ async function handleCloseCommand(parsed, engineName, providerMode, runtimeProvi
2486
2632
  }
2487
2633
  async function handleRecordCommandEntry(parsed) {
2488
2634
  if (parsed.options.workspace === void 0) {
2489
- throw new Error('record requires "--workspace <id>" or OPENSTEER_WORKSPACE.');
2635
+ throw new CliError("missing_workspace", 'record requires "--workspace <id>" or OPENSTEER_WORKSPACE.');
2490
2636
  }
2491
2637
  const url = parsed.options.url ?? parsed.rest[0];
2492
2638
  if (url === void 0) {
2493
- throw new Error('record requires "--url <value>" or a positional URL.');
2639
+ throw new CliError(
2640
+ "missing_arguments",
2641
+ 'record requires "--url <value>" or a positional URL.',
2642
+ "opensteer record --url <url> --workspace <id>"
2643
+ );
2494
2644
  }
2495
2645
  const provider = resolveCliProvider(parsed);
2496
2646
  assertCloudCliOptionsMatchProvider(parsed, provider.mode);
2497
2647
  const engineName = resolveCliEngineName(parsed);
2498
2648
  if (engineName !== "playwright") {
2499
- throw new Error("record requires engine=playwright.");
2649
+ throw new CliError("config_conflict", "record requires engine=playwright.");
2500
2650
  }
2501
2651
  const rootDir = process4.cwd();
2502
2652
  const recordBrowser = parsed.options.browser;
2503
2653
  if (provider.mode === "cloud") {
2504
2654
  if (typeof recordBrowser === "object") {
2505
- throw new Error('record does not support browser.mode="attach".');
2655
+ throw new CliError("config_conflict", 'record does not support browser.mode="attach".');
2506
2656
  }
2507
2657
  const runtimeProvider = buildCliRuntimeProvider(parsed, provider.mode);
2508
2658
  const runtimeConfig = resolveOpensteerRuntimeConfig({
@@ -2522,10 +2672,10 @@ async function handleRecordCommandEntry(parsed) {
2522
2672
  return;
2523
2673
  }
2524
2674
  if (parsed.options.launch?.headless === true) {
2525
- throw new Error('record requires a headed browser. Remove "--headless true".');
2675
+ throw new CliError("config_conflict", 'record requires a headed browser. Remove "--headless true".');
2526
2676
  }
2527
2677
  if (typeof recordBrowser === "object") {
2528
- throw new Error('record does not support browser.mode="attach".');
2678
+ throw new CliError("config_conflict", 'record does not support browser.mode="attach".');
2529
2679
  }
2530
2680
  const launch = {
2531
2681
  ...parsed.options.launch ?? {},
@@ -2594,7 +2744,7 @@ function resolveCliBootstrapAction(argv) {
2594
2744
  }
2595
2745
  function buildCliBrowserProfile(parsed) {
2596
2746
  if (parsed.options.cloudProfileReuseIfActive === true && parsed.options.cloudProfileId === void 0) {
2597
- throw new Error('"--cloud-profile-reuse-if-active" requires "--cloud-profile-id <id>".');
2747
+ throw new CliError("invalid_option", '"--cloud-profile-reuse-if-active" requires "--cloud-profile-id <id>".');
2598
2748
  }
2599
2749
  return parsed.options.cloudProfileId === void 0 ? void 0 : {
2600
2750
  profileId: parsed.options.cloudProfileId,
@@ -2655,7 +2805,8 @@ function buildCliRuntimeProvider(parsed, providerMode) {
2655
2805
  }
2656
2806
  function assertCloudCliOptionsMatchProvider(parsed, providerMode) {
2657
2807
  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)) {
2658
- throw new Error(
2808
+ throw new CliError(
2809
+ "config_conflict",
2659
2810
  'Cloud-specific options require provider=cloud. Set "--provider cloud" or OPENSTEER_PROVIDER=cloud.'
2660
2811
  );
2661
2812
  }
@@ -2689,17 +2840,7 @@ function printVersion() {
2689
2840
  `);
2690
2841
  }
2691
2842
  main().catch((error) => {
2692
- const payload = error instanceof Error ? {
2693
- error: {
2694
- name: error.name,
2695
- message: error.message
2696
- }
2697
- } : {
2698
- error: {
2699
- name: "Error",
2700
- message: String(error)
2701
- }
2702
- };
2843
+ const payload = formatCliErrorOutput(error);
2703
2844
  process4.stderr.write(`${JSON.stringify(payload)}
2704
2845
  `);
2705
2846
  process4.exitCode = 1;