Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 48 additions & 2 deletions cmd/browsers.go
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,8 @@ type BrowsersComputerMoveMouseInput struct {
X int64
Y int64
HoldKeys []string
Smooth *bool
DurationMs *int64
}

type BrowsersComputerScreenshotInput struct {
Expand Down Expand Up @@ -718,6 +720,8 @@ type BrowsersComputerDragMouseInput struct {
StepsPerSegment int64
Button string
HoldKeys []string
Smooth *bool
DurationMs *int64
}

type BrowsersComputerSetCursorInput struct {
Expand Down Expand Up @@ -767,6 +771,16 @@ func (b BrowsersCmd) ComputerMoveMouse(ctx context.Context, in BrowsersComputerM
if len(in.HoldKeys) > 0 {
body.HoldKeys = in.HoldKeys
}
extras := map[string]any{}
if in.Smooth != nil {
extras["smooth"] = *in.Smooth
}
if in.DurationMs != nil {
extras["duration_ms"] = *in.DurationMs
}
if len(extras) > 0 {
body.SetExtraFields(extras)
}
if err := b.computer.MoveMouse(ctx, br.SessionID, body); err != nil {
return util.CleanedUpSdkError{Err: err}
}
Expand Down Expand Up @@ -912,6 +926,16 @@ func (b BrowsersCmd) ComputerDragMouse(ctx context.Context, in BrowsersComputerD
if len(in.HoldKeys) > 0 {
body.HoldKeys = in.HoldKeys
}
extras := map[string]any{}
if in.Smooth != nil {
extras["smooth"] = *in.Smooth
}
if in.DurationMs != nil {
extras["duration_ms"] = *in.DurationMs
}
if len(extras) > 0 {
body.SetExtraFields(extras)
}
if err := b.computer.DragMouse(ctx, br.SessionID, body); err != nil {
return util.CleanedUpSdkError{Err: err}
}
Expand Down Expand Up @@ -2239,6 +2263,8 @@ func init() {
_ = computerMove.MarkFlagRequired("x")
_ = computerMove.MarkFlagRequired("y")
computerMove.Flags().StringSlice("hold-key", []string{}, "Modifier keys to hold (repeatable)")
computerMove.Flags().Bool("smooth", true, "Use human-like Bezier curve path instead of instant teleport")
computerMove.Flags().Int64("duration-ms", 0, "Target duration in ms for smooth movement (50-5000, 0 for auto)")

computerScreenshot := &cobra.Command{Use: "screenshot <id>", Short: "Capture a screenshot (optionally of a region)", Args: cobra.ExactArgs(1), RunE: runBrowsersComputerScreenshot}
computerScreenshot.Flags().Int64("x", 0, "Top-left X")
Expand Down Expand Up @@ -2278,6 +2304,8 @@ func init() {
computerDrag.Flags().Int64("steps-per-segment", 0, "Number of move steps per path segment")
computerDrag.Flags().String("button", "left", "Mouse button: left,middle,right")
computerDrag.Flags().StringSlice("hold-key", []string{}, "Modifier keys to hold (repeatable)")
computerDrag.Flags().Bool("smooth", true, "Use human-like Bezier curves between waypoints")
computerDrag.Flags().Int64("duration-ms", 0, "Target duration in ms for smooth drag (50-10000, 0 for auto)")

// computer set-cursor
computerSetCursor := &cobra.Command{Use: "set-cursor <id>", Short: "Hide or show the cursor", Args: cobra.ExactArgs(1), RunE: runBrowsersComputerSetCursor}
Expand Down Expand Up @@ -2864,8 +2892,17 @@ func runBrowsersComputerMoveMouse(cmd *cobra.Command, args []string) error {
x, _ := cmd.Flags().GetInt64("x")
y, _ := cmd.Flags().GetInt64("y")
holdKeys, _ := cmd.Flags().GetStringSlice("hold-key")
in := BrowsersComputerMoveMouseInput{Identifier: args[0], X: x, Y: y, HoldKeys: holdKeys}
if cmd.Flags().Changed("smooth") {
v, _ := cmd.Flags().GetBool("smooth")
in.Smooth = &v
}
if cmd.Flags().Changed("duration-ms") {
v, _ := cmd.Flags().GetInt64("duration-ms")
in.DurationMs = &v
}
Copy link

Choose a reason for hiding this comment

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

No validation for --duration-ms documented range

Low Severity

The --duration-ms flag's help text documents a valid range of 50–5000 (with 0 for auto), but neither runBrowsersComputerMoveMouse nor ComputerMoveMouse validates the value. Out-of-range values like negative numbers or values above 5000 are silently forwarded to the backend via SetExtraFields. The codebase already validates similar numeric flags (e.g., --width and --height must be greater than zero for screenshots), so this inconsistency could lead to confusing backend errors instead of clear CLI-level feedback.

Additional Locations (1)
Fix in Cursor Fix in Web

b := BrowsersCmd{browsers: &svc, computer: &svc.Computer}
return b.ComputerMoveMouse(cmd.Context(), BrowsersComputerMoveMouseInput{Identifier: args[0], X: x, Y: y, HoldKeys: holdKeys})
return b.ComputerMoveMouse(cmd.Context(), in)
}

func runBrowsersComputerScreenshot(cmd *cobra.Command, args []string) error {
Expand Down Expand Up @@ -2959,8 +2996,17 @@ func runBrowsersComputerDragMouse(cmd *cobra.Command, args []string) error {
path = append(path, []int64{x, y})
}

in := BrowsersComputerDragMouseInput{Identifier: args[0], Path: path, Delay: delay, StepDelayMs: stepDelayMs, StepsPerSegment: stepsPerSegment, Button: button, HoldKeys: holdKeys}
if cmd.Flags().Changed("smooth") {
v, _ := cmd.Flags().GetBool("smooth")
in.Smooth = &v
}
if cmd.Flags().Changed("duration-ms") {
v, _ := cmd.Flags().GetInt64("duration-ms")
in.DurationMs = &v
}
b := BrowsersCmd{browsers: &svc, computer: &svc.Computer}
return b.ComputerDragMouse(cmd.Context(), BrowsersComputerDragMouseInput{Identifier: args[0], Path: path, Delay: delay, StepDelayMs: stepDelayMs, StepsPerSegment: stepsPerSegment, Button: button, HoldKeys: holdKeys})
return b.ComputerDragMouse(cmd.Context(), in)
}

func runBrowsersComputerSetCursor(cmd *cobra.Command, args []string) error {
Expand Down
103 changes: 103 additions & 0 deletions cmd/browsers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,109 @@ func TestBrowsersComputerMoveMouse_PrintsSuccess(t *testing.T) {
assert.Contains(t, out, "Moved mouse to (5,6)")
}

func TestBrowsersComputerMoveMouse_SmoothFalse(t *testing.T) {
setupStdoutCapture(t)
fakeBrowsers := newFakeBrowsersServiceWithSimpleGet()
var capturedBody kernel.BrowserComputerMoveMouseParams
fakeComp := &FakeComputerService{
MoveMouseFunc: func(ctx context.Context, id string, body kernel.BrowserComputerMoveMouseParams, opts ...option.RequestOption) error {
capturedBody = body
return nil
},
}
b := BrowsersCmd{browsers: fakeBrowsers, computer: fakeComp}
smooth := false
_ = b.ComputerMoveMouse(context.Background(), BrowsersComputerMoveMouseInput{Identifier: "id", X: 100, Y: 200, Smooth: &smooth})
extras := capturedBody.ExtraFields()
assert.Contains(t, extras, "smooth")
assert.Equal(t, false, extras["smooth"])
}

func TestBrowsersComputerMoveMouse_DurationMs(t *testing.T) {
setupStdoutCapture(t)
fakeBrowsers := newFakeBrowsersServiceWithSimpleGet()
var capturedBody kernel.BrowserComputerMoveMouseParams
fakeComp := &FakeComputerService{
MoveMouseFunc: func(ctx context.Context, id string, body kernel.BrowserComputerMoveMouseParams, opts ...option.RequestOption) error {
capturedBody = body
return nil
},
}
b := BrowsersCmd{browsers: fakeBrowsers, computer: fakeComp}
smooth := true
dur := int64(1500)
_ = b.ComputerMoveMouse(context.Background(), BrowsersComputerMoveMouseInput{Identifier: "id", X: 100, Y: 200, Smooth: &smooth, DurationMs: &dur})
extras := capturedBody.ExtraFields()
assert.Contains(t, extras, "smooth")
assert.Equal(t, true, extras["smooth"])
assert.Contains(t, extras, "duration_ms")
assert.Equal(t, int64(1500), extras["duration_ms"])
}

func TestBrowsersComputerMoveMouse_NoSmoothFlag(t *testing.T) {
setupStdoutCapture(t)
fakeBrowsers := newFakeBrowsersServiceWithSimpleGet()
var capturedBody kernel.BrowserComputerMoveMouseParams
fakeComp := &FakeComputerService{
MoveMouseFunc: func(ctx context.Context, id string, body kernel.BrowserComputerMoveMouseParams, opts ...option.RequestOption) error {
capturedBody = body
return nil
},
}
b := BrowsersCmd{browsers: fakeBrowsers, computer: fakeComp}
_ = b.ComputerMoveMouse(context.Background(), BrowsersComputerMoveMouseInput{Identifier: "id", X: 100, Y: 200})
extras := capturedBody.ExtraFields()
assert.Empty(t, extras)
}

func TestBrowsersComputerDragMouse_SmoothFalse(t *testing.T) {
setupStdoutCapture(t)
fakeBrowsers := newFakeBrowsersServiceWithSimpleGet()
var capturedBody kernel.BrowserComputerDragMouseParams
fakeComp := &FakeComputerService{
DragMouseFunc: func(ctx context.Context, id string, body kernel.BrowserComputerDragMouseParams, opts ...option.RequestOption) error {
capturedBody = body
return nil
},
}
b := BrowsersCmd{browsers: fakeBrowsers, computer: fakeComp}
smooth := false
_ = b.ComputerDragMouse(context.Background(), BrowsersComputerDragMouseInput{
Identifier: "id",
Path: [][]int64{{100, 200}, {300, 400}},
Smooth: &smooth,
})
extras := capturedBody.ExtraFields()
assert.Contains(t, extras, "smooth")
assert.Equal(t, false, extras["smooth"])
}

func TestBrowsersComputerDragMouse_DurationMs(t *testing.T) {
setupStdoutCapture(t)
fakeBrowsers := newFakeBrowsersServiceWithSimpleGet()
var capturedBody kernel.BrowserComputerDragMouseParams
fakeComp := &FakeComputerService{
DragMouseFunc: func(ctx context.Context, id string, body kernel.BrowserComputerDragMouseParams, opts ...option.RequestOption) error {
capturedBody = body
return nil
},
}
b := BrowsersCmd{browsers: fakeBrowsers, computer: fakeComp}
smooth := true
dur := int64(3000)
_ = b.ComputerDragMouse(context.Background(), BrowsersComputerDragMouseInput{
Identifier: "id",
Path: [][]int64{{100, 200}, {300, 400}},
Smooth: &smooth,
DurationMs: &dur,
})
extras := capturedBody.ExtraFields()
assert.Contains(t, extras, "smooth")
assert.Equal(t, true, extras["smooth"])
assert.Contains(t, extras, "duration_ms")
assert.Equal(t, int64(3000), extras["duration_ms"])
}

func TestBrowsersComputerScreenshot_SavesFile(t *testing.T) {
setupStdoutCapture(t)
dir := t.TempDir()
Expand Down
Loading