From ed7a71f15403e47326b1fb88a816bb1476fd4af8 Mon Sep 17 00:00:00 2001 From: yummybomb <19238148+yummybomb@users.noreply.github.com> Date: Wed, 13 May 2026 17:18:03 +0000 Subject: [PATCH 1/2] Add start URL browser flags --- README.md | 5 +++-- cmd/browser_pools.go | 27 +++++++++++++++++++++++++++ cmd/browsers.go | 21 ++++++++++++++++----- cmd/browsers_test.go | 18 ++++++++++++++++++ go.mod | 2 +- go.sum | 4 ++-- 6 files changed, 67 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 50d9080..2da35f3 100644 --- a/README.md +++ b/README.md @@ -208,6 +208,7 @@ Commands with JSON output support: - `-s, --stealth` - Launch browser in stealth mode to avoid detection - `-H, --headless` - Launch browser without GUI access - `--kiosk` - Launch browser in kiosk mode + - `--start-url ` - Initial page to open on launch - `--pool-id ` - Acquire a browser from the specified pool (mutually exclusive with --pool-name; ignores other session flags) - `--pool-name ` - Acquire a browser from the pool name (mutually exclusive with --pool-id; ignores other session flags) - `--output json`, `-o json` - Output raw JSON object @@ -242,12 +243,12 @@ Commands with JSON output support: - `--fill-rate ` - Percentage of the pool to fill per minute - `--timeout ` - Idle timeout for browsers acquired from the pool - `--stealth`, `--headless`, `--kiosk` - Default pool configuration - - `--profile-id`, `--profile-name`, `--save-changes`, `--proxy-id`, `--extension`, `--viewport` - Same semantics as `kernel browsers create` + - `--profile-id`, `--profile-name`, `--save-changes`, `--proxy-id`, `--start-url`, `--extension`, `--viewport` - Same semantics as `kernel browsers create` - `--output json`, `-o json` - Output raw JSON object - `kernel browser-pools get ` - Get pool details - `--output json`, `-o json` - Output raw JSON object - `kernel browser-pools update ` - Update pool configuration - - Same flags as create plus `--discard-all-idle` to discard all idle browsers in the pool and refill at the specified fill rate + - Same flags as create plus `--clear-start-url` (remove the pool's start URL) and `--discard-all-idle` (discard all idle browsers and refill) - `--output json`, `-o json` - Output raw JSON object - `kernel browser-pools delete ` - Delete a pool - `--force` - Force delete even if browsers are leased diff --git a/cmd/browser_pools.go b/cmd/browser_pools.go index fbb63f8..80272a0 100644 --- a/cmd/browser_pools.go +++ b/cmd/browser_pools.go @@ -86,6 +86,7 @@ type BrowserPoolsCreateInput struct { ProfileName string ProfileSaveChanges BoolFlag ProxyID string + StartURL string Extensions []string Viewport string Output string @@ -131,6 +132,9 @@ func (c BrowserPoolsCmd) Create(ctx context.Context, in BrowserPoolsCreateInput) if in.ProxyID != "" { params.ProxyID = kernel.String(in.ProxyID) } + if in.StartURL != "" { + params.StartURL = kernel.String(in.StartURL) + } params.Extensions = buildExtensionsParam(in.Extensions) @@ -196,6 +200,7 @@ func (c BrowserPoolsCmd) Get(ctx context.Context, in BrowserPoolsGetInput) error {"Kiosk Mode", fmt.Sprintf("%t", cfg.KioskMode)}, {"Profile", formatProfile(cfg.Profile)}, {"Proxy ID", util.OrDash(cfg.ProxyID)}, + {"Start URL", util.OrDash(cfg.StartURL)}, {"Extensions", formatExtensions(cfg.Extensions)}, {"Viewport", formatViewport(cfg.Viewport)}, } @@ -217,6 +222,8 @@ type BrowserPoolsUpdateInput struct { ProfileName string ProfileSaveChanges BoolFlag ProxyID string + StartURL string + ClearStartURL bool Extensions []string Viewport string DiscardAllIdle BoolFlag @@ -227,6 +234,9 @@ func (c BrowserPoolsCmd) Update(ctx context.Context, in BrowserPoolsUpdateInput) if in.Output != "" && in.Output != "json" { return fmt.Errorf("unsupported --output value: use 'json'") } + if in.StartURL != "" && in.ClearStartURL { + return fmt.Errorf("cannot specify both --start-url and --clear-start-url") + } params := kernel.BrowserPoolUpdateParams{} @@ -267,6 +277,11 @@ func (c BrowserPoolsCmd) Update(ctx context.Context, in BrowserPoolsUpdateInput) if in.ProxyID != "" { params.ProxyID = kernel.String(in.ProxyID) } + if in.ClearStartURL { + params.StartURL = kernel.String("") + } else if in.StartURL != "" { + params.StartURL = kernel.String(in.StartURL) + } params.Extensions = buildExtensionsParam(in.Extensions) @@ -352,6 +367,9 @@ func (c BrowserPoolsCmd) Acquire(ctx context.Context, in BrowserPoolsAcquireInpu {"CDP WebSocket URL", resp.CdpWsURL}, {"Live View URL", resp.BrowserLiveViewURL}, } + if resp.StartURL != "" { + tableData = append(tableData, []string{"Start URL", resp.StartURL}) + } PrintTableNoPad(tableData, true) return nil } @@ -472,6 +490,7 @@ func init() { browserPoolsCreateCmd.Flags().String("profile-name", "", "Profile name") browserPoolsCreateCmd.Flags().Bool("save-changes", false, "Save changes to profile") browserPoolsCreateCmd.Flags().String("proxy-id", "", "Proxy ID") + browserPoolsCreateCmd.Flags().String("start-url", "", "Initial page to open for new browsers") browserPoolsCreateCmd.Flags().StringSlice("extension", []string{}, "Extension IDs or names") browserPoolsCreateCmd.Flags().String("viewport", "", "Viewport size (e.g. 1280x800)") @@ -488,6 +507,8 @@ func init() { browserPoolsUpdateCmd.Flags().String("profile-name", "", "Profile name") browserPoolsUpdateCmd.Flags().Bool("save-changes", false, "Save changes to profile") browserPoolsUpdateCmd.Flags().String("proxy-id", "", "Proxy ID") + browserPoolsUpdateCmd.Flags().String("start-url", "", "Initial page to open for new browsers") + browserPoolsUpdateCmd.Flags().Bool("clear-start-url", false, "Clear the pool start URL") browserPoolsUpdateCmd.Flags().StringSlice("extension", []string{}, "Extension IDs or names") browserPoolsUpdateCmd.Flags().String("viewport", "", "Viewport size (e.g. 1280x800)") browserPoolsUpdateCmd.Flags().Bool("discard-all-idle", false, "Discard all idle browsers") @@ -539,6 +560,7 @@ func runBrowserPoolsCreate(cmd *cobra.Command, args []string) error { profileName, _ := cmd.Flags().GetString("profile-name") saveChanges, _ := cmd.Flags().GetBool("save-changes") proxyID, _ := cmd.Flags().GetString("proxy-id") + startURL, _ := cmd.Flags().GetString("start-url") extensions, _ := cmd.Flags().GetStringSlice("extension") viewport, _ := cmd.Flags().GetString("viewport") output, _ := cmd.Flags().GetString("output") @@ -555,6 +577,7 @@ func runBrowserPoolsCreate(cmd *cobra.Command, args []string) error { ProfileName: profileName, ProfileSaveChanges: BoolFlag{Set: cmd.Flags().Changed("save-changes"), Value: saveChanges}, ProxyID: proxyID, + StartURL: startURL, Extensions: extensions, Viewport: viewport, Output: output, @@ -585,6 +608,8 @@ func runBrowserPoolsUpdate(cmd *cobra.Command, args []string) error { profileName, _ := cmd.Flags().GetString("profile-name") saveChanges, _ := cmd.Flags().GetBool("save-changes") proxyID, _ := cmd.Flags().GetString("proxy-id") + startURL, _ := cmd.Flags().GetString("start-url") + clearStartURL, _ := cmd.Flags().GetBool("clear-start-url") extensions, _ := cmd.Flags().GetStringSlice("extension") viewport, _ := cmd.Flags().GetString("viewport") discardIdle, _ := cmd.Flags().GetBool("discard-all-idle") @@ -603,6 +628,8 @@ func runBrowserPoolsUpdate(cmd *cobra.Command, args []string) error { ProfileName: profileName, ProfileSaveChanges: BoolFlag{Set: cmd.Flags().Changed("save-changes"), Value: saveChanges}, ProxyID: proxyID, + StartURL: startURL, + ClearStartURL: clearStartURL, Extensions: extensions, Viewport: viewport, DiscardAllIdle: BoolFlag{Set: cmd.Flags().Changed("discard-all-idle"), Value: discardIdle}, diff --git a/cmd/browsers.go b/cmd/browsers.go index 66a2502..38f728d 100644 --- a/cmd/browsers.go +++ b/cmd/browsers.go @@ -186,6 +186,7 @@ type BrowsersCreateInput struct { ProfileName string ProfileSaveChanges BoolFlag ProxyID string + StartURL string Extensions []string Viewport string Output string @@ -392,6 +393,9 @@ func (b BrowsersCmd) Create(ctx context.Context, in BrowsersCreateInput) error { if in.ProxyID != "" { params.ProxyID = kernel.Opt(in.ProxyID) } + if in.StartURL != "" { + params.StartURL = kernel.Opt(in.StartURL) + } // Map extensions (IDs or names) into params.Extensions if len(in.Extensions) > 0 { @@ -435,17 +439,17 @@ func (b BrowsersCmd) Create(ctx context.Context, in BrowsersCreateInput) error { return util.PrintPrettyJSON(browser) } - printBrowserSessionResult(browser.SessionID, browser.CdpWsURL, browser.BrowserLiveViewURL, browser.Persistence, browser.Profile) + printBrowserSessionResult(browser.SessionID, browser.CdpWsURL, browser.BrowserLiveViewURL, browser.Persistence, browser.Profile, browser.StartURL) return nil } -func printBrowserSessionResult(sessionID, cdpURL, liveViewURL string, persistence kernel.BrowserPersistence, profile kernel.Profile) { - tableData := buildBrowserTableData(sessionID, cdpURL, liveViewURL, persistence, profile) +func printBrowserSessionResult(sessionID, cdpURL, liveViewURL string, persistence kernel.BrowserPersistence, profile kernel.Profile, startURL string) { + tableData := buildBrowserTableData(sessionID, cdpURL, liveViewURL, persistence, profile, startURL) PrintTableNoPad(tableData, true) } // buildBrowserTableData creates a base table with common browser session fields. -func buildBrowserTableData(sessionID, cdpURL, liveViewURL string, persistence kernel.BrowserPersistence, profile kernel.Profile) pterm.TableData { +func buildBrowserTableData(sessionID, cdpURL, liveViewURL string, persistence kernel.BrowserPersistence, profile kernel.Profile, startURL string) pterm.TableData { tableData := pterm.TableData{ {"Property", "Value"}, {"Session ID", sessionID}, @@ -464,6 +468,9 @@ func buildBrowserTableData(sessionID, cdpURL, liveViewURL string, persistence ke } tableData = append(tableData, []string{"Profile", profVal}) } + if startURL != "" { + tableData = append(tableData, []string{"Start URL", startURL}) + } return tableData } @@ -554,6 +561,7 @@ func (b BrowsersCmd) Get(ctx context.Context, in BrowsersGetInput) error { browser.BrowserLiveViewURL, browser.Persistence, browser.Profile, + browser.StartURL, ) // Append additional detailed fields @@ -2525,6 +2533,7 @@ func init() { browsersCreateCmd.Flags().String("profile-name", "", "Profile name to load into the browser session (mutually exclusive with --profile-id)") browsersCreateCmd.Flags().Bool("save-changes", false, "If set, save changes back to the profile when the session ends") browsersCreateCmd.Flags().String("proxy-id", "", "Proxy ID to use for the browser session") + browsersCreateCmd.Flags().String("start-url", "", "Initial page to open on launch") browsersCreateCmd.Flags().StringSlice("extension", []string{}, "Extension IDs or names to load (repeatable; may be passed multiple times or comma-separated)") 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") browsersCreateCmd.Flags().Bool("viewport-interactive", false, "Interactively select viewport size from list") @@ -2597,6 +2606,7 @@ func runBrowsersCreate(cmd *cobra.Command, args []string) error { profileName, _ := cmd.Flags().GetString("profile-name") saveChanges, _ := cmd.Flags().GetBool("save-changes") proxyID, _ := cmd.Flags().GetString("proxy-id") + startURL, _ := cmd.Flags().GetString("start-url") extensions, _ := cmd.Flags().GetStringSlice("extension") viewport, _ := cmd.Flags().GetString("viewport") viewportInteractive, _ := cmd.Flags().GetBool("viewport-interactive") @@ -2676,7 +2686,7 @@ func runBrowsersCreate(cmd *cobra.Command, args []string) error { if output == "json" { return util.PrintPrettyJSON(resp) } - printBrowserSessionResult(resp.SessionID, resp.CdpWsURL, resp.BrowserLiveViewURL, resp.Persistence, resp.Profile) + printBrowserSessionResult(resp.SessionID, resp.CdpWsURL, resp.BrowserLiveViewURL, resp.Persistence, resp.Profile, resp.StartURL) return nil } @@ -2709,6 +2719,7 @@ func runBrowsersCreate(cmd *cobra.Command, args []string) error { ProfileName: profileName, ProfileSaveChanges: BoolFlag{Set: cmd.Flags().Changed("save-changes"), Value: saveChanges}, ProxyID: proxyID, + StartURL: startURL, Extensions: extensions, Viewport: viewport, Output: output, diff --git a/cmd/browsers_test.go b/cmd/browsers_test.go index 816c2ea..b31e8cc 100644 --- a/cmd/browsers_test.go +++ b/cmd/browsers_test.go @@ -1632,6 +1632,24 @@ func TestBrowsersCreate_WithViewportNoRefreshRate(t *testing.T) { assert.False(t, captured.Viewport.RefreshRate.Valid()) } +func TestBrowsersCreate_WithStartURL(t *testing.T) { + setupStdoutCapture(t) + var captured kernel.BrowserNewParams + fake := &FakeBrowsersService{NewFunc: func(ctx context.Context, body kernel.BrowserNewParams, opts ...option.RequestOption) (*kernel.BrowserNewResponse, error) { + captured = body + return &kernel.BrowserNewResponse{SessionID: "session123", CdpWsURL: "ws://example"}, nil + }} + b := BrowsersCmd{browsers: fake} + + err := b.Create(context.Background(), BrowsersCreateInput{ + StartURL: "https://example.com", + }) + + assert.NoError(t, err) + assert.True(t, captured.StartURL.Valid()) + assert.Equal(t, "https://example.com", captured.StartURL.Value) +} + func TestBrowsersCreate_WithInvalidViewport(t *testing.T) { setupStdoutCapture(t) fake := &FakeBrowsersService{} diff --git a/go.mod b/go.mod index 53e8873..62cf135 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.52.0 + github.com/kernel/kernel-go-sdk v0.53.0 github.com/klauspost/compress v1.18.5 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/pterm/pterm v0.12.80 diff --git a/go.sum b/go.sum index 12e488c..b29cf9b 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.52.0 h1:ChRAMo6oMAEmazC610FtcqKFO/cqHzU9v1ECF0MiR8E= -github.com/kernel/kernel-go-sdk v0.52.0/go.mod h1:EeZzSuHZVeHKxKCPUzxou2bovNGhXaz0RXrSqKNf1AQ= +github.com/kernel/kernel-go-sdk v0.53.0 h1:XgcuJv3G4a6nr9LYBZ21gLUWvsIDLSG4YhZAngNrqE0= +github.com/kernel/kernel-go-sdk v0.53.0/go.mod h1:EeZzSuHZVeHKxKCPUzxou2bovNGhXaz0RXrSqKNf1AQ= github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= From 068a8802a5fb5351e555a70b67437cbc329bdcdb Mon Sep 17 00:00:00 2001 From: yummybomb <19238148+yummybomb@users.noreply.github.com> Date: Wed, 13 May 2026 18:56:54 +0000 Subject: [PATCH 2/2] Validate start URL flag values --- cmd/browser_pools.go | 13 +++++++++++++ cmd/browsers.go | 3 +++ cmd/browsers_test.go | 17 +++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/cmd/browser_pools.go b/cmd/browser_pools.go index 80272a0..ca5fa96 100644 --- a/cmd/browser_pools.go +++ b/cmd/browser_pools.go @@ -96,6 +96,9 @@ func (c BrowserPoolsCmd) Create(ctx context.Context, in BrowserPoolsCreateInput) if in.Output != "" && in.Output != "json" { return fmt.Errorf("unsupported --output value: use 'json'") } + if err := validateStartURLFlag(in.StartURL); err != nil { + return err + } params := kernel.BrowserPoolNewParams{ Size: in.Size, @@ -234,6 +237,9 @@ func (c BrowserPoolsCmd) Update(ctx context.Context, in BrowserPoolsUpdateInput) if in.Output != "" && in.Output != "json" { return fmt.Errorf("unsupported --output value: use 'json'") } + if err := validateStartURLFlag(in.StartURL); err != nil { + return err + } if in.StartURL != "" && in.ClearStartURL { return fmt.Errorf("cannot specify both --start-url and --clear-start-url") } @@ -692,6 +698,13 @@ func buildProfileParam(profileID, profileName string, saveChanges BoolFlag) (*ke return &profile, nil } +func validateStartURLFlag(startURL string) error { + if strings.HasPrefix(startURL, "-") { + return fmt.Errorf("--start-url requires a URL value") + } + return nil +} + func buildExtensionsParam(extensions []string) []kernel.BrowserExtensionParam { if len(extensions) == 0 { return nil diff --git a/cmd/browsers.go b/cmd/browsers.go index 38f728d..6d39958 100644 --- a/cmd/browsers.go +++ b/cmd/browsers.go @@ -347,6 +347,9 @@ func (b BrowsersCmd) Create(ctx context.Context, in BrowsersCreateInput) error { if in.Output != "" && in.Output != "json" { return fmt.Errorf("unsupported --output value: use 'json'") } + if err := validateStartURLFlag(in.StartURL); err != nil { + return err + } if in.Output != "json" { pterm.Info.Println("Creating browser session...") diff --git a/cmd/browsers_test.go b/cmd/browsers_test.go index b31e8cc..da4ba05 100644 --- a/cmd/browsers_test.go +++ b/cmd/browsers_test.go @@ -1650,6 +1650,23 @@ func TestBrowsersCreate_WithStartURL(t *testing.T) { assert.Equal(t, "https://example.com", captured.StartURL.Value) } +func TestBrowsersCreate_RejectsStartURLFlagToken(t *testing.T) { + called := false + fake := &FakeBrowsersService{NewFunc: func(ctx context.Context, body kernel.BrowserNewParams, opts ...option.RequestOption) (*kernel.BrowserNewResponse, error) { + called = true + return &kernel.BrowserNewResponse{}, nil + }} + b := BrowsersCmd{browsers: fake} + + err := b.Create(context.Background(), BrowsersCreateInput{ + StartURL: "--headless", + }) + + require.Error(t, err) + assert.Contains(t, err.Error(), "--start-url requires a URL value") + assert.False(t, called) +} + func TestBrowsersCreate_WithInvalidViewport(t *testing.T) { setupStdoutCapture(t) fake := &FakeBrowsersService{}