Skip to content

feat(cli): add browse macro record and replay commands#2257

Open
optimusbuilder wants to merge 2 commits into
browserbase:mainfrom
optimusbuilder:feat/browse-macro-cli
Open

feat(cli): add browse macro record and replay commands#2257
optimusbuilder wants to merge 2 commits into
browserbase:mainfrom
optimusbuilder:feat/browse-macro-cli

Conversation

@optimusbuilder

@optimusbuilder optimusbuilder commented Jun 17, 2026

Copy link
Copy Markdown

why

Users running repeatable browse workflows need a lightweight way to capture and replay driver command sequences without scripting each step manually.

what changed

  • Add browse macro commands: record, stop, run, list, show, and delete
  • Record successful driver commands via the shared runDriverCommandFromFlags path after execution
  • Persist macros to ~/.browse/macros/<name>.json (override with BROWSE_MACRO_DIR)
  • Replay macros step-by-step with optional --delay between steps
  • Skip inspection-only commands (snapshot, refs, cursor, tab.list) from recordings
  • Add unit tests for macro storage, recording, and replay

test plan

  • pnpm --filter browse build
  • pnpm --filter browse exec vitest run tests/macro.test.ts
  • browse macro record login-flow
  • Run a few driver commands (open, click, fill)
  • browse macro stop and browse macro show login-flow
  • browse macro run login-flow in an active session

Made with Cursor


Summary by cubic

Adds browse macro commands to record and replay driver command sequences so users can save repeatable flows and run them later in an active session. Macros are stored on disk and can be listed, inspected, and deleted.

  • New Features

    • New browse macro subcommands: record, stop, run, list, show, delete; macros persist to ~/.browse/macros/<name>.json (override with BROWSE_MACRO_DIR) and can replay with --delay.
    • Records successful driver commands via the shared runDriverCommandFromFlags path, skipping inspection-only commands (snapshot, refs, cursor, tab.list); includes unit tests.
  • Bug Fixes

    • Emit driver results before best-effort recording, and macro delete now fails cleanly when a name is not found (no --force).

Written for commit 6aa7337. Summary will update on new commits.

Review in cubic

Let users capture successful driver commands into reusable macros and replay them in a session.

Co-authored-by: Cursor <cursoragent@cursor.com>
@changeset-bot

changeset-bot Bot commented Jun 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6aa7337

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

Copy link
Copy Markdown
Contributor

This PR is from an external contributor and must be approved by a stagehand team member with write access before CI can run.
Approving the latest commit mirrors it into an internal PR owned by the approver.
If new commits are pushed later, the internal PR stays open but is marked stale until someone approves the latest external commit and refreshes it.

@github-actions github-actions Bot added external-contributor Tracks PRs mirrored from external contributor forks. external-contributor:awaiting-approval Waiting for a stagehand team member to approve the latest external commit. labels Jun 17, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 14 files

Confidence score: 3/5

  • In packages/cli/src/lib/driver/command-cli.ts, macro recording can throw before outputJson runs, which can turn a successful driver operation into a reported CLI failure and hide valid output from users/automation. Make recording persistence best-effort (or move it after result emission) so filesystem errors don’t mask command success before merging.
  • In packages/cli/src/commands/macro/delete.ts, throwing new Error() bypasses the typed CommandFailure flow and can skip centralized handling in BrowseCommand.catch, leading to inconsistent error formatting/exit behavior. Switch to fail()/CommandFailure so failures are normalized by the shared boundary.
  • In packages/cli/src/commands/macro/delete.ts, the documented --force behavior and examples don’t match implementation, so browse macro delete <name> currently fails where docs imply it should proceed/confirm. Align the command logic or update help/examples before merge to avoid immediate user confusion and failed workflows.
Architecture diagram
sequenceDiagram
    participant CLI as browse CLI
    participant MacroCmd as macro subcommand
    participant Recording as recording.ts
    participant Store as store.ts
    participant Replay as replay.ts
    participant Driver as command-cli.ts
    participant Runtime as driver/runtime.ts
    participant Session as Active Session

    Note over CLI,Session: Record Flow

    CLI->>MacroCmd: macro record <name>
    MacroCmd->>Recording: startMacroRecording(name)
    Recording->>Store: readRecordingState()
    Store-->>Recording: null (no active recording)
    Recording->>Store: writeRecordingState({name, steps:[]})
    Recording-->>MacroCmd: started
    MacroCmd-->>CLI: {recording: true}

    CLI->>Driver: run driver command (e.g., open, click)
    Driver->>Runtime: runDriverCommandWithTarget()
    Runtime-->>Driver: result
    Driver->>Recording: appendMacroStepIfRecording(command, params)
    alt command is snapshot/refs/cursor/tab.list
        Recording-->>Driver: skip (no append)
    else command is recordable
        Recording->>Store: readRecordingState()
        Store-->>Recording: active state
        Recording->>Recording: push step {command, params}
        Recording->>Store: writeRecordingState(updated)
    end
    Driver-->>CLI: output result

    CLI->>MacroCmd: macro stop
    MacroCmd->>Recording: stopMacroRecording()
    Recording->>Store: readRecordingState()
    Store-->>Recording: active state
    Recording->>Recording: build BrowseMacro from state
    Recording->>Store: saveMacro(macro) -> ~/.browse/macros/<name>.json
    Recording->>Store: clearRecordingState()
    Recording-->>MacroCmd: {name, steps}
    MacroCmd-->>CLI: {message: "Saved..."}

    Note over CLI,Session: Replay Flow

    CLI->>MacroCmd: macro run <name> [--delay N]
    MacroCmd->>Replay: replayMacro({name, session, target, delayMs})
    Replay->>Store: loadMacro(name)
    Store-->>Replay: BrowseMacro with steps array
    loop each step in macro.steps
        Replay->>Runtime: runDriverCommandWithTarget(session, target, command, params)
        Runtime-->>Replay: result
        Replay->>Replay: push result to results array
        opt delayMs > 0
            Replay->>Replay: sleep(delayMs)
        end
    end
    Replay-->>MacroCmd: {macro, results}
    MacroCmd-->>CLI: {name, results, steps}

    Note over CLI,Session: List/Show/Delete Flow

    CLI->>MacroCmd: macro list
    MacroCmd->>Store: listMacroNames() -> scan ~/.browse/macros/*.json
    Store-->>MacroCmd: sorted macro names
    MacroCmd->>Recording: getActiveRecordingName()
    Recording-->>MacroCmd: active name or null
    MacroCmd-->>CLI: {macros, recording}

    CLI->>MacroCmd: macro show <name>
    MacroCmd->>Store: loadMacro(name) -> read JSON file
    Store-->>MacroCmd: BrowseMacro object
    MacroCmd-->>CLI: output macro JSON

    CLI->>MacroCmd: macro delete <name> --force
    MacroCmd->>Store: macroFilePath(name)
    MacroCmd->>MacroCmd: fs.unlink(file)
    MacroCmd-->>CLI: {deleted: true, name}
Loading

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread packages/cli/src/lib/driver/command-cli.ts Outdated
Comment thread packages/cli/src/commands/macro/delete.ts Outdated
Comment thread packages/cli/src/commands/macro/delete.ts Outdated
Emit driver results before best-effort recording, remove misleading --force on delete, and use fail() for not-found errors.

Co-authored-by: Cursor <cursoragent@cursor.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 4 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/cli/src/lib/macro/recording.ts">

<violation number="1" location="packages/cli/src/lib/macro/recording.ts:92">
P2: Silent suppression of macro recording errors can hide dropped steps and produce a misleadingly successful recording UX.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment on lines +92 to +94
} catch {
// Best-effort recording must not mask successful driver commands.
}

@cubic-dev-ai cubic-dev-ai Bot Jun 17, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Silent suppression of macro recording errors can hide dropped steps and produce a misleadingly successful recording UX.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/lib/macro/recording.ts, line 92:

<comment>Silent suppression of macro recording errors can hide dropped steps and produce a misleadingly successful recording UX.</comment>

<file context>
@@ -82,3 +82,14 @@ export async function getActiveRecordingName(): Promise<string | null> {
+): Promise<void> {
+  try {
+    await appendMacroStepIfRecording(command, params);
+  } catch {
+    // Best-effort recording must not mask successful driver commands.
+  }
</file context>
Suggested change
} catch {
// Best-effort recording must not mask successful driver commands.
}
} catch (error) {
// Best-effort recording must not mask successful driver commands.
// Surface the failure so users can diagnose missing macro steps.
console.error("Failed to record macro step:", error);
}
Fix with cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

external-contributor:awaiting-approval Waiting for a stagehand team member to approve the latest external commit. external-contributor Tracks PRs mirrored from external contributor forks.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant