@@ -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
3131type 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.
177183type BrowsersCmd struct {
178184 browsers BrowsersService
@@ -359,6 +365,12 @@ func (b BrowsersCmd) Create(ctx context.Context, in BrowsersCreateInput) error {
359365}
360366
361367func 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
384395func (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
461459func (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+
18341890func 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+
22292301func 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