Skip to content

Commit 502c042

Browse files
authored
refactor(browsers): reuse browser lookup logic (#45)
<!-- CURSOR_SUMMARY --> > [!NOTE] > Adds a `browsers get` command for detailed session info and switches `view`/`delete` to use direct Get-based lookups with pagination helpers. > > - **CLI**: > - **New**: `browsers get <id>` to display detailed browser session info (table or `--output json`). > - **View**: now uses direct `Get` and prints live view URL to stdout; warns when headless or URL missing. > - Registers `get` command and `--output` flag. > - **Core/Refactor**: > - Extend `BrowsersService` with `Get`. > - Extract `buildBrowserTableData` for shared table rendering. > - **Delete**: reuse `resolveBrowserByIdentifier`; add full pagination via `listAllBrowsers` and `safeGetNextPage`. > - **Tests**: > - Add/adjust tests for `get`, `view` (headless/missing/err), and error paths. > - Update fakes to support `Get` and new behaviors. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit be7d1c5. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 81c29c0 commit 502c042

File tree

2 files changed

+278
-50
lines changed

2 files changed

+278
-50
lines changed

cmd/browsers.go

Lines changed: 127 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
// BrowsersService defines the subset of the Kernel SDK browser client that we use.
3030
// See https://github.com/onkernel/kernel-go-sdk/blob/main/browser.go
3131
type BrowsersService interface {
32+
Get(ctx context.Context, id string, opts ...option.RequestOption) (res *kernel.BrowserGetResponse, err error)
3233
List(ctx context.Context, query kernel.BrowserListParams, opts ...option.RequestOption) (res *pagination.OffsetPagination[kernel.BrowserListResponse], err error)
3334
New(ctx context.Context, body kernel.BrowserNewParams, opts ...option.RequestOption) (res *kernel.BrowserNewResponse, err error)
3435
Delete(ctx context.Context, body kernel.BrowserDeleteParams, opts ...option.RequestOption) (err error)
@@ -173,6 +174,11 @@ type BrowsersViewInput struct {
173174
Identifier string
174175
}
175176

177+
type BrowsersGetInput struct {
178+
Identifier string
179+
Output string
180+
}
181+
176182
// BrowsersCmd is a cobra-independent command handler for browsers operations.
177183
type BrowsersCmd struct {
178184
browsers BrowsersService
@@ -359,6 +365,12 @@ func (b BrowsersCmd) Create(ctx context.Context, in BrowsersCreateInput) error {
359365
}
360366

361367
func printBrowserSessionResult(sessionID, cdpURL, liveViewURL string, persistence kernel.BrowserPersistence, profile kernel.Profile) {
368+
tableData := buildBrowserTableData(sessionID, cdpURL, liveViewURL, persistence, profile)
369+
PrintTableNoPad(tableData, true)
370+
}
371+
372+
// buildBrowserTableData creates a base table with common browser session fields.
373+
func buildBrowserTableData(sessionID, cdpURL, liveViewURL string, persistence kernel.BrowserPersistence, profile kernel.Profile) pterm.TableData {
362374
tableData := pterm.TableData{
363375
{"Property", "Value"},
364376
{"Session ID", sessionID},
@@ -377,29 +389,15 @@ func printBrowserSessionResult(sessionID, cdpURL, liveViewURL string, persistenc
377389
}
378390
tableData = append(tableData, []string{"Profile", profVal})
379391
}
380-
381-
PrintTableNoPad(tableData, true)
392+
return tableData
382393
}
383394

384395
func (b BrowsersCmd) Delete(ctx context.Context, in BrowsersDeleteInput) error {
385396
if !in.SkipConfirm {
386-
page, err := b.browsers.List(ctx, kernel.BrowserListParams{})
397+
found, err := b.resolveBrowserByIdentifier(ctx, in.Identifier)
387398
if err != nil {
388399
return util.CleanedUpSdkError{Err: err}
389400
}
390-
if page == nil || page.Items == nil || len(page.Items) == 0 {
391-
pterm.Error.Println("No browsers found")
392-
return nil
393-
}
394-
395-
var found *kernel.BrowserListResponse
396-
for _, br := range page.Items {
397-
if br.SessionID == in.Identifier || br.Persistence.ID == in.Identifier {
398-
bCopy := br
399-
found = &bCopy
400-
break
401-
}
402-
}
403401
if found == nil {
404402
pterm.Error.Printf("Browser '%s' not found\n", in.Identifier)
405403
return nil
@@ -459,31 +457,81 @@ func (b BrowsersCmd) Delete(ctx context.Context, in BrowsersDeleteInput) error {
459457
}
460458

461459
func (b BrowsersCmd) View(ctx context.Context, in BrowsersViewInput) error {
462-
page, err := b.browsers.List(ctx, kernel.BrowserListParams{})
460+
browser, err := b.browsers.Get(ctx, in.Identifier)
463461
if err != nil {
464462
return util.CleanedUpSdkError{Err: err}
465463
}
466-
467-
if page == nil || page.Items == nil || len(page.Items) == 0 {
468-
pterm.Error.Println("No browsers found")
464+
if browser == nil {
465+
pterm.Error.Printf("Browser '%s' not found\n", in.Identifier)
469466
return nil
470467
}
471-
472-
var foundBrowser *kernel.BrowserListResponse
473-
for _, browser := range page.Items {
474-
if browser.Persistence.ID == in.Identifier || browser.SessionID == in.Identifier {
475-
foundBrowser = &browser
476-
break
468+
if browser.BrowserLiveViewURL == "" {
469+
if browser.Headless {
470+
pterm.Warning.Println("This browser is running in headless mode and does not have a live view URL")
471+
} else {
472+
pterm.Warning.Println("No live view URL available for this browser")
477473
}
474+
return nil
475+
}
476+
477+
fmt.Println(browser.BrowserLiveViewURL)
478+
return nil
479+
}
480+
481+
func (b BrowsersCmd) Get(ctx context.Context, in BrowsersGetInput) error {
482+
if in.Output != "" && in.Output != "json" {
483+
pterm.Error.Println("unsupported --output value: use 'json'")
484+
return nil
478485
}
479486

480-
if foundBrowser == nil {
487+
browser, err := b.browsers.Get(ctx, in.Identifier)
488+
if err != nil {
489+
return util.CleanedUpSdkError{Err: err}
490+
}
491+
if browser == nil {
481492
pterm.Error.Printf("Browser '%s' not found\n", in.Identifier)
482493
return nil
483494
}
484495

485-
// Output just the URL
486-
pterm.Info.Println(foundBrowser.BrowserLiveViewURL)
496+
if in.Output == "json" {
497+
bs, err := json.MarshalIndent(browser, "", " ")
498+
if err != nil {
499+
return err
500+
}
501+
fmt.Println(string(bs))
502+
return nil
503+
}
504+
505+
// Build table starting with common browser fields
506+
tableData := buildBrowserTableData(
507+
browser.SessionID,
508+
browser.CdpWsURL,
509+
browser.BrowserLiveViewURL,
510+
browser.Persistence,
511+
browser.Profile,
512+
)
513+
514+
// Append additional detailed fields
515+
tableData = append(tableData, []string{"Created At", util.FormatLocal(browser.CreatedAt)})
516+
tableData = append(tableData, []string{"Timeout (seconds)", fmt.Sprintf("%d", browser.TimeoutSeconds)})
517+
tableData = append(tableData, []string{"Headless", fmt.Sprintf("%t", browser.Headless)})
518+
tableData = append(tableData, []string{"Stealth", fmt.Sprintf("%t", browser.Stealth)})
519+
tableData = append(tableData, []string{"Kiosk Mode", fmt.Sprintf("%t", browser.KioskMode)})
520+
if browser.Viewport.Width > 0 && browser.Viewport.Height > 0 {
521+
viewportStr := fmt.Sprintf("%dx%d", browser.Viewport.Width, browser.Viewport.Height)
522+
if browser.Viewport.RefreshRate > 0 {
523+
viewportStr = fmt.Sprintf("%s@%d", viewportStr, browser.Viewport.RefreshRate)
524+
}
525+
tableData = append(tableData, []string{"Viewport", viewportStr})
526+
}
527+
if browser.ProxyID != "" {
528+
tableData = append(tableData, []string{"Proxy ID", browser.ProxyID})
529+
}
530+
if !browser.DeletedAt.IsZero() {
531+
tableData = append(tableData, []string{"Deleted At", util.FormatLocal(browser.DeletedAt)})
532+
}
533+
534+
PrintTableNoPad(tableData, true)
487535
return nil
488536
}
489537

@@ -1831,17 +1879,29 @@ var browsersViewCmd = &cobra.Command{
18311879
RunE: runBrowsersView,
18321880
}
18331881

1882+
var browsersGetCmd = &cobra.Command{
1883+
Use: "get <id>",
1884+
Short: "Get detailed information about a browser session",
1885+
Long: "Retrieve and display detailed information about a specific browser session including configuration, URLs, and status.",
1886+
Args: cobra.ExactArgs(1),
1887+
RunE: runBrowsersGet,
1888+
}
1889+
18341890
func init() {
18351891
// list flags
18361892
browsersListCmd.Flags().StringP("output", "o", "", "Output format: json for raw API response")
18371893
browsersListCmd.Flags().Bool("include-deleted", false, "Include soft-deleted browser sessions in the results")
18381894
browsersListCmd.Flags().Int("limit", 0, "Maximum number of results to return (default 20, max 100)")
18391895
browsersListCmd.Flags().Int("offset", 0, "Number of results to skip (for pagination)")
18401896

1897+
// get flags
1898+
browsersGetCmd.Flags().StringP("output", "o", "", "Output format: json for raw API response")
1899+
18411900
browsersCmd.AddCommand(browsersListCmd)
18421901
browsersCmd.AddCommand(browsersCreateCmd)
18431902
browsersCmd.AddCommand(browsersDeleteCmd)
18441903
browsersCmd.AddCommand(browsersViewCmd)
1904+
browsersCmd.AddCommand(browsersGetCmd)
18451905

18461906
// logs
18471907
logsRoot := &cobra.Command{Use: "logs", Short: "Browser logs operations"}
@@ -2226,6 +2286,18 @@ func runBrowsersView(cmd *cobra.Command, args []string) error {
22262286
return b.View(cmd.Context(), in)
22272287
}
22282288

2289+
func runBrowsersGet(cmd *cobra.Command, args []string) error {
2290+
client := getKernelClient(cmd)
2291+
out, _ := cmd.Flags().GetString("output")
2292+
2293+
svc := client.Browsers
2294+
b := BrowsersCmd{browsers: &svc}
2295+
return b.Get(cmd.Context(), BrowsersGetInput{
2296+
Identifier: args[0],
2297+
Output: out,
2298+
})
2299+
}
2300+
22292301
func runBrowsersLogsStream(cmd *cobra.Command, args []string) error {
22302302
client := getKernelClient(cmd)
22312303
svc := client.Browsers
@@ -2643,16 +2715,37 @@ func truncateURL(url string, maxLen int) string {
26432715
return url[:maxLen-3] + "..."
26442716
}
26452717

2646-
// resolveBrowserByIdentifier finds a browser by session ID or persistent ID (backward compatibility).
2647-
func (b BrowsersCmd) resolveBrowserByIdentifier(ctx context.Context, identifier string) (*kernel.BrowserListResponse, error) {
2718+
// listAllBrowsers fetches all browsers by paginating through all pages.
2719+
func (b BrowsersCmd) listAllBrowsers(ctx context.Context) ([]kernel.BrowserListResponse, error) {
2720+
var allBrowsers []kernel.BrowserListResponse
26482721
page, err := b.browsers.List(ctx, kernel.BrowserListParams{})
26492722
if err != nil {
26502723
return nil, err
26512724
}
2652-
if page == nil || page.Items == nil {
2653-
return nil, nil
2725+
for page != nil && len(page.Items) > 0 {
2726+
allBrowsers = append(allBrowsers, page.Items...)
2727+
page = safeGetNextPage(page)
2728+
}
2729+
return allBrowsers, nil
2730+
}
2731+
2732+
// safeGetNextPage attempts to get the next page, returning nil if unavailable or on error.
2733+
func safeGetNextPage(page *pagination.OffsetPagination[kernel.BrowserListResponse]) *pagination.OffsetPagination[kernel.BrowserListResponse] {
2734+
defer func() { recover() }()
2735+
nextPage, err := page.GetNextPage()
2736+
if err != nil {
2737+
return nil
2738+
}
2739+
return nextPage
2740+
}
2741+
2742+
// resolveBrowserByIdentifier finds a browser by session ID or persistent ID (backward compatibility).
2743+
func (b BrowsersCmd) resolveBrowserByIdentifier(ctx context.Context, identifier string) (*kernel.BrowserListResponse, error) {
2744+
browsers, err := b.listAllBrowsers(ctx)
2745+
if err != nil {
2746+
return nil, err
26542747
}
2655-
for _, br := range page.Items {
2748+
for _, br := range browsers {
26562749
if br.SessionID == identifier || br.Persistence.ID == identifier {
26572750
bCopy := br
26582751
return &bCopy, nil

0 commit comments

Comments
 (0)