@@ -12,6 +12,7 @@ import (
1212 "os"
1313 "path/filepath"
1414 "regexp"
15+ "strconv"
1516 "strings"
1617
1718 "github.com/onkernel/cli/pkg/util"
@@ -30,7 +31,7 @@ type BrowsersService interface {
3031 New (ctx context.Context , body kernel.BrowserNewParams , opts ... option.RequestOption ) (res * kernel.BrowserNewResponse , err error )
3132 Delete (ctx context.Context , body kernel.BrowserDeleteParams , opts ... option.RequestOption ) (err error )
3233 DeleteByID (ctx context.Context , id string , opts ... option.RequestOption ) (err error )
33- UploadExtensions (ctx context.Context , id string , body kernel.BrowserUploadExtensionsParams , opts ... option.RequestOption ) (err error )
34+ LoadExtensions (ctx context.Context , id string , body kernel.BrowserLoadExtensionsParams , opts ... option.RequestOption ) (err error )
3435}
3536
3637// BrowserReplaysService defines the subset we use for browser replays.
@@ -81,6 +82,53 @@ type BoolFlag struct {
8182// Regular expression to validate CUID2 identifiers (24 lowercase alphanumeric characters).
8283var cuidRegex = regexp .MustCompile (`^[a-z0-9]{24}$` )
8384
85+ // getAvailableViewports returns the list of supported viewport configurations.
86+ func getAvailableViewports () []string {
87+ return []string {
88+ "2560x1440@10" ,
89+ "1920x1080@25" ,
90+ "1920x1200@25" ,
91+ "1440x900@25" ,
92+ "1024x768@60" ,
93+ }
94+ }
95+
96+ // parseViewport parses a viewport string (e.g., "1920x1080@25") and returns width, height, and refresh rate.
97+ // Returns error if the format is invalid.
98+ func parseViewport (viewport string ) (width , height , refreshRate int64 , err error ) {
99+ parts := strings .Split (viewport , "@" )
100+ var dimStr string
101+ if len (parts ) == 1 {
102+ dimStr = parts [0 ]
103+ refreshRate = 0
104+ } else if len (parts ) == 2 {
105+ dimStr = parts [0 ]
106+ rr , parseErr := strconv .ParseInt (parts [1 ], 10 , 64 )
107+ if parseErr != nil {
108+ return 0 , 0 , 0 , fmt .Errorf ("invalid refresh rate: %v" , parseErr )
109+ }
110+ refreshRate = rr
111+ } else {
112+ return 0 , 0 , 0 , fmt .Errorf ("invalid viewport format" )
113+ }
114+
115+ dims := strings .Split (dimStr , "x" )
116+ if len (dims ) != 2 {
117+ return 0 , 0 , 0 , fmt .Errorf ("invalid viewport format, expected WIDTHxHEIGHT[@RATE]" )
118+ }
119+
120+ w , err := strconv .ParseInt (dims [0 ], 10 , 64 )
121+ if err != nil {
122+ return 0 , 0 , 0 , fmt .Errorf ("invalid width: %v" , err )
123+ }
124+ h , err := strconv .ParseInt (dims [1 ], 10 , 64 )
125+ if err != nil {
126+ return 0 , 0 , 0 , fmt .Errorf ("invalid height: %v" , err )
127+ }
128+
129+ return w , h , refreshRate , nil
130+ }
131+
84132// Inputs for each command
85133type BrowsersCreateInput struct {
86134 PersistenceID string
@@ -92,6 +140,7 @@ type BrowsersCreateInput struct {
92140 ProfileSaveChanges BoolFlag
93141 ProxyID string
94142 Extensions []string
143+ Viewport string
95144}
96145
97146type BrowsersDeleteInput struct {
@@ -230,6 +279,22 @@ func (b BrowsersCmd) Create(ctx context.Context, in BrowsersCreateInput) error {
230279 }
231280 }
232281
282+ // Add viewport if specified
283+ if in .Viewport != "" {
284+ width , height , refreshRate , err := parseViewport (in .Viewport )
285+ if err != nil {
286+ pterm .Error .Printf ("Invalid viewport format: %v\n " , err )
287+ return nil
288+ }
289+ params .Viewport = kernel.BrowserNewParamsViewport {
290+ Width : width ,
291+ Height : height ,
292+ }
293+ if refreshRate > 0 {
294+ params .Viewport .RefreshRate = kernel .Opt (refreshRate )
295+ }
296+ }
297+
233298 browser , err := b .browsers .New (ctx , params )
234299 if err != nil {
235300 return util.CleanedUpSdkError {Err : err }
@@ -1239,7 +1304,7 @@ func (b BrowsersCmd) ExtensionsUpload(ctx context.Context, in BrowsersExtensions
12391304 return nil
12401305 }
12411306
1242- var extensions []kernel.BrowserUploadExtensionsParamsExtension
1307+ var extensions []kernel.BrowserLoadExtensionsParamsExtension
12431308 var tempZipFiles []string
12441309 var openFiles []* os.File
12451310
@@ -1280,14 +1345,14 @@ func (b BrowsersCmd) ExtensionsUpload(ctx context.Context, in BrowsersExtensions
12801345 }
12811346 openFiles = append (openFiles , zipFile )
12821347
1283- extensions = append (extensions , kernel.BrowserUploadExtensionsParamsExtension {
1348+ extensions = append (extensions , kernel.BrowserLoadExtensionsParamsExtension {
12841349 Name : extName ,
12851350 ZipFile : zipFile ,
12861351 })
12871352 }
12881353
12891354 pterm .Info .Printf ("Uploading %d extension(s) to browser %s...\n " , len (extensions ), br .SessionID )
1290- if err := b .browsers .UploadExtensions (ctx , br .SessionID , kernel.BrowserUploadExtensionsParams {
1355+ if err := b .browsers .LoadExtensions (ctx , br .SessionID , kernel.BrowserLoadExtensionsParams {
12911356 Extensions : extensions ,
12921357 }); err != nil {
12931358 return util.CleanedUpSdkError {Err : err }
@@ -1482,6 +1547,8 @@ func init() {
14821547 browsersCreateCmd .Flags ().Bool ("save-changes" , false , "If set, save changes back to the profile when the session ends" )
14831548 browsersCreateCmd .Flags ().String ("proxy-id" , "" , "Proxy ID to use for the browser session" )
14841549 browsersCreateCmd .Flags ().StringSlice ("extension" , []string {}, "Extension IDs or names to load (repeatable; may be passed multiple times or comma-separated)" )
1550+ browsersCreateCmd .Flags ().String ("viewport" , "" , "Browser viewport size (e.g., 1920x1080@25). Supported: 2560x1440@10, 1920x1080@25, 1920x1200@25, 1440x900@25, 1024x768@60" )
1551+ browsersCreateCmd .Flags ().Bool ("viewport-interactive" , false , "Interactively select viewport size from list" )
14851552
14861553 // Add flags for delete command
14871554 browsersDeleteCmd .Flags ().BoolP ("yes" , "y" , false , "Skip confirmation prompt" )
@@ -1510,6 +1577,25 @@ func runBrowsersCreate(cmd *cobra.Command, args []string) error {
15101577 saveChanges , _ := cmd .Flags ().GetBool ("save-changes" )
15111578 proxyID , _ := cmd .Flags ().GetString ("proxy-id" )
15121579 extensions , _ := cmd .Flags ().GetStringSlice ("extension" )
1580+ viewport , _ := cmd .Flags ().GetString ("viewport" )
1581+ viewportInteractive , _ := cmd .Flags ().GetBool ("viewport-interactive" )
1582+
1583+ // Handle interactive viewport selection
1584+ if viewportInteractive {
1585+ if viewport != "" {
1586+ pterm .Warning .Println ("Both --viewport and --viewport-interactive specified; using interactive mode" )
1587+ }
1588+ options := getAvailableViewports ()
1589+ selectedViewport , err := pterm .DefaultInteractiveSelect .
1590+ WithOptions (options ).
1591+ WithDefaultText ("Select a viewport size:" ).
1592+ Show ()
1593+ if err != nil {
1594+ pterm .Error .Printf ("Failed to select viewport: %v\n " , err )
1595+ return nil
1596+ }
1597+ viewport = selectedViewport
1598+ }
15131599
15141600 in := BrowsersCreateInput {
15151601 PersistenceID : persistenceID ,
@@ -1521,6 +1607,7 @@ func runBrowsersCreate(cmd *cobra.Command, args []string) error {
15211607 ProfileSaveChanges : BoolFlag {Set : cmd .Flags ().Changed ("save-changes" ), Value : saveChanges },
15221608 ProxyID : proxyID ,
15231609 Extensions : extensions ,
1610+ Viewport : viewport ,
15241611 }
15251612
15261613 svc := client .Browsers
0 commit comments