opensteer 0.8.10 → 0.8.12

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.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- var process2 = require('process');
4
+ var process3 = require('process');
5
5
  var child_process = require('child_process');
6
6
  var crypto = require('crypto');
7
7
  var promises = require('fs/promises');
@@ -42,7 +42,8 @@ function _interopNamespace(e) {
42
42
  return Object.freeze(n);
43
43
  }
44
44
 
45
- var process2__default = /*#__PURE__*/_interopDefault(process2);
45
+ var process3__default = /*#__PURE__*/_interopDefault(process3);
46
+ var os__default = /*#__PURE__*/_interopDefault(os);
46
47
  var path7__default = /*#__PURE__*/_interopDefault(path7);
47
48
  var sharp__default = /*#__PURE__*/_interopDefault(sharp);
48
49
  var cheerio__namespace = /*#__PURE__*/_interopNamespace(cheerio);
@@ -59,7 +60,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
59
60
 
60
61
  // package.json
61
62
  var package_default = {
62
- version: "0.8.9"};
63
+ version: "0.8.10"};
63
64
  util.promisify(child_process.execFile);
64
65
  Math.floor(Date.now() - process.uptime() * 1e3);
65
66
  ({ ...process.env});
@@ -2283,6 +2284,9 @@ function isBrowserCoreError(value) {
2283
2284
  // ../browser-core/src/cdp-visual-stability.ts
2284
2285
  var DEFAULT_VISUAL_STABILITY_SETTLE_MS = 750;
2285
2286
 
2287
+ // ../browser-core/src/post-load-tracker.ts
2288
+ var DEFAULT_POST_LOAD_TRACKER_QUIET_WINDOW_MS = 400;
2289
+
2286
2290
  // ../protocol/src/identity.ts
2287
2291
  var refPrefixes = [
2288
2292
  "session",
@@ -7472,9 +7476,23 @@ var opensteerInspectStorageInputSchema = objectSchema(
7472
7476
  title: "OpensteerInspectStorageInput"
7473
7477
  }
7474
7478
  );
7479
+ var opensteerComputerMouseButtonSchema = enumSchema(["left", "middle", "right"], {
7480
+ title: "OpensteerComputerMouseButton"
7481
+ });
7482
+ var opensteerComputerKeyModifierSchema = enumSchema(
7483
+ ["Shift", "Control", "Alt", "Meta"],
7484
+ {
7485
+ title: "OpensteerComputerKeyModifier"
7486
+ }
7487
+ );
7475
7488
  var opensteerDomClickInputSchema = objectSchema(
7476
7489
  {
7477
7490
  target: opensteerTargetInputSchema,
7491
+ button: opensteerComputerMouseButtonSchema,
7492
+ clickCount: integerSchema({ minimum: 1 }),
7493
+ modifiers: arraySchema(opensteerComputerKeyModifierSchema, {
7494
+ uniqueItems: true
7495
+ }),
7478
7496
  persistAsDescription: stringSchema(),
7479
7497
  captureNetwork: stringSchema({ minLength: 1 })
7480
7498
  },
@@ -7574,18 +7592,6 @@ var opensteerSessionCloseOutputSchema = objectSchema(
7574
7592
  required: ["closed"]
7575
7593
  }
7576
7594
  );
7577
- var opensteerComputerMouseButtonSchema = enumSchema(
7578
- ["left", "middle", "right"],
7579
- {
7580
- title: "OpensteerComputerMouseButton"
7581
- }
7582
- );
7583
- var opensteerComputerKeyModifierSchema = enumSchema(
7584
- ["Shift", "Control", "Alt", "Meta"],
7585
- {
7586
- title: "OpensteerComputerKeyModifier"
7587
- }
7588
- );
7589
7595
  var opensteerComputerAnnotationSchema = enumSchema(opensteerComputerAnnotationNames, {
7590
7596
  title: "OpensteerComputerAnnotation"
7591
7597
  });
@@ -11528,6 +11534,7 @@ function collectEnvironment(baseEnv, predicate) {
11528
11534
  async function loadCliEnvironment(cwd) {
11529
11535
  loadEnvironment(cwd);
11530
11536
  }
11537
+ var OPENSTEER_GITHUB_SOURCE = "steerlabs/opensteer";
11531
11538
  function createOpensteerSkillsInvocation(input) {
11532
11539
  const cliArgs = ["add", input.skillSourcePath];
11533
11540
  if (input.options.all === true) {
@@ -11568,7 +11575,7 @@ function resolveOpensteerSkillsCliPath() {
11568
11575
  }
11569
11576
  return cliPath;
11570
11577
  }
11571
- function resolveOpensteerSkillSourcePath() {
11578
+ function resolveOpensteerLocalSkillSourcePath() {
11572
11579
  let ancestor = path7__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('bin.cjs', document.baseURI).href))));
11573
11580
  for (let index = 0; index < 6; index += 1) {
11574
11581
  const candidate = path7__default.default.join(ancestor, "skills");
@@ -11580,17 +11587,32 @@ function resolveOpensteerSkillSourcePath() {
11580
11587
  }
11581
11588
  throw new Error("Unable to find the packaged Opensteer skill source directory.");
11582
11589
  }
11590
+ async function checkOpensteerGitHubReachable() {
11591
+ try {
11592
+ const response = await fetch(`https://github.com/${OPENSTEER_GITHUB_SOURCE}`, {
11593
+ method: "HEAD",
11594
+ signal: AbortSignal.timeout(3e3),
11595
+ redirect: "manual"
11596
+ });
11597
+ return response.status < 500;
11598
+ } catch {
11599
+ return false;
11600
+ }
11601
+ }
11583
11602
  async function runOpensteerSkillsInstaller(options = {}, overrideDeps = {}) {
11584
11603
  const deps = {
11585
11604
  resolveSkillsCliPath: resolveOpensteerSkillsCliPath,
11586
- resolveSkillSourcePath: resolveOpensteerSkillSourcePath,
11605
+ resolveLocalSkillSourcePath: resolveOpensteerLocalSkillSourcePath,
11606
+ checkGitHubReachable: checkOpensteerGitHubReachable,
11587
11607
  spawnInvocation: spawnOpensteerSkillsInvocation,
11588
11608
  ...overrideDeps
11589
11609
  };
11610
+ const useGitHub = await deps.checkGitHubReachable();
11611
+ const skillSourcePath = useGitHub ? OPENSTEER_GITHUB_SOURCE : deps.resolveLocalSkillSourcePath();
11590
11612
  const invocation = createOpensteerSkillsInvocation({
11591
11613
  options,
11592
11614
  skillsCliPath: deps.resolveSkillsCliPath(),
11593
- skillSourcePath: deps.resolveSkillSourcePath()
11615
+ skillSourcePath
11594
11616
  });
11595
11617
  return deps.spawnInvocation(invocation);
11596
11618
  }
@@ -12126,6 +12148,30 @@ var OpensteerCloudClient = class {
12126
12148
  });
12127
12149
  return await response.json();
12128
12150
  }
12151
+ async getSessionRecording(sessionId) {
12152
+ const response = await this.request(`/v1/sessions/${encodeURIComponent(sessionId)}/recording`, {
12153
+ method: "GET"
12154
+ });
12155
+ return await response.json();
12156
+ }
12157
+ async startSessionRecording(sessionId) {
12158
+ const response = await this.request(
12159
+ `/v1/sessions/${encodeURIComponent(sessionId)}/recording/start`,
12160
+ {
12161
+ method: "POST"
12162
+ }
12163
+ );
12164
+ return await response.json();
12165
+ }
12166
+ async stopSessionRecording(sessionId) {
12167
+ const response = await this.request(
12168
+ `/v1/sessions/${encodeURIComponent(sessionId)}/recording/stop`,
12169
+ {
12170
+ method: "POST"
12171
+ }
12172
+ );
12173
+ return await response.json();
12174
+ }
12129
12175
  async closeSession(sessionId) {
12130
12176
  const response = await this.request(`/v1/sessions/${encodeURIComponent(sessionId)}`, {
12131
12177
  method: "DELETE"
@@ -12316,12 +12362,23 @@ function resolveCloudConfig(input = {}) {
12316
12362
  if (!baseUrl || baseUrl.trim().length === 0) {
12317
12363
  throw new Error("provider=cloud requires OPENSTEER_BASE_URL or provider.baseUrl.");
12318
12364
  }
12365
+ const appBaseUrl = cloudProvider?.appBaseUrl ?? input.environment?.OPENSTEER_CLOUD_APP_BASE_URL;
12319
12366
  return {
12320
12367
  apiKey: apiKey.trim(),
12321
12368
  baseUrl: baseUrl.trim().replace(/\/+$/, ""),
12369
+ ...appBaseUrl === void 0 || appBaseUrl.trim().length === 0 ? {} : { appBaseUrl: appBaseUrl.trim().replace(/\/+$/, "") },
12322
12370
  ...cloudProvider?.browserProfile === void 0 ? {} : { browserProfile: cloudProvider.browserProfile }
12323
12371
  };
12324
12372
  }
12373
+ function requireCloudAppBaseUrl(cloudConfig) {
12374
+ const appBaseUrl = cloudConfig.appBaseUrl;
12375
+ if (!appBaseUrl || appBaseUrl.trim().length === 0) {
12376
+ throw new Error(
12377
+ 'record with provider=cloud requires OPENSTEER_CLOUD_APP_BASE_URL or "--cloud-app-base-url".'
12378
+ );
12379
+ }
12380
+ return appBaseUrl.trim().replace(/\/+$/, "");
12381
+ }
12325
12382
 
12326
12383
  // ../runtime-core/package.json
12327
12384
  var package_default2 = {
@@ -12333,16 +12390,7 @@ var OPENSTEER_RUNTIME_CORE_VERSION = package_default2.version;
12333
12390
  // ../runtime-core/src/action-boundary.ts
12334
12391
  var actionBoundaryDiagnosticsBySignal = /* @__PURE__ */ new WeakMap();
12335
12392
  async function captureActionBoundarySnapshot(engine, pageRef) {
12336
- const frames = await engine.listFrames({ pageRef });
12337
- const mainFrame = frames.find((frame) => frame.isMainFrame);
12338
- if (!mainFrame) {
12339
- throw new Error(`page ${pageRef} does not expose a main frame`);
12340
- }
12341
- return {
12342
- pageRef,
12343
- documentRef: mainFrame.documentRef,
12344
- url: mainFrame.url
12345
- };
12393
+ return engine.getActionBoundarySnapshot({ pageRef });
12346
12394
  }
12347
12395
  function createActionBoundaryDiagnostics(input) {
12348
12396
  return {
@@ -12459,6 +12507,7 @@ var NAVIGATION_VISUAL_STABILITY_PROFILE = {
12459
12507
  scope: "visible-frames",
12460
12508
  timeoutMs: 7e3
12461
12509
  };
12510
+ var NAVIGATION_POST_LOAD_CAPTURE_WINDOW_MS = 1e3;
12462
12511
  var defaultDomActionSettleObserver = {
12463
12512
  async settle(input) {
12464
12513
  if (input.trigger !== "dom-action") {
@@ -12494,6 +12543,13 @@ var defaultNavigationSettleObserver = {
12494
12543
  return false;
12495
12544
  }
12496
12545
  try {
12546
+ await input.engine.waitForPostLoadQuiet({
12547
+ pageRef: input.pageRef,
12548
+ timeoutMs: effectiveTimeout,
12549
+ quietMs: DEFAULT_POST_LOAD_TRACKER_QUIET_WINDOW_MS,
12550
+ captureWindowMs: Math.min(NAVIGATION_POST_LOAD_CAPTURE_WINDOW_MS, effectiveTimeout),
12551
+ signal: input.signal
12552
+ });
12497
12553
  await input.engine.waitForVisualStability({
12498
12554
  pageRef: input.pageRef,
12499
12555
  timeoutMs: effectiveTimeout,
@@ -23274,11 +23330,11 @@ var SandboxClock = class {
23274
23330
  performanceNow() {
23275
23331
  return this.mode === "manual" ? this.manualNow - this.startedAt : (globalThis.performance?.now() ?? 0) - this.performanceStartedAt;
23276
23332
  }
23277
- setTimeout(callback, delay3 = 0, ...args) {
23278
- return this.registerTimer(false, callback, delay3, args);
23333
+ setTimeout(callback, delay4 = 0, ...args) {
23334
+ return this.registerTimer(false, callback, delay4, args);
23279
23335
  }
23280
- setInterval(callback, delay3 = 0, ...args) {
23281
- return this.registerTimer(true, callback, delay3, args);
23336
+ setInterval(callback, delay4 = 0, ...args) {
23337
+ return this.registerTimer(true, callback, delay4, args);
23282
23338
  }
23283
23339
  clearTimeout(timerId) {
23284
23340
  this.clearTimer(timerId);
@@ -23299,9 +23355,9 @@ var SandboxClock = class {
23299
23355
  this.clearTimer(timerId);
23300
23356
  }
23301
23357
  }
23302
- registerTimer(repeat, callback, delay3, args) {
23358
+ registerTimer(repeat, callback, delay4, args) {
23303
23359
  const timerId = this.nextTimerId++;
23304
- const normalizedDelay = Math.max(0, delay3);
23360
+ const normalizedDelay = Math.max(0, delay4);
23305
23361
  const record = {
23306
23362
  callback,
23307
23363
  args,
@@ -24472,6 +24528,9 @@ var OpensteerSessionRuntime = class {
24472
24528
  const result = await this.requireDom().click({
24473
24529
  pageRef,
24474
24530
  target,
24531
+ ...input.button === void 0 ? {} : { button: input.button },
24532
+ ...input.clickCount === void 0 ? {} : { clickCount: input.clickCount },
24533
+ ...input.modifiers === void 0 ? {} : { modifiers: input.modifiers },
24475
24534
  timeout
24476
24535
  });
24477
24536
  return {
@@ -33191,7 +33250,11 @@ function screenshotMediaType(format2) {
33191
33250
  var SINGLE_ATTRIBUTE_PRIORITY = Array.from(
33192
33251
  /* @__PURE__ */ new Set(["data-testid", "data-test", "data-qa", "data-cy", "id", ...STABLE_PRIMARY_ATTR_KEYS])
33193
33252
  );
33194
- var FLOW_RECORDER_INSTALL_SOURCE = String.raw`(() => {
33253
+ function createFlowRecorderInstallScript(options = {}) {
33254
+ const normalizedOptions = {
33255
+ showStopButton: options.showStopButton ?? true
33256
+ };
33257
+ return String.raw`(() => {
33195
33258
  const TOP_LEVEL_ONLY = (() => {
33196
33259
  try {
33197
33260
  return window.top === window.self;
@@ -33216,6 +33279,7 @@ var FLOW_RECORDER_INSTALL_SOURCE = String.raw`(() => {
33216
33279
  const volatileLazyLoadingAttrs = new Set(${JSON.stringify([...VOLATILE_LAZY_LOADING_ATTRS])});
33217
33280
  const volatileClassTokens = new Set(${JSON.stringify([...VOLATILE_CLASS_TOKENS])});
33218
33281
  const volatileLazyClassTokens = new Set(${JSON.stringify([...VOLATILE_LAZY_CLASS_TOKENS])});
33282
+ const options = ${JSON.stringify(normalizedOptions)};
33219
33283
 
33220
33284
  const previous = globalScope[recorderKey];
33221
33285
  if (previous && typeof previous.dispose === "function") {
@@ -33226,6 +33290,7 @@ var FLOW_RECORDER_INSTALL_SOURCE = String.raw`(() => {
33226
33290
  const cleanup = [];
33227
33291
  const inputFlushTimers = new Map();
33228
33292
  const pendingInputs = new Map();
33293
+ let historyStateCache = undefined;
33229
33294
  let stopRequested = false;
33230
33295
  let pendingWheel = undefined;
33231
33296
 
@@ -33546,44 +33611,76 @@ var FLOW_RECORDER_INSTALL_SOURCE = String.raw`(() => {
33546
33611
  inputFlushTimers.set(selector, timerId);
33547
33612
  }
33548
33613
 
33549
- function updateHistoryState(mode, nextUrl) {
33550
- let state;
33551
- try {
33552
- const raw = sessionStorage.getItem(historyStateKey);
33553
- state = raw ? JSON.parse(raw) : undefined;
33554
- } catch {
33555
- state = undefined;
33614
+ function createDefaultHistoryState(currentUrl) {
33615
+ return {
33616
+ entries: [currentUrl],
33617
+ index: 0,
33618
+ };
33619
+ }
33620
+
33621
+ function normalizeHistoryState(state) {
33622
+ if (
33623
+ !state ||
33624
+ !Array.isArray(state.entries) ||
33625
+ !state.entries.every((entry) => typeof entry === "string") ||
33626
+ !Number.isInteger(state.index)
33627
+ ) {
33628
+ return undefined;
33556
33629
  }
33557
- if (!state || !Array.isArray(state.entries) || typeof state.index !== "number") {
33558
- state = { entries: [location.href], index: 0 };
33630
+ const entries = state.entries.slice();
33631
+ if (entries.length === 0) {
33632
+ return createDefaultHistoryState(location.href);
33559
33633
  }
33634
+ return {
33635
+ entries,
33636
+ index: Math.min(entries.length - 1, Math.max(0, state.index)),
33637
+ };
33638
+ }
33639
+
33640
+ function writeHistoryState(state) {
33641
+ const normalized = normalizeHistoryState(state) ?? createDefaultHistoryState(location.href);
33642
+ historyStateCache = normalized;
33643
+ try {
33644
+ sessionStorage.setItem(historyStateKey, JSON.stringify(normalized));
33645
+ } catch {}
33646
+ return normalized;
33647
+ }
33648
+
33649
+ function applyHistoryState(state, mode, nextUrl) {
33650
+ const current = normalizeHistoryState(state) ?? createDefaultHistoryState(location.href);
33651
+ const nextState = {
33652
+ entries: current.entries.slice(),
33653
+ index: current.index,
33654
+ };
33560
33655
  if (mode === "replace") {
33561
- state.entries[state.index] = nextUrl;
33656
+ nextState.entries[nextState.index] = nextUrl;
33562
33657
  } else if (mode === "push") {
33563
- state.entries = state.entries.slice(0, state.index + 1);
33564
- state.entries.push(nextUrl);
33565
- state.index = state.entries.length - 1;
33658
+ nextState.entries = nextState.entries.slice(0, nextState.index + 1);
33659
+ nextState.entries.push(nextUrl);
33660
+ nextState.index = nextState.entries.length - 1;
33566
33661
  } else if (mode === "back") {
33567
- state.index = Math.max(0, state.index - 1);
33662
+ nextState.index = Math.max(0, nextState.index - 1);
33568
33663
  } else if (mode === "forward") {
33569
- state.index = Math.min(state.entries.length - 1, state.index + 1);
33664
+ nextState.index = Math.min(nextState.entries.length - 1, nextState.index + 1);
33570
33665
  }
33571
- try {
33572
- sessionStorage.setItem(historyStateKey, JSON.stringify(state));
33573
- } catch {}
33574
- return state;
33666
+ return nextState;
33667
+ }
33668
+
33669
+ function updateHistoryState(mode, nextUrl) {
33670
+ return writeHistoryState(applyHistoryState(readHistoryState(), mode, nextUrl));
33575
33671
  }
33576
33672
 
33577
33673
  function readHistoryState() {
33674
+ const cached = normalizeHistoryState(historyStateCache);
33675
+ if (cached) {
33676
+ historyStateCache = cached;
33677
+ return cached;
33678
+ }
33578
33679
  try {
33579
33680
  const raw = sessionStorage.getItem(historyStateKey);
33580
- const parsed = raw ? JSON.parse(raw) : undefined;
33581
- if (
33582
- parsed &&
33583
- Array.isArray(parsed.entries) &&
33584
- parsed.entries.every((entry) => typeof entry === "string") &&
33585
- typeof parsed.index === "number"
33586
- ) {
33681
+ const parsed = normalizeHistoryState(raw ? JSON.parse(raw) : undefined);
33682
+ if (parsed) {
33683
+ historyStateCache = parsed;
33587
33684
  return parsed;
33588
33685
  }
33589
33686
  } catch {}
@@ -33595,28 +33692,30 @@ var FLOW_RECORDER_INSTALL_SOURCE = String.raw`(() => {
33595
33692
  if (!state) {
33596
33693
  return undefined;
33597
33694
  }
33695
+ if (state.entries[state.index] === currentUrl) {
33696
+ return undefined;
33697
+ }
33598
33698
  if (state.entries[state.index - 1] === currentUrl) {
33599
- updateHistoryState("back", currentUrl);
33699
+ writeHistoryState({
33700
+ entries: state.entries,
33701
+ index: state.index - 1,
33702
+ });
33600
33703
  return "go-back";
33601
33704
  }
33602
33705
  if (state.entries[state.index + 1] === currentUrl) {
33603
- updateHistoryState("forward", currentUrl);
33706
+ writeHistoryState({
33707
+ entries: state.entries,
33708
+ index: state.index + 1,
33709
+ });
33604
33710
  return "go-forward";
33605
33711
  }
33606
33712
  const existingIndex = state.entries.lastIndexOf(currentUrl);
33607
- if (existingIndex !== -1) {
33608
- if (existingIndex < state.index) {
33609
- while (readHistoryState()?.index > existingIndex) {
33610
- updateHistoryState("back", currentUrl);
33611
- }
33612
- return "go-back";
33613
- }
33614
- if (existingIndex > state.index) {
33615
- while (readHistoryState()?.index < existingIndex) {
33616
- updateHistoryState("forward", currentUrl);
33617
- }
33618
- return "go-forward";
33619
- }
33713
+ if (existingIndex !== -1 && existingIndex !== state.index) {
33714
+ writeHistoryState({
33715
+ entries: state.entries,
33716
+ index: existingIndex,
33717
+ });
33718
+ return existingIndex < state.index ? "go-back" : "go-forward";
33620
33719
  }
33621
33720
  return undefined;
33622
33721
  }
@@ -33743,10 +33842,6 @@ var FLOW_RECORDER_INSTALL_SOURCE = String.raw`(() => {
33743
33842
  " outline: 2px solid #2563eb;",
33744
33843
  " outline-offset: 2px;",
33745
33844
  "}",
33746
- "button:disabled {",
33747
- " cursor: wait;",
33748
- " opacity: 0.8;",
33749
- "}",
33750
33845
  ].join("\n");
33751
33846
  const button = document.createElement("button");
33752
33847
  button.type = "button";
@@ -33979,7 +34074,8 @@ var FLOW_RECORDER_INSTALL_SOURCE = String.raw`(() => {
33979
34074
  });
33980
34075
  return;
33981
34076
  }
33982
- if (readHistoryState()?.entries[readHistoryState().index] !== currentUrl) {
34077
+ const state = readHistoryState();
34078
+ if (state?.entries[state.index] !== currentUrl) {
33983
34079
  updateHistoryState("push", currentUrl);
33984
34080
  enqueue({
33985
34081
  kind: "navigate",
@@ -34033,10 +34129,13 @@ var FLOW_RECORDER_INSTALL_SOURCE = String.raw`(() => {
34033
34129
  },
34034
34130
  };
34035
34131
 
34036
- ensureStopButtonMounted();
34132
+ if (options.showStopButton) {
34133
+ ensureStopButtonMounted();
34134
+ }
34037
34135
  onInstall();
34038
34136
  })();`;
34039
- var FLOW_RECORDER_INSTALL_SCRIPT = FLOW_RECORDER_INSTALL_SOURCE;
34137
+ }
34138
+ var FLOW_RECORDER_INSTALL_SCRIPT = createFlowRecorderInstallScript();
34040
34139
  var FLOW_RECORDER_DRAIN_SCRIPT = String.raw`(() => {
34041
34140
  const recorder = globalThis.__opensteerFlowRecorder;
34042
34141
  if (!recorder || typeof recorder.drain !== "function") {
@@ -34056,6 +34155,7 @@ var FlowRecorderCollector = class {
34056
34155
  runtime;
34057
34156
  pollIntervalMs;
34058
34157
  onAction;
34158
+ installScript;
34059
34159
  pages = /* @__PURE__ */ new Map();
34060
34160
  actions = [];
34061
34161
  nextPageOrdinal = 0;
@@ -34068,10 +34168,11 @@ var FlowRecorderCollector = class {
34068
34168
  this.runtime = runtime;
34069
34169
  this.pollIntervalMs = options.pollIntervalMs ?? 250;
34070
34170
  this.onAction = options.onAction;
34171
+ this.installScript = options.installScript ?? FLOW_RECORDER_INSTALL_SCRIPT;
34071
34172
  }
34072
34173
  async install() {
34073
34174
  await this.runtime.addInitScript({
34074
- script: FLOW_RECORDER_INSTALL_SCRIPT
34175
+ script: this.installScript
34075
34176
  });
34076
34177
  const { pages } = await this.runtime.listPages();
34077
34178
  for (const page of pages) {
@@ -34080,7 +34181,7 @@ var FlowRecorderCollector = class {
34080
34181
  await Promise.all(
34081
34182
  pages.map(
34082
34183
  (page) => this.runtime.evaluate({
34083
- script: FLOW_RECORDER_INSTALL_SCRIPT,
34184
+ script: this.installScript,
34084
34185
  pageRef: page.pageRef
34085
34186
  }).catch(() => void 0)
34086
34187
  )
@@ -34112,6 +34213,18 @@ var FlowRecorderCollector = class {
34112
34213
  getActions() {
34113
34214
  return this.actions.slice();
34114
34215
  }
34216
+ getPages() {
34217
+ return [...this.pages.values()].map((page) => ({
34218
+ pageId: page.pageId,
34219
+ pageRef: page.pageRef,
34220
+ ...page.openerPageRef === void 0 ? {} : { openerPageRef: page.openerPageRef },
34221
+ ...page.openerPageId === void 0 ? {} : { openerPageId: page.openerPageId },
34222
+ currentUrl: page.currentUrl
34223
+ })).sort((left, right) => comparePageIds(left.pageId, right.pageId));
34224
+ }
34225
+ getFocusedPageId() {
34226
+ return this.focusedPageId;
34227
+ }
34115
34228
  async waitForStop() {
34116
34229
  if (this.stopDetected) {
34117
34230
  return;
@@ -34208,10 +34321,17 @@ var FlowRecorderCollector = class {
34208
34321
  }
34209
34322
  for (const listedPage of input.listedPages) {
34210
34323
  if (!input.previousPageRefs.has(listedPage.pageRef)) {
34211
- const created = this.ensureKnownPage(listedPage.pageRef, listedPage.url, listedPage.openerPageRef);
34324
+ const created = this.ensureKnownPage(
34325
+ listedPage.pageRef,
34326
+ listedPage.url,
34327
+ listedPage.openerPageRef
34328
+ );
34212
34329
  actions.push({
34213
34330
  kind: "new-tab",
34214
- timestamp: Math.max(0, (firstEventTimestampByPage.get(listedPage.pageRef) ?? input.pollTimestamp) - 1),
34331
+ timestamp: Math.max(
34332
+ 0,
34333
+ (firstEventTimestampByPage.get(listedPage.pageRef) ?? input.pollTimestamp) - 1
34334
+ ),
34215
34335
  pageId: created.pageId,
34216
34336
  pageUrl: listedPage.url,
34217
34337
  detail: {
@@ -34265,7 +34385,11 @@ var FlowRecorderCollector = class {
34265
34385
  (event) => this.normalizeRawEvent(event, knownPage, evaluatedPage.currentUrl)
34266
34386
  )
34267
34387
  );
34268
- this.updateKnownPage(evaluatedPage.pageRef, evaluatedPage.currentUrl, evaluatedPage.openerPageRef);
34388
+ this.updateKnownPage(
34389
+ evaluatedPage.pageRef,
34390
+ evaluatedPage.currentUrl,
34391
+ evaluatedPage.openerPageRef
34392
+ );
34269
34393
  }
34270
34394
  const focusedPage = input.evaluatedPages.find((page) => page.focused);
34271
34395
  if (focusedPage !== void 0 && focusedPage.pageId !== this.focusedPageId) {
@@ -34516,6 +34640,14 @@ function actionSortPriority(kind) {
34516
34640
  return 6;
34517
34641
  }
34518
34642
  }
34643
+ function comparePageIds(left, right) {
34644
+ const leftMatch = /^page(\d+)$/u.exec(left);
34645
+ const rightMatch = /^page(\d+)$/u.exec(right);
34646
+ if (leftMatch && rightMatch) {
34647
+ return Number(leftMatch[1]) - Number(rightMatch[1]);
34648
+ }
34649
+ return left.localeCompare(right);
34650
+ }
34519
34651
  function delay2(ms) {
34520
34652
  return new Promise((resolve4) => setTimeout(resolve4, ms));
34521
34653
  }
@@ -34565,17 +34697,16 @@ var WINDOW_SCROLL_SCRIPT = String.raw`(deltaX, deltaY) => {
34565
34697
  window.scrollBy(Number(deltaX), Number(deltaY));
34566
34698
  }`;
34567
34699
  function generateReplayScript(options) {
34568
- const pageIds = collectPageIds(options.actions);
34569
- const initialPageId = pageIds[0] ?? "page0";
34700
+ const replayTarget = resolveReplayTarget(options);
34701
+ const initialPages = orderInitialPages(resolveInitialPages(options));
34702
+ const activeInitialPageId = resolveActiveInitialPageId(options, initialPages);
34703
+ const initialPageId = initialPages[0]?.pageId ?? "page0";
34570
34704
  const lines = [
34571
34705
  `import { Opensteer } from "opensteer";`,
34572
34706
  ``,
34573
- `const opensteer = new Opensteer({`,
34574
- ` workspace: ${JSON.stringify(options.workspace)},`,
34575
- ` browser: "persistent",`,
34576
- `});`,
34707
+ ...renderOpensteerBootstrap(replayTarget),
34577
34708
  ``,
34578
- `const ${initialPageId} = (await opensteer.open(${JSON.stringify(options.startUrl)})).pageRef;`,
34709
+ `const ${initialPageId} = (await opensteer.open(${JSON.stringify(initialPages[0]?.initialUrl ?? "")})).pageRef;`,
34579
34710
  `let activePageRef: string | undefined = ${initialPageId};`,
34580
34711
  ``,
34581
34712
  `async function ensureActive(pageRef: string): Promise<void> {`,
@@ -34616,6 +34747,17 @@ function generateReplayScript(options) {
34616
34747
  `try {`
34617
34748
  ];
34618
34749
  const declaredPages = /* @__PURE__ */ new Set([initialPageId]);
34750
+ for (const page of initialPages.slice(1)) {
34751
+ const openerPageId = page.openerPageId !== void 0 && declaredPages.has(page.openerPageId) ? page.openerPageId : void 0;
34752
+ lines.push(
34753
+ ` const ${page.pageId} = (await opensteer.newPage(${renderNewPageInput(openerPageId, page.initialUrl)})).pageRef;`
34754
+ );
34755
+ lines.push(` activePageRef = ${page.pageId};`);
34756
+ declaredPages.add(page.pageId);
34757
+ }
34758
+ if (activeInitialPageId !== void 0 && activeInitialPageId !== initialPageId) {
34759
+ lines.push(` await ensureActive(${activeInitialPageId});`);
34760
+ }
34619
34761
  for (let index = 0; index < options.actions.length; index += 1) {
34620
34762
  const action = options.actions[index];
34621
34763
  const pageVar = action.pageId;
@@ -34648,16 +34790,11 @@ function generateReplayScript(options) {
34648
34790
  case "dblclick":
34649
34791
  lines.push(` await ensureActive(${pageVar});`);
34650
34792
  lines.push(
34651
- ` await opensteer.click({ selector: ${JSON.stringify(requireSelector(action))} });`
34652
- );
34653
- lines.push(
34654
- ` await opensteer.click({ selector: ${JSON.stringify(requireSelector(action))} });`
34793
+ ` await opensteer.click({ selector: ${JSON.stringify(requireSelector(action))}, clickCount: 2 });`
34655
34794
  );
34656
34795
  break;
34657
34796
  case "keypress":
34658
- lines.push(
34659
- ` await dispatchKey(${pageVar}, ${JSON.stringify(action.detail.key)});`
34660
- );
34797
+ lines.push(` await dispatchKey(${pageVar}, ${JSON.stringify(action.detail.key)});`);
34661
34798
  break;
34662
34799
  case "scroll": {
34663
34800
  const { direction, amount, isWindowScroll } = normalizeScrollAction(action);
@@ -34685,7 +34822,7 @@ function generateReplayScript(options) {
34685
34822
  case "new-tab": {
34686
34823
  const openerPageVar = action.detail.openerPageId;
34687
34824
  const shouldUseWaitForPage = shouldUsePopupWait(options.actions, index, openerPageVar);
34688
- const creationLine = shouldUseWaitForPage && openerPageVar !== void 0 ? ` const ${pageVar} = (await opensteer.waitForPage({ openerPageRef: ${openerPageVar}, timeoutMs: 30_000 })).pageRef;` : ` const ${pageVar} = (await opensteer.newPage({${renderNewPageArguments(action.detail.openerPageId, action.detail.initialUrl)}})).pageRef;`;
34825
+ const creationLine = shouldUseWaitForPage && openerPageVar !== void 0 ? ` const ${pageVar} = (await opensteer.waitForPage({ openerPageRef: ${openerPageVar}, timeoutMs: 30_000 })).pageRef;` : ` const ${pageVar} = (await opensteer.newPage(${renderNewPageInput(action.detail.openerPageId, action.detail.initialUrl)})).pageRef;`;
34689
34826
  lines.push(creationLine);
34690
34827
  lines.push(` activePageRef = ${pageVar};`);
34691
34828
  declaredPages.add(pageVar);
@@ -34718,29 +34855,114 @@ function generateReplayScript(options) {
34718
34855
  return `${lines.join("\n")}
34719
34856
  `;
34720
34857
  }
34721
- function collectPageIds(actions) {
34722
- const orderedIds = /* @__PURE__ */ new Set(["page0"]);
34723
- for (const action of actions) {
34724
- orderedIds.add(action.pageId);
34725
- if (action.kind === "new-tab" && action.detail.openerPageId !== void 0) {
34726
- orderedIds.add(action.detail.openerPageId);
34727
- }
34728
- if (action.kind === "switch-tab" && action.detail.fromPageId !== void 0) {
34729
- orderedIds.add(action.detail.fromPageId);
34730
- }
34731
- if (action.kind === "switch-tab") {
34732
- orderedIds.add(action.detail.toPageId);
34858
+ function resolveReplayTarget(options) {
34859
+ if (options.replayTarget !== void 0) {
34860
+ return options.replayTarget;
34861
+ }
34862
+ if (options.workspace !== void 0) {
34863
+ return {
34864
+ kind: "local",
34865
+ workspace: options.workspace
34866
+ };
34867
+ }
34868
+ throw new Error("Replay codegen requires either replayTarget or workspace.");
34869
+ }
34870
+ function resolveInitialPages(options) {
34871
+ if (options.initialPages !== void 0 && options.initialPages.length > 0) {
34872
+ const unique = /* @__PURE__ */ new Set();
34873
+ return options.initialPages.map((page) => {
34874
+ if (unique.has(page.pageId)) {
34875
+ throw new Error(`Duplicate initial page id "${page.pageId}" in recording bootstrap.`);
34876
+ }
34877
+ unique.add(page.pageId);
34878
+ return page;
34879
+ });
34880
+ }
34881
+ const startUrl = options.startUrl;
34882
+ if (startUrl === void 0) {
34883
+ throw new Error("Replay codegen requires startUrl when initialPages is not provided.");
34884
+ }
34885
+ return [
34886
+ {
34887
+ pageId: "page0",
34888
+ initialUrl: startUrl
34733
34889
  }
34890
+ ];
34891
+ }
34892
+ function resolveActiveInitialPageId(options, initialPages) {
34893
+ if (options.activePageId !== void 0) {
34894
+ return options.activePageId;
34734
34895
  }
34735
- return [...orderedIds].sort(comparePageIds);
34896
+ return initialPages[0]?.pageId;
34736
34897
  }
34737
- function comparePageIds(left, right) {
34738
- const leftMatch = /^page(\d+)$/u.exec(left);
34739
- const rightMatch = /^page(\d+)$/u.exec(right);
34740
- if (leftMatch && rightMatch) {
34741
- return Number(leftMatch[1]) - Number(rightMatch[1]);
34898
+ function renderOpensteerBootstrap(replayTarget) {
34899
+ if (replayTarget.kind === "local") {
34900
+ return [
34901
+ `const opensteer = new Opensteer({`,
34902
+ ` workspace: ${JSON.stringify(replayTarget.workspace)},`,
34903
+ ` browser: "persistent",`,
34904
+ `});`
34905
+ ];
34742
34906
  }
34743
- return left.localeCompare(right);
34907
+ return [
34908
+ renderRequireEnvHelper(replayTarget),
34909
+ ``,
34910
+ `const opensteer = new Opensteer({`,
34911
+ ` provider: {`,
34912
+ ` mode: "cloud",`,
34913
+ ` baseUrl: requireEnv(${JSON.stringify(replayTarget.baseUrlEnvVar ?? "OPENSTEER_BASE_URL")}),`,
34914
+ ` apiKey: requireEnv(${JSON.stringify(replayTarget.apiKeyEnvVar ?? "OPENSTEER_API_KEY")}),`,
34915
+ ...renderCloudBrowserProfile(replayTarget),
34916
+ ` },`,
34917
+ `});`
34918
+ ];
34919
+ }
34920
+ function renderRequireEnvHelper(replayTarget) {
34921
+ const baseUrlEnvVar = replayTarget.baseUrlEnvVar ?? "OPENSTEER_BASE_URL";
34922
+ const apiKeyEnvVar = replayTarget.apiKeyEnvVar ?? "OPENSTEER_API_KEY";
34923
+ return [
34924
+ `function requireEnv(name: string): string {`,
34925
+ ` const value = process.env[name];`,
34926
+ ` if (typeof value === "string" && value.trim().length > 0) {`,
34927
+ ` return value;`,
34928
+ ` }`,
34929
+ ` throw new Error(\`Missing environment variable \${name}. Set ${baseUrlEnvVar} and ${apiKeyEnvVar} before replaying this recording.\`);`,
34930
+ `}`
34931
+ ].join("\n");
34932
+ }
34933
+ function renderCloudBrowserProfile(replayTarget) {
34934
+ if (replayTarget.browserProfileId === void 0) {
34935
+ return [];
34936
+ }
34937
+ return [
34938
+ ` browserProfile: {`,
34939
+ ` profileId: ${JSON.stringify(replayTarget.browserProfileId)},`,
34940
+ ...replayTarget.reuseBrowserProfileIfActive ? [` reuseIfActive: true,`] : [],
34941
+ ` },`
34942
+ ];
34943
+ }
34944
+ function orderInitialPages(initialPages) {
34945
+ const ordered = [];
34946
+ const declared = /* @__PURE__ */ new Set();
34947
+ const remaining = [...initialPages];
34948
+ while (remaining.length > 0) {
34949
+ let advanced = false;
34950
+ for (let index = 0; index < remaining.length; index += 1) {
34951
+ const page = remaining[index];
34952
+ if (page.openerPageId !== void 0 && !declared.has(page.openerPageId)) {
34953
+ continue;
34954
+ }
34955
+ ordered.push(page);
34956
+ declared.add(page.pageId);
34957
+ remaining.splice(index, 1);
34958
+ advanced = true;
34959
+ break;
34960
+ }
34961
+ if (!advanced) {
34962
+ ordered.push(...remaining.splice(0, remaining.length));
34963
+ }
34964
+ }
34965
+ return ordered;
34744
34966
  }
34745
34967
  function requireSelector(action) {
34746
34968
  if (action.selector === void 0) {
@@ -34774,18 +34996,18 @@ function shouldUsePopupWait(actions, newTabIndex, openerPageId) {
34774
34996
  }
34775
34997
  return previousAction.kind === "click" || previousAction.kind === "dblclick" || previousAction.kind === "keypress";
34776
34998
  }
34777
- function renderNewPageArguments(openerPageId, initialUrl) {
34999
+ function renderNewPageInput(openerPageId, initialUrl) {
34778
35000
  const argumentsList = [];
34779
35001
  if (openerPageId !== void 0) {
34780
- argumentsList.push(` openerPageRef: ${openerPageId}`);
35002
+ argumentsList.push(`openerPageRef: ${openerPageId}`);
34781
35003
  }
34782
35004
  if (initialUrl.length > 0 && initialUrl !== "about:blank") {
34783
- argumentsList.push(` url: ${JSON.stringify(initialUrl)}`);
35005
+ argumentsList.push(`url: ${JSON.stringify(initialUrl)}`);
34784
35006
  }
34785
35007
  if (argumentsList.length === 0) {
34786
- return ``;
35008
+ return `{}`;
34787
35009
  }
34788
- return ` ${argumentsList.join(",")}`.trimStart();
35010
+ return `{ ${argumentsList.join(", ")} }`;
34789
35011
  }
34790
35012
  var OpensteerSemanticRestError = class extends Error {
34791
35013
  opensteerError;
@@ -35934,18 +36156,6 @@ function resolveOwnership(browser) {
35934
36156
  }
35935
36157
 
35936
36158
  // src/sdk/runtime-resolution.ts
35937
- var LOCAL_ONLY_RUNTIME_OPTION_KEYS = [
35938
- "launch",
35939
- "context",
35940
- "engine",
35941
- "engineFactory",
35942
- "policy",
35943
- "descriptorStore",
35944
- "extractionDescriptorStore",
35945
- "registryOverrides",
35946
- "observationSessionId",
35947
- "observationSink"
35948
- ];
35949
36159
  function resolveOpensteerRuntimeConfig(input = {}) {
35950
36160
  const environment = input.environment ?? process.env;
35951
36161
  const provider = resolveOpensteerProvider({
@@ -35970,15 +36180,8 @@ function createOpensteerSemanticRuntime(input = {}) {
35970
36180
  ...input.provider === void 0 ? {} : { provider: input.provider },
35971
36181
  ...input.environment === void 0 ? {} : { environment: input.environment }
35972
36182
  });
35973
- const localOnlyRuntimeOptions = listLocalOnlyRuntimeOptions(runtimeOptions);
35974
- const providerMode = config.provider.mode === "cloud" && config.provider.source !== "explicit" && localOnlyRuntimeOptions.length > 0 ? "local" : config.provider.mode;
35975
- if (config.provider.mode === "cloud" && config.provider.source === "explicit" && localOnlyRuntimeOptions.length > 0) {
35976
- throw new Error(
35977
- `provider=cloud does not support local runtime options: ${localOnlyRuntimeOptions.join(", ")}.`
35978
- );
35979
- }
35980
- assertProviderSupportsEngine(providerMode, engine);
35981
- if (providerMode === "cloud") {
36183
+ assertProviderSupportsEngine(config.provider.mode, engine);
36184
+ if (config.provider.mode === "cloud") {
35982
36185
  return new CloudSessionProxy(new OpensteerCloudClient(config.cloud), {
35983
36186
  ...runtimeOptions.rootDir === void 0 ? {} : { rootDir: runtimeOptions.rootDir },
35984
36187
  ...runtimeOptions.rootPath === void 0 ? {} : { rootPath: runtimeOptions.rootPath },
@@ -35992,9 +36195,6 @@ function createOpensteerSemanticRuntime(input = {}) {
35992
36195
  engineName: engine
35993
36196
  });
35994
36197
  }
35995
- function listLocalOnlyRuntimeOptions(runtimeOptions) {
35996
- return LOCAL_ONLY_RUNTIME_OPTION_KEYS.filter((key) => runtimeOptions[key] !== void 0);
35997
- }
35998
36198
  async function collectOpensteerStatus(input) {
35999
36199
  const output = {
36000
36200
  provider: {
@@ -36152,6 +36352,43 @@ function formatLaneRow(input) {
36152
36352
  const summary = input.summary.padEnd(16, " ");
36153
36353
  return `${input.marker} ${provider} ${status} ${summary}${input.detail ?? ""}`.trimEnd();
36154
36354
  }
36355
+ async function openBrowserUrl(url) {
36356
+ const command = resolveOpenBrowserCommand(url);
36357
+ await new Promise((resolve4, reject) => {
36358
+ const child = child_process.spawn(command.executable, command.args, {
36359
+ detached: process3__default.default.platform !== "win32",
36360
+ stdio: "ignore"
36361
+ });
36362
+ child.once("error", reject);
36363
+ child.once("spawn", () => {
36364
+ child.unref();
36365
+ resolve4();
36366
+ });
36367
+ });
36368
+ }
36369
+ function resolveOpenBrowserCommand(url) {
36370
+ if (process3__default.default.platform === "darwin") {
36371
+ return {
36372
+ executable: "open",
36373
+ args: [url]
36374
+ };
36375
+ }
36376
+ if (process3__default.default.platform === "win32" || isWsl()) {
36377
+ return {
36378
+ executable: process3__default.default.platform === "win32" ? "cmd" : "cmd.exe",
36379
+ args: ["/c", "start", "", url]
36380
+ };
36381
+ }
36382
+ return {
36383
+ executable: "xdg-open",
36384
+ args: [url]
36385
+ };
36386
+ }
36387
+ function isWsl() {
36388
+ return process3__default.default.platform === "linux" && (process3__default.default.env.WSL_DISTRO_NAME !== void 0 || os__default.default.release().toLowerCase().includes("microsoft"));
36389
+ }
36390
+
36391
+ // src/cli/record.ts
36155
36392
  async function runOpensteerRecordCommand(input) {
36156
36393
  const stdout = input.stdout ?? process.stdout;
36157
36394
  const stderr = input.stderr ?? process.stderr;
@@ -36202,6 +36439,72 @@ async function runOpensteerRecordCommand(input) {
36202
36439
  }
36203
36440
  }
36204
36441
  }
36442
+ async function runOpensteerCloudRecordCommand(input) {
36443
+ const stdout = input.stdout ?? process.stdout;
36444
+ const stderr = input.stderr ?? process.stderr;
36445
+ const cloudAppBaseUrl = requireCloudAppBaseUrl(input.cloudConfig);
36446
+ const outputPath = resolveRecordOutputPath({
36447
+ rootDir: input.rootDir,
36448
+ workspace: input.workspace,
36449
+ ...input.outputPath === void 0 ? {} : { outputPath: input.outputPath }
36450
+ });
36451
+ let cloud;
36452
+ const resolveCloud = () => {
36453
+ cloud ??= new OpensteerCloudClient(input.cloudConfig);
36454
+ return cloud;
36455
+ };
36456
+ const runtime = input.runtime ?? new CloudSessionProxy(resolveCloud(), {
36457
+ rootDir: input.rootDir,
36458
+ workspace: input.workspace
36459
+ });
36460
+ const client = input.client ?? resolveCloud();
36461
+ const sleep5 = input.sleep ?? delay3;
36462
+ const openUrl = input.openUrl ?? openBrowserUrl;
36463
+ let closed = false;
36464
+ try {
36465
+ await runtime.open({
36466
+ url: input.url,
36467
+ ...input.browser === void 0 ? {} : { browser: input.browser },
36468
+ ...input.launch === void 0 ? {} : { launch: input.launch },
36469
+ ...input.context === void 0 ? {} : { context: input.context }
36470
+ });
36471
+ const sessionId = await resolveCloudRecordingSessionId(runtime);
36472
+ const sessionUrl = buildCloudRecordingSessionUrl(cloudAppBaseUrl, sessionId);
36473
+ await client.startSessionRecording(sessionId);
36474
+ await tryOpenCloudRecordingSessionUrl({
36475
+ sessionUrl,
36476
+ stderr,
36477
+ openUrl
36478
+ });
36479
+ stderr.write(
36480
+ `Recording browser actions for workspace "${input.workspace}". Open ${sessionUrl} and click "Stop recording" in the browser session toolbar when you're done.
36481
+ `
36482
+ );
36483
+ const completed = await waitForCloudRecordingCompletion({
36484
+ client,
36485
+ sessionId,
36486
+ ...input.pollIntervalMs === void 0 ? {} : { pollIntervalMs: input.pollIntervalMs },
36487
+ sleep: sleep5
36488
+ });
36489
+ if (completed.result === void 0) {
36490
+ throw new Error("Cloud recording completed without a replay script.");
36491
+ }
36492
+ await promises.mkdir(path7__default.default.dirname(outputPath), { recursive: true });
36493
+ await promises.writeFile(outputPath, completed.result.script, "utf8");
36494
+ await runtime.close();
36495
+ closed = true;
36496
+ stdout.write(`${outputPath}
36497
+ `);
36498
+ stderr.write(`Cloud browser session: ${sessionUrl}
36499
+ `);
36500
+ stderr.write(`Wrote replay script to ${outputPath}
36501
+ `);
36502
+ } finally {
36503
+ if (!closed) {
36504
+ await runtime.close().catch(() => void 0);
36505
+ }
36506
+ }
36507
+ }
36205
36508
  function resolveRecordOutputPath(input) {
36206
36509
  if (input.outputPath !== void 0) {
36207
36510
  return path7__default.default.resolve(input.rootDir, input.outputPath);
@@ -36236,6 +36539,39 @@ function createRecorderRuntimeAdapter(runtime) {
36236
36539
  }
36237
36540
  };
36238
36541
  }
36542
+ function buildCloudRecordingSessionUrl(appBaseUrl, sessionId) {
36543
+ return `${appBaseUrl}/browsers/${encodeURIComponent(sessionId)}`;
36544
+ }
36545
+ async function tryOpenCloudRecordingSessionUrl(input) {
36546
+ try {
36547
+ await input.openUrl(input.sessionUrl);
36548
+ } catch {
36549
+ input.stderr.write(
36550
+ `Could not automatically open the cloud browser session. Open it manually: ${input.sessionUrl}
36551
+ `
36552
+ );
36553
+ }
36554
+ }
36555
+ async function resolveCloudRecordingSessionId(runtime) {
36556
+ const info = await runtime.info();
36557
+ if (typeof info.sessionId !== "string" || info.sessionId.length === 0) {
36558
+ throw new Error("Cloud recording could not resolve the created session id.");
36559
+ }
36560
+ return info.sessionId;
36561
+ }
36562
+ async function waitForCloudRecordingCompletion(input) {
36563
+ const pollIntervalMs = input.pollIntervalMs ?? 1e3;
36564
+ for (; ; ) {
36565
+ const state = await input.client.getSessionRecording(input.sessionId);
36566
+ if (state.status === "completed") {
36567
+ return state;
36568
+ }
36569
+ if (state.status === "failed") {
36570
+ throw new Error(state.error ?? "Cloud recording failed.");
36571
+ }
36572
+ await input.sleep(pollIntervalMs);
36573
+ }
36574
+ }
36239
36575
  function formatRecordedAction(action) {
36240
36576
  const time = new Date(action.timestamp).toISOString().slice(11, 19);
36241
36577
  switch (action.kind) {
@@ -36267,6 +36603,11 @@ function formatRecordedAction(action) {
36267
36603
  return `[${time}] reload ${action.pageId}`;
36268
36604
  }
36269
36605
  }
36606
+ function delay3(ms) {
36607
+ return new Promise((resolve4) => {
36608
+ setTimeout(resolve4, ms);
36609
+ });
36610
+ }
36270
36611
 
36271
36612
  // src/cli/bin.ts
36272
36613
  var OPERATION_ALIASES = /* @__PURE__ */ new Map([
@@ -36323,7 +36664,7 @@ var OPERATION_ALIASES = /* @__PURE__ */ new Map([
36323
36664
  ["close", "session.close"]
36324
36665
  ]);
36325
36666
  async function main() {
36326
- const argv = process2__default.default.argv.slice(2);
36667
+ const argv = process3__default.default.argv.slice(2);
36327
36668
  const bootstrapAction = resolveCliBootstrapAction(argv);
36328
36669
  if (bootstrapAction === "version") {
36329
36670
  printVersion();
@@ -36333,7 +36674,7 @@ async function main() {
36333
36674
  printHelp();
36334
36675
  return;
36335
36676
  }
36336
- await loadCliEnvironment(process2__default.default.cwd());
36677
+ await loadCliEnvironment(process3__default.default.cwd());
36337
36678
  const parsed = parseCommandLine(argv);
36338
36679
  if (parsed.command[0] === "browser") {
36339
36680
  await handleBrowserCommand(parsed);
@@ -36350,7 +36691,7 @@ async function main() {
36350
36691
  list: parsed.options.list === true
36351
36692
  });
36352
36693
  if (exitCode !== 0) {
36353
- process2__default.default.exitCode = exitCode;
36694
+ process3__default.default.exitCode = exitCode;
36354
36695
  }
36355
36696
  return;
36356
36697
  }
@@ -36381,19 +36722,19 @@ async function main() {
36381
36722
  engine: engineName,
36382
36723
  runtimeOptions: {
36383
36724
  workspace: parsed.options.workspace,
36384
- rootDir: process2__default.default.cwd(),
36725
+ rootDir: process3__default.default.cwd(),
36385
36726
  browser: "persistent",
36386
36727
  ...parsed.options.launch === void 0 ? {} : { launch: parsed.options.launch },
36387
36728
  ...parsed.options.context === void 0 ? {} : { context: parsed.options.context }
36388
36729
  }
36389
36730
  });
36390
36731
  const result2 = await runtime2.close();
36391
- process2__default.default.stdout.write(`${JSON.stringify(result2, null, 2)}
36732
+ process3__default.default.stdout.write(`${JSON.stringify(result2, null, 2)}
36392
36733
  `);
36393
36734
  return;
36394
36735
  }
36395
36736
  const manager = new OpensteerBrowserManager({
36396
- rootDir: process2__default.default.cwd(),
36737
+ rootDir: process3__default.default.cwd(),
36397
36738
  workspace: parsed.options.workspace,
36398
36739
  engineName,
36399
36740
  browser: "persistent",
@@ -36401,7 +36742,7 @@ async function main() {
36401
36742
  ...parsed.options.context === void 0 ? {} : { context: parsed.options.context }
36402
36743
  });
36403
36744
  await manager.close();
36404
- process2__default.default.stdout.write(`${JSON.stringify({ closed: true }, null, 2)}
36745
+ process3__default.default.stdout.write(`${JSON.stringify({ closed: true }, null, 2)}
36405
36746
  `);
36406
36747
  return;
36407
36748
  }
@@ -36410,7 +36751,7 @@ async function main() {
36410
36751
  engine: engineName,
36411
36752
  runtimeOptions: {
36412
36753
  workspace: parsed.options.workspace,
36413
- rootDir: process2__default.default.cwd(),
36754
+ rootDir: process3__default.default.cwd(),
36414
36755
  ...parsed.options.browser === void 0 ? {} : { browser: parsed.options.browser },
36415
36756
  ...parsed.options.launch === void 0 ? {} : { launch: parsed.options.launch },
36416
36757
  ...parsed.options.context === void 0 ? {} : { context: parsed.options.context }
@@ -36429,7 +36770,7 @@ async function main() {
36429
36770
  } finally {
36430
36771
  await runtime.disconnect().catch(() => void 0);
36431
36772
  }
36432
- process2__default.default.stdout.write(`${JSON.stringify(result, null, 2)}
36773
+ process3__default.default.stdout.write(`${JSON.stringify(result, null, 2)}
36433
36774
  `);
36434
36775
  }
36435
36776
  async function handleBrowserCommand(parsed) {
@@ -36438,7 +36779,7 @@ async function handleBrowserCommand(parsed) {
36438
36779
  const result = await discoverLocalCdpBrowsers({
36439
36780
  ...parsed.options.launch?.timeoutMs === void 0 ? {} : { timeoutMs: parsed.options.launch.timeoutMs }
36440
36781
  });
36441
- process2__default.default.stdout.write(`${JSON.stringify(result, null, 2)}
36782
+ process3__default.default.stdout.write(`${JSON.stringify(result, null, 2)}
36442
36783
  `);
36443
36784
  return;
36444
36785
  }
@@ -36454,7 +36795,7 @@ async function handleBrowserCommand(parsed) {
36454
36795
  ...parsed.options.attachHeaders === void 0 ? {} : { headers: parsed.options.attachHeaders },
36455
36796
  ...parsed.options.launch?.timeoutMs === void 0 ? {} : { timeoutMs: parsed.options.launch.timeoutMs }
36456
36797
  });
36457
- process2__default.default.stdout.write(`${JSON.stringify(result, null, 2)}
36798
+ process3__default.default.stdout.write(`${JSON.stringify(result, null, 2)}
36458
36799
  `);
36459
36800
  return;
36460
36801
  }
@@ -36463,7 +36804,7 @@ async function handleBrowserCommand(parsed) {
36463
36804
  }
36464
36805
  const engineName = resolveCliEngineName(parsed);
36465
36806
  const manager = new OpensteerBrowserManager({
36466
- rootDir: process2__default.default.cwd(),
36807
+ rootDir: process3__default.default.cwd(),
36467
36808
  workspace: parsed.options.workspace,
36468
36809
  engineName,
36469
36810
  browser: "persistent",
@@ -36473,7 +36814,7 @@ async function handleBrowserCommand(parsed) {
36473
36814
  switch (subcommand) {
36474
36815
  case "status": {
36475
36816
  const result = await manager.status();
36476
- process2__default.default.stdout.write(`${JSON.stringify(result, null, 2)}
36817
+ process3__default.default.stdout.write(`${JSON.stringify(result, null, 2)}
36477
36818
  `);
36478
36819
  return;
36479
36820
  }
@@ -36486,19 +36827,19 @@ async function handleBrowserCommand(parsed) {
36486
36827
  sourceUserDataDir,
36487
36828
  ...parsed.options.sourceProfileDirectory === void 0 ? {} : { sourceProfileDirectory: parsed.options.sourceProfileDirectory }
36488
36829
  });
36489
- process2__default.default.stdout.write(`${JSON.stringify(result, null, 2)}
36830
+ process3__default.default.stdout.write(`${JSON.stringify(result, null, 2)}
36490
36831
  `);
36491
36832
  return;
36492
36833
  }
36493
36834
  case "reset": {
36494
36835
  await manager.reset();
36495
- process2__default.default.stdout.write(`${JSON.stringify({ reset: true }, null, 2)}
36836
+ process3__default.default.stdout.write(`${JSON.stringify({ reset: true }, null, 2)}
36496
36837
  `);
36497
36838
  return;
36498
36839
  }
36499
36840
  case "delete": {
36500
36841
  await manager.delete();
36501
- process2__default.default.stdout.write(`${JSON.stringify({ deleted: true }, null, 2)}
36842
+ process3__default.default.stdout.write(`${JSON.stringify({ deleted: true }, null, 2)}
36502
36843
  `);
36503
36844
  return;
36504
36845
  }
@@ -36516,22 +36857,39 @@ async function handleRecordCommandEntry(parsed) {
36516
36857
  }
36517
36858
  const provider = resolveCliProvider(parsed);
36518
36859
  assertCloudCliOptionsMatchProvider(parsed, provider.mode);
36519
- if (provider.mode !== "local") {
36520
- throw new Error(
36521
- 'record requires provider=local. Set "--provider local" or clear OPENSTEER_PROVIDER.'
36522
- );
36523
- }
36524
36860
  const engineName = resolveCliEngineName(parsed);
36525
36861
  if (engineName !== "playwright") {
36526
36862
  throw new Error("record requires engine=playwright.");
36527
36863
  }
36528
- if (parsed.options.browser !== void 0 && parsed.options.browser !== "persistent") {
36529
- throw new Error('record only supports "--browser persistent".');
36864
+ const rootDir = process3__default.default.cwd();
36865
+ const recordBrowser = parsed.options.browser;
36866
+ if (provider.mode === "cloud") {
36867
+ if (typeof recordBrowser === "object") {
36868
+ throw new Error('record does not support browser.mode="attach".');
36869
+ }
36870
+ const runtimeProvider = buildCliRuntimeProvider(parsed, provider.mode);
36871
+ const runtimeConfig = resolveOpensteerRuntimeConfig({
36872
+ ...runtimeProvider === void 0 ? {} : { provider: runtimeProvider },
36873
+ environment: process3__default.default.env
36874
+ });
36875
+ await runOpensteerCloudRecordCommand({
36876
+ cloudConfig: runtimeConfig.cloud,
36877
+ workspace: parsed.options.workspace,
36878
+ url,
36879
+ rootDir,
36880
+ ...recordBrowser === void 0 ? {} : { browser: recordBrowser },
36881
+ ...parsed.options.launch === void 0 ? {} : { launch: parsed.options.launch },
36882
+ ...parsed.options.context === void 0 ? {} : { context: parsed.options.context },
36883
+ ...parsed.options.output === void 0 ? {} : { outputPath: parsed.options.output }
36884
+ });
36885
+ return;
36530
36886
  }
36531
36887
  if (parsed.options.launch?.headless === true) {
36532
36888
  throw new Error('record requires a headed browser. Remove "--headless true".');
36533
36889
  }
36534
- const rootDir = process2__default.default.cwd();
36890
+ if (recordBrowser !== void 0 && recordBrowser !== "persistent") {
36891
+ throw new Error('record only supports "--browser persistent".');
36892
+ }
36535
36893
  const launch = {
36536
36894
  ...parsed.options.launch ?? {},
36537
36895
  headless: false
@@ -36671,6 +37029,7 @@ var CLI_OPTION_SPECS = {
36671
37029
  provider: { kind: "value" },
36672
37030
  "cloud-base-url": { kind: "value" },
36673
37031
  "cloud-api-key": { kind: "value" },
37032
+ "cloud-app-base-url": { kind: "value" },
36674
37033
  "cloud-profile-id": { kind: "value" },
36675
37034
  "cloud-profile-reuse-if-active": { kind: "boolean" },
36676
37035
  json: { kind: "boolean" },
@@ -36800,6 +37159,7 @@ function parseCommandLine(argv) {
36800
37159
  const provider = providerValue === void 0 ? void 0 : normalizeOpensteerProviderMode(providerValue, "--provider");
36801
37160
  const cloudBaseUrl = readSingle(rawOptions, "cloud-base-url");
36802
37161
  const cloudApiKey = readSingle(rawOptions, "cloud-api-key");
37162
+ const cloudAppBaseUrl = readSingle(rawOptions, "cloud-app-base-url");
36803
37163
  const cloudProfileId = readSingle(rawOptions, "cloud-profile-id");
36804
37164
  const cloudProfileReuseIfActive = readOptionalBoolean(
36805
37165
  rawOptions,
@@ -36821,6 +37181,7 @@ function parseCommandLine(argv) {
36821
37181
  ...provider === void 0 ? {} : { provider },
36822
37182
  ...cloudBaseUrl === void 0 ? {} : { cloudBaseUrl },
36823
37183
  ...cloudApiKey === void 0 ? {} : { cloudApiKey },
37184
+ ...cloudAppBaseUrl === void 0 ? {} : { cloudAppBaseUrl },
36824
37185
  ...cloudProfileId === void 0 ? {} : { cloudProfileId },
36825
37186
  ...cloudProfileReuseIfActive === void 0 ? {} : { cloudProfileReuseIfActive },
36826
37187
  ...json === void 0 ? {} : { json },
@@ -36889,14 +37250,14 @@ function buildCliExplicitProvider(parsed) {
36889
37250
  function resolveCliEngineName(parsed) {
36890
37251
  return resolveOpensteerEngineName({
36891
37252
  ...parsed.options.requestedEngineName === void 0 ? {} : { requested: parsed.options.requestedEngineName },
36892
- ...process2__default.default.env.OPENSTEER_ENGINE === void 0 ? {} : { environment: process2__default.default.env.OPENSTEER_ENGINE }
37253
+ ...process3__default.default.env.OPENSTEER_ENGINE === void 0 ? {} : { environment: process3__default.default.env.OPENSTEER_ENGINE }
36893
37254
  });
36894
37255
  }
36895
37256
  function resolveCliProvider(parsed) {
36896
37257
  const explicitProvider = buildCliExplicitProvider(parsed);
36897
37258
  return resolveOpensteerProvider({
36898
37259
  ...explicitProvider === void 0 ? {} : { provider: explicitProvider },
36899
- ...process2__default.default.env.OPENSTEER_PROVIDER === void 0 ? {} : { environmentProvider: process2__default.default.env.OPENSTEER_PROVIDER }
37260
+ ...process3__default.default.env.OPENSTEER_PROVIDER === void 0 ? {} : { environmentProvider: process3__default.default.env.OPENSTEER_PROVIDER }
36900
37261
  });
36901
37262
  }
36902
37263
  function buildCliRuntimeProvider(parsed, providerMode) {
@@ -36905,7 +37266,7 @@ function buildCliRuntimeProvider(parsed, providerMode) {
36905
37266
  return explicitProvider?.mode === "local" ? explicitProvider : void 0;
36906
37267
  }
36907
37268
  const browserProfile = buildCliBrowserProfile(parsed);
36908
- const hasCloudOverrides = parsed.options.cloudBaseUrl !== void 0 || parsed.options.cloudApiKey !== void 0 || browserProfile !== void 0;
37269
+ const hasCloudOverrides = parsed.options.cloudBaseUrl !== void 0 || parsed.options.cloudApiKey !== void 0 || parsed.options.cloudAppBaseUrl !== void 0 || browserProfile !== void 0;
36909
37270
  if (!hasCloudOverrides && explicitProvider?.mode !== "cloud") {
36910
37271
  return void 0;
36911
37272
  }
@@ -36913,11 +37274,12 @@ function buildCliRuntimeProvider(parsed, providerMode) {
36913
37274
  mode: "cloud",
36914
37275
  ...parsed.options.cloudBaseUrl === void 0 ? {} : { baseUrl: parsed.options.cloudBaseUrl },
36915
37276
  ...parsed.options.cloudApiKey === void 0 ? {} : { apiKey: parsed.options.cloudApiKey },
37277
+ ...parsed.options.cloudAppBaseUrl === void 0 ? {} : { appBaseUrl: parsed.options.cloudAppBaseUrl },
36916
37278
  ...browserProfile === void 0 ? {} : { browserProfile }
36917
37279
  };
36918
37280
  }
36919
37281
  function assertCloudCliOptionsMatchProvider(parsed, providerMode) {
36920
- if (providerMode !== "cloud" && (parsed.options.cloudBaseUrl !== void 0 || parsed.options.cloudApiKey !== void 0 || parsed.options.cloudProfileId !== void 0 || parsed.options.cloudProfileReuseIfActive === true)) {
37282
+ 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)) {
36921
37283
  throw new Error(
36922
37284
  'Cloud-specific options require provider=cloud. Set "--provider cloud" or OPENSTEER_PROVIDER=cloud.'
36923
37285
  );
@@ -36929,20 +37291,20 @@ async function handleStatusCommand(parsed) {
36929
37291
  const runtimeProvider = buildCliRuntimeProvider(parsed, provider.mode);
36930
37292
  const runtimeConfig = resolveOpensteerRuntimeConfig({
36931
37293
  ...runtimeProvider === void 0 ? {} : { provider: runtimeProvider },
36932
- environment: process2__default.default.env
37294
+ environment: process3__default.default.env
36933
37295
  });
36934
37296
  const status = await collectOpensteerStatus({
36935
- rootDir: process2__default.default.cwd(),
37297
+ rootDir: process3__default.default.cwd(),
36936
37298
  ...parsed.options.workspace === void 0 ? {} : { workspace: parsed.options.workspace },
36937
37299
  provider,
36938
37300
  ...runtimeConfig.cloud === void 0 ? {} : { cloudConfig: runtimeConfig.cloud }
36939
37301
  });
36940
37302
  if (parsed.options.json === true) {
36941
- process2__default.default.stdout.write(`${JSON.stringify(status, null, 2)}
37303
+ process3__default.default.stdout.write(`${JSON.stringify(status, null, 2)}
36942
37304
  `);
36943
37305
  return;
36944
37306
  }
36945
- process2__default.default.stdout.write(renderOpensteerStatus(status));
37307
+ process3__default.default.stdout.write(renderOpensteerStatus(status));
36946
37308
  }
36947
37309
  function parseKeyValueList(values) {
36948
37310
  if (values === void 0 || values.length === 0) {
@@ -37027,7 +37389,7 @@ function resolveCommandLength(tokens) {
37027
37389
  return Math.min(tokens.length, 1);
37028
37390
  }
37029
37391
  function printHelp() {
37030
- process2__default.default.stdout.write(`Opensteer v2 CLI
37392
+ process3__default.default.stdout.write(`Opensteer v2 CLI
37031
37393
 
37032
37394
  Usage:
37033
37395
  opensteer open <url> --workspace <id> [--browser persistent|temporary|attach]
@@ -37059,6 +37421,7 @@ Common options:
37059
37421
  --provider local|cloud
37060
37422
  --cloud-base-url <url>
37061
37423
  --cloud-api-key <key>
37424
+ --cloud-app-base-url <url>
37062
37425
  --cloud-profile-id <id>
37063
37426
  --cloud-profile-reuse-if-active <true|false>
37064
37427
  --json <true|false>
@@ -37077,7 +37440,7 @@ Common options:
37077
37440
  `);
37078
37441
  }
37079
37442
  function printVersion() {
37080
- process2__default.default.stdout.write(`${package_default.version}
37443
+ process3__default.default.stdout.write(`${package_default.version}
37081
37444
  `);
37082
37445
  }
37083
37446
  main().catch((error) => {
@@ -37092,9 +37455,9 @@ main().catch((error) => {
37092
37455
  message: String(error)
37093
37456
  }
37094
37457
  };
37095
- process2__default.default.stderr.write(`${JSON.stringify(payload)}
37458
+ process3__default.default.stderr.write(`${JSON.stringify(payload)}
37096
37459
  `);
37097
- process2__default.default.exitCode = 1;
37460
+ process3__default.default.exitCode = 1;
37098
37461
  });
37099
37462
  //# sourceMappingURL=bin.cjs.map
37100
37463
  //# sourceMappingURL=bin.cjs.map