From f87e8931edd81ac0d845a859a24d565e2f1ba10b Mon Sep 17 00:00:00 2001 From: Hiro Tamada Date: Thu, 5 Mar 2026 11:12:18 -0500 Subject: [PATCH 1/2] feat: add --force flag to browsers update viewport resize MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump go-sdk to v0.42.1 and expose the new `force` parameter on `PATCH /browsers/{id}` viewport updates. When `--force` is passed alongside `--viewport`, the resize proceeds even when a live view or recording/replay is active — active recordings are gracefully stopped and restarted as separate segments at the new resolution. Made-with: Cursor --- cmd/browsers.go | 16 +++++++++--- cmd/browsers_test.go | 59 ++++++++++++++++++++++++++++++++++++++++++++ go.mod | 2 +- go.sum | 4 +-- 4 files changed, 75 insertions(+), 6 deletions(-) diff --git a/cmd/browsers.go b/cmd/browsers.go index 6cc7b8d..b2c3ff1 100644 --- a/cmd/browsers.go +++ b/cmd/browsers.go @@ -207,6 +207,7 @@ type BrowsersUpdateInput struct { ProfileName string ProfileSaveChanges BoolFlag Viewport string + Force bool Output string } @@ -622,13 +623,18 @@ func (b BrowsersCmd) Update(ctx context.Context, in BrowsersUpdateInput) error { if err != nil { return fmt.Errorf("invalid viewport format: %v", err) } - params.Viewport = shared.BrowserViewportParam{ - Width: width, - Height: height, + params.Viewport = kernel.BrowserUpdateParamsViewport{ + BrowserViewportParam: shared.BrowserViewportParam{ + Width: width, + Height: height, + }, } if refreshRate > 0 { params.Viewport.RefreshRate = kernel.Opt(refreshRate) } + if in.Force { + params.Viewport.Force = kernel.Opt(true) + } } if in.Output != "json" { @@ -2122,6 +2128,7 @@ Supported operations: - Change or remove proxy (--proxy-id or --clear-proxy) - Load a profile into a session that doesn't have one (--profile-id or --profile-name) - Change viewport dimensions (--viewport) + - Force viewport resize during active live view or recording (--force with --viewport) Note: Profiles can only be loaded into sessions that don't already have a profile.`, Args: func(cmd *cobra.Command, args []string) error { @@ -2159,6 +2166,7 @@ func init() { browsersUpdateCmd.Flags().String("profile-name", "", "Profile name to load into the browser session (mutually exclusive with --profile-id)") browsersUpdateCmd.Flags().Bool("save-changes", false, "If set, save changes back to the profile when the session ends") browsersUpdateCmd.Flags().String("viewport", "", "Browser viewport size (e.g., 1920x1080@25). Supported: 2560x1440@10, 1920x1080@25, 1920x1200@25, 1440x900@25, 1024x768@60, 1200x800@60, 1280x800@60") + browsersUpdateCmd.Flags().Bool("force", false, "Force viewport resize even when a live view or recording/replay is active") browsersCmd.AddCommand(browsersListCmd) browsersCmd.AddCommand(browsersCreateCmd) @@ -2619,6 +2627,7 @@ func runBrowsersUpdate(cmd *cobra.Command, args []string) error { profileName, _ := cmd.Flags().GetString("profile-name") saveChanges, _ := cmd.Flags().GetBool("save-changes") viewport, _ := cmd.Flags().GetString("viewport") + force, _ := cmd.Flags().GetBool("force") svc := client.Browsers b := BrowsersCmd{browsers: &svc} @@ -2630,6 +2639,7 @@ func runBrowsersUpdate(cmd *cobra.Command, args []string) error { ProfileName: profileName, ProfileSaveChanges: BoolFlag{Set: cmd.Flags().Changed("save-changes"), Value: saveChanges}, Viewport: viewport, + Force: force, Output: out, }) } diff --git a/cmd/browsers_test.go b/cmd/browsers_test.go index 1099caf..357eee3 100644 --- a/cmd/browsers_test.go +++ b/cmd/browsers_test.go @@ -1242,3 +1242,62 @@ func TestBrowsersCreate_WithInvalidViewport(t *testing.T) { out := outBuf.String() assert.Contains(t, out, "Invalid viewport format") } + +func TestBrowsersUpdate_WithViewportAndForce(t *testing.T) { + setupStdoutCapture(t) + var captured kernel.BrowserUpdateParams + fake := &FakeBrowsersService{UpdateFunc: func(ctx context.Context, id string, body kernel.BrowserUpdateParams, opts ...option.RequestOption) (*kernel.BrowserUpdateResponse, error) { + captured = body + return &kernel.BrowserUpdateResponse{SessionID: "session123"}, nil + }} + b := BrowsersCmd{browsers: fake} + + err := b.Update(context.Background(), BrowsersUpdateInput{ + Identifier: "session123", + Viewport: "1920x1080@25", + Force: true, + }) + + assert.NoError(t, err) + assert.Equal(t, int64(1920), captured.Viewport.Width) + assert.Equal(t, int64(1080), captured.Viewport.Height) + assert.True(t, captured.Viewport.RefreshRate.Valid()) + assert.Equal(t, int64(25), captured.Viewport.RefreshRate.Value) + assert.True(t, captured.Viewport.Force.Valid()) + assert.True(t, captured.Viewport.Force.Value) +} + +func TestBrowsersUpdate_WithViewportNoForce(t *testing.T) { + setupStdoutCapture(t) + var captured kernel.BrowserUpdateParams + fake := &FakeBrowsersService{UpdateFunc: func(ctx context.Context, id string, body kernel.BrowserUpdateParams, opts ...option.RequestOption) (*kernel.BrowserUpdateResponse, error) { + captured = body + return &kernel.BrowserUpdateResponse{SessionID: "session123"}, nil + }} + b := BrowsersCmd{browsers: fake} + + err := b.Update(context.Background(), BrowsersUpdateInput{ + Identifier: "session123", + Viewport: "1920x1080@25", + Force: false, + }) + + assert.NoError(t, err) + assert.Equal(t, int64(1920), captured.Viewport.Width) + assert.Equal(t, int64(1080), captured.Viewport.Height) + assert.False(t, captured.Viewport.Force.Valid()) +} + +func TestBrowsersUpdate_ForceWithoutViewport_Errors(t *testing.T) { + setupStdoutCapture(t) + fake := &FakeBrowsersService{} + b := BrowsersCmd{browsers: fake} + + err := b.Update(context.Background(), BrowsersUpdateInput{ + Identifier: "session123", + Force: true, + }) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "must specify at least one of") +} diff --git a/go.mod b/go.mod index 241a31c..b21379c 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.1 github.com/golang-jwt/jwt/v5 v5.2.2 github.com/joho/godotenv v1.5.1 - github.com/kernel/kernel-go-sdk v0.40.0 + github.com/kernel/kernel-go-sdk v0.42.1 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/pterm/pterm v0.12.80 github.com/samber/lo v1.51.0 diff --git a/go.sum b/go.sum index e2304ce..a8af996 100644 --- a/go.sum +++ b/go.sum @@ -64,8 +64,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/kernel/kernel-go-sdk v0.40.0 h1:RQON4dE9HwvEcF5wM3WVKs/Om0PCH0eTDEB3iwjOvy4= -github.com/kernel/kernel-go-sdk v0.40.0/go.mod h1:EeZzSuHZVeHKxKCPUzxou2bovNGhXaz0RXrSqKNf1AQ= +github.com/kernel/kernel-go-sdk v0.42.1 h1:uDoMNXyfS59fkZ9laJO/qKFAysRrqdg06zNB3uLEdEk= +github.com/kernel/kernel-go-sdk v0.42.1/go.mod h1:EeZzSuHZVeHKxKCPUzxou2bovNGhXaz0RXrSqKNf1AQ= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= From b77a4b1fbd7a47050896384623c21c848fa8b373 Mon Sep 17 00:00:00 2001 From: Hiro Tamada Date: Thu, 5 Mar 2026 11:22:08 -0500 Subject: [PATCH 2/2] fix: validate --force requires --viewport on browsers update Adds explicit validation so --force is rejected with a clear error when used without --viewport, instead of being silently ignored. Made-with: Cursor --- cmd/browsers.go | 5 +++++ cmd/browsers_test.go | 17 ++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/cmd/browsers.go b/cmd/browsers.go index b2c3ff1..badd879 100644 --- a/cmd/browsers.go +++ b/cmd/browsers.go @@ -590,6 +590,11 @@ func (b BrowsersCmd) Update(ctx context.Context, in BrowsersUpdateInput) error { return fmt.Errorf("--save-changes requires --profile-id or --profile-name") } + // Validate --force is only used with a viewport change + if in.Force && !hasViewportChange { + return fmt.Errorf("--force requires --viewport") + } + // Validate that at least one update option is provided if !hasProxyChange && !hasProfileChange && !hasViewportChange { return fmt.Errorf("must specify at least one of: --proxy-id, --clear-proxy, --profile-id, --profile-name, or --viewport") diff --git a/cmd/browsers_test.go b/cmd/browsers_test.go index 357eee3..0f88054 100644 --- a/cmd/browsers_test.go +++ b/cmd/browsers_test.go @@ -1299,5 +1299,20 @@ func TestBrowsersUpdate_ForceWithoutViewport_Errors(t *testing.T) { }) assert.Error(t, err) - assert.Contains(t, err.Error(), "must specify at least one of") + assert.Contains(t, err.Error(), "--force requires --viewport") +} + +func TestBrowsersUpdate_ForceWithProxyButNoViewport_Errors(t *testing.T) { + setupStdoutCapture(t) + fake := &FakeBrowsersService{} + b := BrowsersCmd{browsers: fake} + + err := b.Update(context.Background(), BrowsersUpdateInput{ + Identifier: "session123", + ProxyID: "proxy-123", + Force: true, + }) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "--force requires --viewport") }