Skip to content

Commit b67d5ee

Browse files
authored
Add start URL flags to browser commands (#161)
Summary - update the Go SDK to v0.53.0 for start_url fields - add --start-url to browser create and browser pool create/update - add --clear-start-url for browser pool update - reject --start-url values that are parsed from another flag token - show Start URL in browser and pool outputs - document the new flags <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk CLI enhancement that adds optional flags and output fields; main risk is minor behavior changes in flag validation and pool update semantics. > > **Overview** > Adds `--start-url` support to `kernel browsers create` and `kernel browser-pools create/update`, wiring it through to the SDK request params and displaying the configured/returned Start URL in human-readable outputs. > > Introduces `--clear-start-url` for pool updates (mutually exclusive with `--start-url`) and rejects `--start-url` values that look like another flag token (e.g. `--headless`) to prevent accidental mis-parsing. > > Bumps `kernel-go-sdk` to `v0.53.0`, adds tests covering the new start URL behavior, and updates README flag documentation. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 068a880. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: yummybomb <19238148+yummybomb@users.noreply.github.com>
1 parent 295a8d0 commit b67d5ee

6 files changed

Lines changed: 100 additions & 10 deletions

File tree

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ Commands with JSON output support:
208208
- `-s, --stealth` - Launch browser in stealth mode to avoid detection
209209
- `-H, --headless` - Launch browser without GUI access
210210
- `--kiosk` - Launch browser in kiosk mode
211+
- `--start-url <url>` - Initial page to open on launch
211212
- `--pool-id <id>` - Acquire a browser from the specified pool (mutually exclusive with --pool-name; ignores other session flags)
212213
- `--pool-name <name>` - Acquire a browser from the pool name (mutually exclusive with --pool-id; ignores other session flags)
213214
- `--output json`, `-o json` - Output raw JSON object
@@ -242,12 +243,12 @@ Commands with JSON output support:
242243
- `--fill-rate <n>` - Percentage of the pool to fill per minute
243244
- `--timeout <seconds>` - Idle timeout for browsers acquired from the pool
244245
- `--stealth`, `--headless`, `--kiosk` - Default pool configuration
245-
- `--profile-id`, `--profile-name`, `--save-changes`, `--proxy-id`, `--extension`, `--viewport` - Same semantics as `kernel browsers create`
246+
- `--profile-id`, `--profile-name`, `--save-changes`, `--proxy-id`, `--start-url`, `--extension`, `--viewport` - Same semantics as `kernel browsers create`
246247
- `--output json`, `-o json` - Output raw JSON object
247248
- `kernel browser-pools get <id-or-name>` - Get pool details
248249
- `--output json`, `-o json` - Output raw JSON object
249250
- `kernel browser-pools update <id-or-name>` - Update pool configuration
250-
- Same flags as create plus `--discard-all-idle` to discard all idle browsers in the pool and refill at the specified fill rate
251+
- Same flags as create plus `--clear-start-url` (remove the pool's start URL) and `--discard-all-idle` (discard all idle browsers and refill)
251252
- `--output json`, `-o json` - Output raw JSON object
252253
- `kernel browser-pools delete <id-or-name>` - Delete a pool
253254
- `--force` - Force delete even if browsers are leased

cmd/browser_pools.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ type BrowserPoolsCreateInput struct {
8686
ProfileName string
8787
ProfileSaveChanges BoolFlag
8888
ProxyID string
89+
StartURL string
8990
Extensions []string
9091
Viewport string
9192
Output string
@@ -95,6 +96,9 @@ func (c BrowserPoolsCmd) Create(ctx context.Context, in BrowserPoolsCreateInput)
9596
if in.Output != "" && in.Output != "json" {
9697
return fmt.Errorf("unsupported --output value: use 'json'")
9798
}
99+
if err := validateStartURLFlag(in.StartURL); err != nil {
100+
return err
101+
}
98102

99103
params := kernel.BrowserPoolNewParams{
100104
Size: in.Size,
@@ -131,6 +135,9 @@ func (c BrowserPoolsCmd) Create(ctx context.Context, in BrowserPoolsCreateInput)
131135
if in.ProxyID != "" {
132136
params.ProxyID = kernel.String(in.ProxyID)
133137
}
138+
if in.StartURL != "" {
139+
params.StartURL = kernel.String(in.StartURL)
140+
}
134141

135142
params.Extensions = buildExtensionsParam(in.Extensions)
136143

@@ -196,6 +203,7 @@ func (c BrowserPoolsCmd) Get(ctx context.Context, in BrowserPoolsGetInput) error
196203
{"Kiosk Mode", fmt.Sprintf("%t", cfg.KioskMode)},
197204
{"Profile", formatProfile(cfg.Profile)},
198205
{"Proxy ID", util.OrDash(cfg.ProxyID)},
206+
{"Start URL", util.OrDash(cfg.StartURL)},
199207
{"Extensions", formatExtensions(cfg.Extensions)},
200208
{"Viewport", formatViewport(cfg.Viewport)},
201209
}
@@ -217,6 +225,8 @@ type BrowserPoolsUpdateInput struct {
217225
ProfileName string
218226
ProfileSaveChanges BoolFlag
219227
ProxyID string
228+
StartURL string
229+
ClearStartURL bool
220230
Extensions []string
221231
Viewport string
222232
DiscardAllIdle BoolFlag
@@ -227,6 +237,12 @@ func (c BrowserPoolsCmd) Update(ctx context.Context, in BrowserPoolsUpdateInput)
227237
if in.Output != "" && in.Output != "json" {
228238
return fmt.Errorf("unsupported --output value: use 'json'")
229239
}
240+
if err := validateStartURLFlag(in.StartURL); err != nil {
241+
return err
242+
}
243+
if in.StartURL != "" && in.ClearStartURL {
244+
return fmt.Errorf("cannot specify both --start-url and --clear-start-url")
245+
}
230246

231247
params := kernel.BrowserPoolUpdateParams{}
232248

@@ -267,6 +283,11 @@ func (c BrowserPoolsCmd) Update(ctx context.Context, in BrowserPoolsUpdateInput)
267283
if in.ProxyID != "" {
268284
params.ProxyID = kernel.String(in.ProxyID)
269285
}
286+
if in.ClearStartURL {
287+
params.StartURL = kernel.String("")
288+
} else if in.StartURL != "" {
289+
params.StartURL = kernel.String(in.StartURL)
290+
}
270291

271292
params.Extensions = buildExtensionsParam(in.Extensions)
272293

@@ -352,6 +373,9 @@ func (c BrowserPoolsCmd) Acquire(ctx context.Context, in BrowserPoolsAcquireInpu
352373
{"CDP WebSocket URL", resp.CdpWsURL},
353374
{"Live View URL", resp.BrowserLiveViewURL},
354375
}
376+
if resp.StartURL != "" {
377+
tableData = append(tableData, []string{"Start URL", resp.StartURL})
378+
}
355379
PrintTableNoPad(tableData, true)
356380
return nil
357381
}
@@ -472,6 +496,7 @@ func init() {
472496
browserPoolsCreateCmd.Flags().String("profile-name", "", "Profile name")
473497
browserPoolsCreateCmd.Flags().Bool("save-changes", false, "Save changes to profile")
474498
browserPoolsCreateCmd.Flags().String("proxy-id", "", "Proxy ID")
499+
browserPoolsCreateCmd.Flags().String("start-url", "", "Initial page to open for new browsers")
475500
browserPoolsCreateCmd.Flags().StringSlice("extension", []string{}, "Extension IDs or names")
476501
browserPoolsCreateCmd.Flags().String("viewport", "", "Viewport size (e.g. 1280x800)")
477502

@@ -488,6 +513,8 @@ func init() {
488513
browserPoolsUpdateCmd.Flags().String("profile-name", "", "Profile name")
489514
browserPoolsUpdateCmd.Flags().Bool("save-changes", false, "Save changes to profile")
490515
browserPoolsUpdateCmd.Flags().String("proxy-id", "", "Proxy ID")
516+
browserPoolsUpdateCmd.Flags().String("start-url", "", "Initial page to open for new browsers")
517+
browserPoolsUpdateCmd.Flags().Bool("clear-start-url", false, "Clear the pool start URL")
491518
browserPoolsUpdateCmd.Flags().StringSlice("extension", []string{}, "Extension IDs or names")
492519
browserPoolsUpdateCmd.Flags().String("viewport", "", "Viewport size (e.g. 1280x800)")
493520
browserPoolsUpdateCmd.Flags().Bool("discard-all-idle", false, "Discard all idle browsers")
@@ -539,6 +566,7 @@ func runBrowserPoolsCreate(cmd *cobra.Command, args []string) error {
539566
profileName, _ := cmd.Flags().GetString("profile-name")
540567
saveChanges, _ := cmd.Flags().GetBool("save-changes")
541568
proxyID, _ := cmd.Flags().GetString("proxy-id")
569+
startURL, _ := cmd.Flags().GetString("start-url")
542570
extensions, _ := cmd.Flags().GetStringSlice("extension")
543571
viewport, _ := cmd.Flags().GetString("viewport")
544572
output, _ := cmd.Flags().GetString("output")
@@ -555,6 +583,7 @@ func runBrowserPoolsCreate(cmd *cobra.Command, args []string) error {
555583
ProfileName: profileName,
556584
ProfileSaveChanges: BoolFlag{Set: cmd.Flags().Changed("save-changes"), Value: saveChanges},
557585
ProxyID: proxyID,
586+
StartURL: startURL,
558587
Extensions: extensions,
559588
Viewport: viewport,
560589
Output: output,
@@ -585,6 +614,8 @@ func runBrowserPoolsUpdate(cmd *cobra.Command, args []string) error {
585614
profileName, _ := cmd.Flags().GetString("profile-name")
586615
saveChanges, _ := cmd.Flags().GetBool("save-changes")
587616
proxyID, _ := cmd.Flags().GetString("proxy-id")
617+
startURL, _ := cmd.Flags().GetString("start-url")
618+
clearStartURL, _ := cmd.Flags().GetBool("clear-start-url")
588619
extensions, _ := cmd.Flags().GetStringSlice("extension")
589620
viewport, _ := cmd.Flags().GetString("viewport")
590621
discardIdle, _ := cmd.Flags().GetBool("discard-all-idle")
@@ -603,6 +634,8 @@ func runBrowserPoolsUpdate(cmd *cobra.Command, args []string) error {
603634
ProfileName: profileName,
604635
ProfileSaveChanges: BoolFlag{Set: cmd.Flags().Changed("save-changes"), Value: saveChanges},
605636
ProxyID: proxyID,
637+
StartURL: startURL,
638+
ClearStartURL: clearStartURL,
606639
Extensions: extensions,
607640
Viewport: viewport,
608641
DiscardAllIdle: BoolFlag{Set: cmd.Flags().Changed("discard-all-idle"), Value: discardIdle},
@@ -665,6 +698,13 @@ func buildProfileParam(profileID, profileName string, saveChanges BoolFlag) (*ke
665698
return &profile, nil
666699
}
667700

701+
func validateStartURLFlag(startURL string) error {
702+
if strings.HasPrefix(startURL, "-") {
703+
return fmt.Errorf("--start-url requires a URL value")
704+
}
705+
return nil
706+
}
707+
668708
func buildExtensionsParam(extensions []string) []kernel.BrowserExtensionParam {
669709
if len(extensions) == 0 {
670710
return nil

cmd/browsers.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ type BrowsersCreateInput struct {
186186
ProfileName string
187187
ProfileSaveChanges BoolFlag
188188
ProxyID string
189+
StartURL string
189190
Extensions []string
190191
Viewport string
191192
Output string
@@ -346,6 +347,9 @@ func (b BrowsersCmd) Create(ctx context.Context, in BrowsersCreateInput) error {
346347
if in.Output != "" && in.Output != "json" {
347348
return fmt.Errorf("unsupported --output value: use 'json'")
348349
}
350+
if err := validateStartURLFlag(in.StartURL); err != nil {
351+
return err
352+
}
349353

350354
if in.Output != "json" {
351355
pterm.Info.Println("Creating browser session...")
@@ -392,6 +396,9 @@ func (b BrowsersCmd) Create(ctx context.Context, in BrowsersCreateInput) error {
392396
if in.ProxyID != "" {
393397
params.ProxyID = kernel.Opt(in.ProxyID)
394398
}
399+
if in.StartURL != "" {
400+
params.StartURL = kernel.Opt(in.StartURL)
401+
}
395402

396403
// Map extensions (IDs or names) into params.Extensions
397404
if len(in.Extensions) > 0 {
@@ -435,17 +442,17 @@ func (b BrowsersCmd) Create(ctx context.Context, in BrowsersCreateInput) error {
435442
return util.PrintPrettyJSON(browser)
436443
}
437444

438-
printBrowserSessionResult(browser.SessionID, browser.CdpWsURL, browser.BrowserLiveViewURL, browser.Persistence, browser.Profile)
445+
printBrowserSessionResult(browser.SessionID, browser.CdpWsURL, browser.BrowserLiveViewURL, browser.Persistence, browser.Profile, browser.StartURL)
439446
return nil
440447
}
441448

442-
func printBrowserSessionResult(sessionID, cdpURL, liveViewURL string, persistence kernel.BrowserPersistence, profile kernel.Profile) {
443-
tableData := buildBrowserTableData(sessionID, cdpURL, liveViewURL, persistence, profile)
449+
func printBrowserSessionResult(sessionID, cdpURL, liveViewURL string, persistence kernel.BrowserPersistence, profile kernel.Profile, startURL string) {
450+
tableData := buildBrowserTableData(sessionID, cdpURL, liveViewURL, persistence, profile, startURL)
444451
PrintTableNoPad(tableData, true)
445452
}
446453

447454
// buildBrowserTableData creates a base table with common browser session fields.
448-
func buildBrowserTableData(sessionID, cdpURL, liveViewURL string, persistence kernel.BrowserPersistence, profile kernel.Profile) pterm.TableData {
455+
func buildBrowserTableData(sessionID, cdpURL, liveViewURL string, persistence kernel.BrowserPersistence, profile kernel.Profile, startURL string) pterm.TableData {
449456
tableData := pterm.TableData{
450457
{"Property", "Value"},
451458
{"Session ID", sessionID},
@@ -464,6 +471,9 @@ func buildBrowserTableData(sessionID, cdpURL, liveViewURL string, persistence ke
464471
}
465472
tableData = append(tableData, []string{"Profile", profVal})
466473
}
474+
if startURL != "" {
475+
tableData = append(tableData, []string{"Start URL", startURL})
476+
}
467477
return tableData
468478
}
469479

@@ -554,6 +564,7 @@ func (b BrowsersCmd) Get(ctx context.Context, in BrowsersGetInput) error {
554564
browser.BrowserLiveViewURL,
555565
browser.Persistence,
556566
browser.Profile,
567+
browser.StartURL,
557568
)
558569

559570
// Append additional detailed fields
@@ -2525,6 +2536,7 @@ func init() {
25252536
browsersCreateCmd.Flags().String("profile-name", "", "Profile name to load into the browser session (mutually exclusive with --profile-id)")
25262537
browsersCreateCmd.Flags().Bool("save-changes", false, "If set, save changes back to the profile when the session ends")
25272538
browsersCreateCmd.Flags().String("proxy-id", "", "Proxy ID to use for the browser session")
2539+
browsersCreateCmd.Flags().String("start-url", "", "Initial page to open on launch")
25282540
browsersCreateCmd.Flags().StringSlice("extension", []string{}, "Extension IDs or names to load (repeatable; may be passed multiple times or comma-separated)")
25292541
browsersCreateCmd.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")
25302542
browsersCreateCmd.Flags().Bool("viewport-interactive", false, "Interactively select viewport size from list")
@@ -2597,6 +2609,7 @@ func runBrowsersCreate(cmd *cobra.Command, args []string) error {
25972609
profileName, _ := cmd.Flags().GetString("profile-name")
25982610
saveChanges, _ := cmd.Flags().GetBool("save-changes")
25992611
proxyID, _ := cmd.Flags().GetString("proxy-id")
2612+
startURL, _ := cmd.Flags().GetString("start-url")
26002613
extensions, _ := cmd.Flags().GetStringSlice("extension")
26012614
viewport, _ := cmd.Flags().GetString("viewport")
26022615
viewportInteractive, _ := cmd.Flags().GetBool("viewport-interactive")
@@ -2676,7 +2689,7 @@ func runBrowsersCreate(cmd *cobra.Command, args []string) error {
26762689
if output == "json" {
26772690
return util.PrintPrettyJSON(resp)
26782691
}
2679-
printBrowserSessionResult(resp.SessionID, resp.CdpWsURL, resp.BrowserLiveViewURL, resp.Persistence, resp.Profile)
2692+
printBrowserSessionResult(resp.SessionID, resp.CdpWsURL, resp.BrowserLiveViewURL, resp.Persistence, resp.Profile, resp.StartURL)
26802693
return nil
26812694
}
26822695

@@ -2709,6 +2722,7 @@ func runBrowsersCreate(cmd *cobra.Command, args []string) error {
27092722
ProfileName: profileName,
27102723
ProfileSaveChanges: BoolFlag{Set: cmd.Flags().Changed("save-changes"), Value: saveChanges},
27112724
ProxyID: proxyID,
2725+
StartURL: startURL,
27122726
Extensions: extensions,
27132727
Viewport: viewport,
27142728
Output: output,

cmd/browsers_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1632,6 +1632,41 @@ func TestBrowsersCreate_WithViewportNoRefreshRate(t *testing.T) {
16321632
assert.False(t, captured.Viewport.RefreshRate.Valid())
16331633
}
16341634

1635+
func TestBrowsersCreate_WithStartURL(t *testing.T) {
1636+
setupStdoutCapture(t)
1637+
var captured kernel.BrowserNewParams
1638+
fake := &FakeBrowsersService{NewFunc: func(ctx context.Context, body kernel.BrowserNewParams, opts ...option.RequestOption) (*kernel.BrowserNewResponse, error) {
1639+
captured = body
1640+
return &kernel.BrowserNewResponse{SessionID: "session123", CdpWsURL: "ws://example"}, nil
1641+
}}
1642+
b := BrowsersCmd{browsers: fake}
1643+
1644+
err := b.Create(context.Background(), BrowsersCreateInput{
1645+
StartURL: "https://example.com",
1646+
})
1647+
1648+
assert.NoError(t, err)
1649+
assert.True(t, captured.StartURL.Valid())
1650+
assert.Equal(t, "https://example.com", captured.StartURL.Value)
1651+
}
1652+
1653+
func TestBrowsersCreate_RejectsStartURLFlagToken(t *testing.T) {
1654+
called := false
1655+
fake := &FakeBrowsersService{NewFunc: func(ctx context.Context, body kernel.BrowserNewParams, opts ...option.RequestOption) (*kernel.BrowserNewResponse, error) {
1656+
called = true
1657+
return &kernel.BrowserNewResponse{}, nil
1658+
}}
1659+
b := BrowsersCmd{browsers: fake}
1660+
1661+
err := b.Create(context.Background(), BrowsersCreateInput{
1662+
StartURL: "--headless",
1663+
})
1664+
1665+
require.Error(t, err)
1666+
assert.Contains(t, err.Error(), "--start-url requires a URL value")
1667+
assert.False(t, called)
1668+
}
1669+
16351670
func TestBrowsersCreate_WithInvalidViewport(t *testing.T) {
16361671
setupStdoutCapture(t)
16371672
fake := &FakeBrowsersService{}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.1
1010
github.com/golang-jwt/jwt/v5 v5.2.2
1111
github.com/joho/godotenv v1.5.1
12-
github.com/kernel/kernel-go-sdk v0.52.0
12+
github.com/kernel/kernel-go-sdk v0.53.0
1313
github.com/klauspost/compress v1.18.5
1414
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
1515
github.com/pterm/pterm v0.12.80

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
6464
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
6565
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
6666
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
67-
github.com/kernel/kernel-go-sdk v0.52.0 h1:ChRAMo6oMAEmazC610FtcqKFO/cqHzU9v1ECF0MiR8E=
68-
github.com/kernel/kernel-go-sdk v0.52.0/go.mod h1:EeZzSuHZVeHKxKCPUzxou2bovNGhXaz0RXrSqKNf1AQ=
67+
github.com/kernel/kernel-go-sdk v0.53.0 h1:XgcuJv3G4a6nr9LYBZ21gLUWvsIDLSG4YhZAngNrqE0=
68+
github.com/kernel/kernel-go-sdk v0.53.0/go.mod h1:EeZzSuHZVeHKxKCPUzxou2bovNGhXaz0RXrSqKNf1AQ=
6969
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
7070
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
7171
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=

0 commit comments

Comments
 (0)