From 73816eee098544048506df64eae866df4a0e685e Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Thu, 23 Oct 2025 09:42:37 -0700 Subject: [PATCH 1/4] playwright code exec --- cmd/browsers.go | 104 +++++++++++++++++++++++++++++++++++++++++++++--- go.mod | 2 + go.sum | 4 +- 3 files changed, 102 insertions(+), 8 deletions(-) diff --git a/cmd/browsers.go b/cmd/browsers.go index ece0aa0..161368f 100644 --- a/cmd/browsers.go +++ b/cmd/browsers.go @@ -73,6 +73,11 @@ type BrowserLogService interface { StreamStreaming(ctx context.Context, id string, query kernel.BrowserLogStreamParams, opts ...option.RequestOption) (stream *ssestream.Stream[shared.LogEvent]) } +// BrowserPlaywrightService defines the subset we use for Playwright execution. +type BrowserPlaywrightService interface { + Execute(ctx context.Context, id string, body kernel.BrowserPlaywrightExecuteParams, opts ...option.RequestOption) (res *kernel.BrowserPlaywrightExecuteResponse, err error) +} + // BrowserComputerService defines the subset we use for OS-level mouse & screen. type BrowserComputerService interface { CaptureScreenshot(ctx context.Context, id string, body kernel.BrowserComputerCaptureScreenshotParams, opts ...option.RequestOption) (res *http.Response, err error) @@ -166,12 +171,13 @@ type BrowsersViewInput struct { // BrowsersCmd is a cobra-independent command handler for browsers operations. type BrowsersCmd struct { - browsers BrowsersService - replays BrowserReplaysService - fs BrowserFSService - process BrowserProcessService - logs BrowserLogService - computer BrowserComputerService + browsers BrowsersService + replays BrowserReplaysService + fs BrowserFSService + process BrowserProcessService + logs BrowserLogService + computer BrowserComputerService + playwright BrowserPlaywrightService } type BrowsersListInput struct { @@ -940,6 +946,59 @@ type BrowsersProcessStdoutStreamInput struct { ProcessID string } +// Playwright +type BrowsersPlaywrightExecuteInput struct { + Identifier string + Code string + Timeout int64 +} + +func (b BrowsersCmd) PlaywrightExecute(ctx context.Context, in BrowsersPlaywrightExecuteInput) error { + if b.playwright == nil { + pterm.Error.Println("playwright service not available") + return nil + } + br, err := b.resolveBrowserByIdentifier(ctx, in.Identifier) + if err != nil { + return util.CleanedUpSdkError{Err: err} + } + if br == nil { + pterm.Error.Printf("Browser '%s' not found\n", in.Identifier) + return nil + } + params := kernel.BrowserPlaywrightExecuteParams{Code: in.Code} + if in.Timeout > 0 { + params.TimeoutSec = kernel.Opt(in.Timeout) + } + res, err := b.playwright.Execute(ctx, br.SessionID, params) + if err != nil { + return util.CleanedUpSdkError{Err: err} + } + + rows := pterm.TableData{{"Property", "Value"}, {"Success", fmt.Sprintf("%t", res.Success)}} + PrintTableNoPad(rows, true) + + if res.Stdout != "" { + pterm.Info.Println("stdout:") + fmt.Println(res.Stdout) + } + if res.Stderr != "" { + pterm.Info.Println("stderr:") + fmt.Fprintln(os.Stderr, res.Stderr) + } + if res.Result != nil { + bs, err := json.MarshalIndent(res.Result, "", " ") + if err == nil { + pterm.Info.Println("result:") + fmt.Println(string(bs)) + } + } + if !res.Success && res.Error != "" { + pterm.Error.Printf("error: %s\n", res.Error) + } + return nil +} + func (b BrowsersCmd) ProcessExec(ctx context.Context, in BrowsersProcessExecInput) error { if b.process == nil { pterm.Error.Println("process service not available") @@ -1898,6 +1957,13 @@ func init() { computerRoot.AddCommand(computerClick, computerMove, computerScreenshot, computerType, computerPressKey, computerScroll, computerDrag) browsersCmd.AddCommand(computerRoot) + // playwright + playwrightRoot := &cobra.Command{Use: "playwright", Short: "Playwright operations"} + playwrightExecute := &cobra.Command{Use: "execute [code]", Short: "Execute Playwright/TypeScript code against the browser", Args: cobra.MinimumNArgs(1), RunE: runBrowsersPlaywrightExecute} + playwrightExecute.Flags().Int64("timeout", 0, "Maximum execution time in seconds (default per server)") + playwrightRoot.AddCommand(playwrightExecute) + browsersCmd.AddCommand(playwrightRoot) + // Add flags for create command browsersCreateCmd.Flags().StringP("persistent-id", "p", "", "Unique identifier for browser session persistence") browsersCreateCmd.Flags().BoolP("stealth", "s", false, "Launch browser in stealth mode to avoid detection") @@ -2120,6 +2186,32 @@ func runBrowsersProcessStdoutStream(cmd *cobra.Command, args []string) error { return b.ProcessStdoutStream(cmd.Context(), BrowsersProcessStdoutStreamInput{Identifier: args[0], ProcessID: args[1]}) } +func runBrowsersPlaywrightExecute(cmd *cobra.Command, args []string) error { + client := getKernelClient(cmd) + svc := client.Browsers + + var code string + if len(args) >= 2 { + code = strings.Join(args[1:], " ") + } else { + // Read code from stdin + stat, _ := os.Stdin.Stat() + if (stat.Mode() & os.ModeCharDevice) != 0 { + pterm.Error.Println("no code provided. Provide code as an argument or pipe via stdin") + return nil + } + data, err := io.ReadAll(os.Stdin) + if err != nil { + pterm.Error.Printf("failed to read stdin: %v\n", err) + return nil + } + code = string(data) + } + timeout, _ := cmd.Flags().GetInt64("timeout") + b := BrowsersCmd{browsers: &svc, playwright: &svc.Playwright} + return b.PlaywrightExecute(cmd.Context(), BrowsersPlaywrightExecuteInput{Identifier: args[0], Code: strings.TrimSpace(code), Timeout: timeout}) +} + func runBrowsersFSNewDirectory(cmd *cobra.Command, args []string) error { client := getKernelClient(cmd) svc := client.Browsers diff --git a/go.mod b/go.mod index 769b59e..4c3637c 100644 --- a/go.mod +++ b/go.mod @@ -58,3 +58,5 @@ require ( golang.org/x/text v0.24.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/onkernel/kernel-go-sdk => github.com/stainless-sdks/kernel-go v0.0.0-20251023003520-8596b55f4eda diff --git a/go.sum b/go.sum index 4f3e67f..09db721 100644 --- a/go.sum +++ b/go.sum @@ -91,8 +91,6 @@ github.com/muesli/mango-pflag v0.1.0 h1:UADqbYgpUyRoBja3g6LUL+3LErjpsOwaC9ywvBWe github.com/muesli/mango-pflag v0.1.0/go.mod h1:YEQomTxaCUp8PrbhFh10UfbhbQrM/xJ4i2PB8VTLLW0= github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8= github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig= -github.com/onkernel/kernel-go-sdk v0.15.0 h1:gLeZixS9bhOy1WuEk/eFogHUBjG6UJKN2l+OJNNdE+4= -github.com/onkernel/kernel-go-sdk v0.15.0/go.mod h1:MjUR92i8UPqjrmneyVykae6GuB3GGSmnQtnjf1v74Dc= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -118,6 +116,8 @@ github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stainless-sdks/kernel-go v0.0.0-20251023003520-8596b55f4eda h1:B4l9L34CCyUSwdHgNwRncNVi1kGaLxQBdeg08Nq49lg= +github.com/stainless-sdks/kernel-go v0.0.0-20251023003520-8596b55f4eda/go.mod h1:MjUR92i8UPqjrmneyVykae6GuB3GGSmnQtnjf1v74Dc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= From bee9c330653899d636572cd678ad482302214ce5 Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Mon, 27 Oct 2025 14:43:36 -0400 Subject: [PATCH 2/4] point go.mod to latest --- go.mod | 4 +--- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 4c3637c..1cf9e58 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/charmbracelet/fang v0.2.0 github.com/golang-jwt/jwt/v5 v5.2.2 github.com/joho/godotenv v1.5.1 - github.com/onkernel/kernel-go-sdk v0.15.0 + github.com/onkernel/kernel-go-sdk v0.16.0 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/pterm/pterm v0.12.80 github.com/samber/lo v1.51.0 @@ -58,5 +58,3 @@ require ( golang.org/x/text v0.24.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) - -replace github.com/onkernel/kernel-go-sdk => github.com/stainless-sdks/kernel-go v0.0.0-20251023003520-8596b55f4eda diff --git a/go.sum b/go.sum index 09db721..4bacd74 100644 --- a/go.sum +++ b/go.sum @@ -91,6 +91,8 @@ github.com/muesli/mango-pflag v0.1.0 h1:UADqbYgpUyRoBja3g6LUL+3LErjpsOwaC9ywvBWe github.com/muesli/mango-pflag v0.1.0/go.mod h1:YEQomTxaCUp8PrbhFh10UfbhbQrM/xJ4i2PB8VTLLW0= github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8= github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig= +github.com/onkernel/kernel-go-sdk v0.16.0 h1:vH07M5/28Ih3b7QVc+mEY+HycdGBZ+0MtjL6ALe326A= +github.com/onkernel/kernel-go-sdk v0.16.0/go.mod h1:MjUR92i8UPqjrmneyVykae6GuB3GGSmnQtnjf1v74Dc= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -116,8 +118,6 @@ github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stainless-sdks/kernel-go v0.0.0-20251023003520-8596b55f4eda h1:B4l9L34CCyUSwdHgNwRncNVi1kGaLxQBdeg08Nq49lg= -github.com/stainless-sdks/kernel-go v0.0.0-20251023003520-8596b55f4eda/go.mod h1:MjUR92i8UPqjrmneyVykae6GuB3GGSmnQtnjf1v74Dc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= From c55589ac1073e64c4dbb0b655b36c7825f73d64d Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Mon, 27 Oct 2025 14:59:10 -0400 Subject: [PATCH 3/4] docs --- README.md | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1fb21f0..8a65396 100644 --- a/README.md +++ b/README.md @@ -285,6 +285,12 @@ Create an API key from the [Kernel dashboard](https://dashboard.onkernel.com). - `--button