Skip to content

Commit 8e8fe38

Browse files
feat: add --smooth and --duration-ms flags to move-mouse
Support human-like Bezier curve mouse movement from the CLI. Smooth is enabled by default; pass --smooth=false for instant teleport. Use --duration-ms to control movement timing (50-5000ms). Made-with: Cursor
1 parent 2d940f3 commit 8e8fe38

2 files changed

Lines changed: 79 additions & 1 deletion

File tree

cmd/browsers.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,8 @@ type BrowsersComputerMoveMouseInput struct {
674674
X int64
675675
Y int64
676676
HoldKeys []string
677+
Smooth *bool
678+
DurationMs *int64
677679
}
678680

679681
type BrowsersComputerScreenshotInput struct {
@@ -767,6 +769,16 @@ func (b BrowsersCmd) ComputerMoveMouse(ctx context.Context, in BrowsersComputerM
767769
if len(in.HoldKeys) > 0 {
768770
body.HoldKeys = in.HoldKeys
769771
}
772+
extras := map[string]any{}
773+
if in.Smooth != nil {
774+
extras["smooth"] = *in.Smooth
775+
}
776+
if in.DurationMs != nil {
777+
extras["duration_ms"] = *in.DurationMs
778+
}
779+
if len(extras) > 0 {
780+
body.SetExtraFields(extras)
781+
}
770782
if err := b.computer.MoveMouse(ctx, br.SessionID, body); err != nil {
771783
return util.CleanedUpSdkError{Err: err}
772784
}
@@ -2239,6 +2251,8 @@ func init() {
22392251
_ = computerMove.MarkFlagRequired("x")
22402252
_ = computerMove.MarkFlagRequired("y")
22412253
computerMove.Flags().StringSlice("hold-key", []string{}, "Modifier keys to hold (repeatable)")
2254+
computerMove.Flags().Bool("smooth", true, "Use human-like Bezier curve path instead of instant teleport")
2255+
computerMove.Flags().Int64("duration-ms", 0, "Target duration in ms for smooth movement (50-5000, 0 for auto)")
22422256

22432257
computerScreenshot := &cobra.Command{Use: "screenshot <id>", Short: "Capture a screenshot (optionally of a region)", Args: cobra.ExactArgs(1), RunE: runBrowsersComputerScreenshot}
22442258
computerScreenshot.Flags().Int64("x", 0, "Top-left X")
@@ -2864,8 +2878,17 @@ func runBrowsersComputerMoveMouse(cmd *cobra.Command, args []string) error {
28642878
x, _ := cmd.Flags().GetInt64("x")
28652879
y, _ := cmd.Flags().GetInt64("y")
28662880
holdKeys, _ := cmd.Flags().GetStringSlice("hold-key")
2881+
in := BrowsersComputerMoveMouseInput{Identifier: args[0], X: x, Y: y, HoldKeys: holdKeys}
2882+
if cmd.Flags().Changed("smooth") {
2883+
v, _ := cmd.Flags().GetBool("smooth")
2884+
in.Smooth = &v
2885+
}
2886+
if cmd.Flags().Changed("duration-ms") {
2887+
v, _ := cmd.Flags().GetInt64("duration-ms")
2888+
in.DurationMs = &v
2889+
}
28672890
b := BrowsersCmd{browsers: &svc, computer: &svc.Computer}
2868-
return b.ComputerMoveMouse(cmd.Context(), BrowsersComputerMoveMouseInput{Identifier: args[0], X: x, Y: y, HoldKeys: holdKeys})
2891+
return b.ComputerMoveMouse(cmd.Context(), in)
28692892
}
28702893

28712894
func runBrowsersComputerScreenshot(cmd *cobra.Command, args []string) error {

cmd/browsers_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,6 +1053,61 @@ func TestBrowsersComputerMoveMouse_PrintsSuccess(t *testing.T) {
10531053
assert.Contains(t, out, "Moved mouse to (5,6)")
10541054
}
10551055

1056+
func TestBrowsersComputerMoveMouse_SmoothFalse(t *testing.T) {
1057+
setupStdoutCapture(t)
1058+
fakeBrowsers := newFakeBrowsersServiceWithSimpleGet()
1059+
var capturedBody kernel.BrowserComputerMoveMouseParams
1060+
fakeComp := &FakeComputerService{
1061+
MoveMouseFunc: func(ctx context.Context, id string, body kernel.BrowserComputerMoveMouseParams, opts ...option.RequestOption) error {
1062+
capturedBody = body
1063+
return nil
1064+
},
1065+
}
1066+
b := BrowsersCmd{browsers: fakeBrowsers, computer: fakeComp}
1067+
smooth := false
1068+
_ = b.ComputerMoveMouse(context.Background(), BrowsersComputerMoveMouseInput{Identifier: "id", X: 100, Y: 200, Smooth: &smooth})
1069+
extras := capturedBody.ExtraFields()
1070+
assert.Contains(t, extras, "smooth")
1071+
assert.Equal(t, false, extras["smooth"])
1072+
}
1073+
1074+
func TestBrowsersComputerMoveMouse_DurationMs(t *testing.T) {
1075+
setupStdoutCapture(t)
1076+
fakeBrowsers := newFakeBrowsersServiceWithSimpleGet()
1077+
var capturedBody kernel.BrowserComputerMoveMouseParams
1078+
fakeComp := &FakeComputerService{
1079+
MoveMouseFunc: func(ctx context.Context, id string, body kernel.BrowserComputerMoveMouseParams, opts ...option.RequestOption) error {
1080+
capturedBody = body
1081+
return nil
1082+
},
1083+
}
1084+
b := BrowsersCmd{browsers: fakeBrowsers, computer: fakeComp}
1085+
smooth := true
1086+
dur := int64(1500)
1087+
_ = b.ComputerMoveMouse(context.Background(), BrowsersComputerMoveMouseInput{Identifier: "id", X: 100, Y: 200, Smooth: &smooth, DurationMs: &dur})
1088+
extras := capturedBody.ExtraFields()
1089+
assert.Contains(t, extras, "smooth")
1090+
assert.Equal(t, true, extras["smooth"])
1091+
assert.Contains(t, extras, "duration_ms")
1092+
assert.Equal(t, int64(1500), extras["duration_ms"])
1093+
}
1094+
1095+
func TestBrowsersComputerMoveMouse_NoSmoothFlag(t *testing.T) {
1096+
setupStdoutCapture(t)
1097+
fakeBrowsers := newFakeBrowsersServiceWithSimpleGet()
1098+
var capturedBody kernel.BrowserComputerMoveMouseParams
1099+
fakeComp := &FakeComputerService{
1100+
MoveMouseFunc: func(ctx context.Context, id string, body kernel.BrowserComputerMoveMouseParams, opts ...option.RequestOption) error {
1101+
capturedBody = body
1102+
return nil
1103+
},
1104+
}
1105+
b := BrowsersCmd{browsers: fakeBrowsers, computer: fakeComp}
1106+
_ = b.ComputerMoveMouse(context.Background(), BrowsersComputerMoveMouseInput{Identifier: "id", X: 100, Y: 200})
1107+
extras := capturedBody.ExtraFields()
1108+
assert.Empty(t, extras)
1109+
}
1110+
10561111
func TestBrowsersComputerScreenshot_SavesFile(t *testing.T) {
10571112
setupStdoutCapture(t)
10581113
dir := t.TempDir()

0 commit comments

Comments
 (0)