Skip to content

Commit a7716d9

Browse files
CLI: Update SDK to 9e90177b921114c93e264ca9792537bf2d8de754 and add missing flags
Keep the CLI aligned with the latest kernel-go-sdk release while exposing browser process env/TTY options and browser pool chrome policy support that were already present in the SDK. Tested: go test ./cmd/... && go build ./... Tested: kernel browsers process exec --env Tested: kernel browsers process spawn --allocate-tty --cols --rows --env Tested: kernel browser-pools create/update --chrome-policy Made-with: Cursor
1 parent 6d54518 commit a7716d9

6 files changed

Lines changed: 261 additions & 13 deletions

File tree

cmd/browser_pools.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cmd
22

33
import (
44
"context"
5+
"encoding/json"
56
"fmt"
67
"strings"
78

@@ -86,6 +87,7 @@ type BrowserPoolsCreateInput struct {
8687
ProfileName string
8788
ProfileSaveChanges BoolFlag
8889
ProxyID string
90+
ChromePolicy string
8991
Extensions []string
9092
Viewport string
9193
Output string
@@ -131,6 +133,14 @@ func (c BrowserPoolsCmd) Create(ctx context.Context, in BrowserPoolsCreateInput)
131133
if in.ProxyID != "" {
132134
params.ProxyID = kernel.String(in.ProxyID)
133135
}
136+
chromePolicy, err := parseChromePolicy(in.ChromePolicy)
137+
if err != nil {
138+
pterm.Error.Println(err.Error())
139+
return nil
140+
}
141+
if len(chromePolicy) > 0 {
142+
params.ChromePolicy = chromePolicy
143+
}
134144

135145
params.Extensions = buildExtensionsParam(in.Extensions)
136146

@@ -196,6 +206,7 @@ func (c BrowserPoolsCmd) Get(ctx context.Context, in BrowserPoolsGetInput) error
196206
{"Kiosk Mode", fmt.Sprintf("%t", cfg.KioskMode)},
197207
{"Profile", formatProfile(cfg.Profile)},
198208
{"Proxy ID", util.OrDash(cfg.ProxyID)},
209+
{"Chrome Policy", formatChromePolicy(cfg.ChromePolicy)},
199210
{"Extensions", formatExtensions(cfg.Extensions)},
200211
{"Viewport", formatViewport(cfg.Viewport)},
201212
}
@@ -217,6 +228,7 @@ type BrowserPoolsUpdateInput struct {
217228
ProfileName string
218229
ProfileSaveChanges BoolFlag
219230
ProxyID string
231+
ChromePolicy string
220232
Extensions []string
221233
Viewport string
222234
DiscardAllIdle BoolFlag
@@ -267,6 +279,14 @@ func (c BrowserPoolsCmd) Update(ctx context.Context, in BrowserPoolsUpdateInput)
267279
if in.ProxyID != "" {
268280
params.ProxyID = kernel.String(in.ProxyID)
269281
}
282+
chromePolicy, err := parseChromePolicy(in.ChromePolicy)
283+
if err != nil {
284+
pterm.Error.Println(err.Error())
285+
return nil
286+
}
287+
if len(chromePolicy) > 0 {
288+
params.ChromePolicy = chromePolicy
289+
}
270290

271291
params.Extensions = buildExtensionsParam(in.Extensions)
272292

@@ -472,6 +492,7 @@ func init() {
472492
browserPoolsCreateCmd.Flags().String("profile-name", "", "Profile name")
473493
browserPoolsCreateCmd.Flags().Bool("save-changes", false, "Save changes to profile")
474494
browserPoolsCreateCmd.Flags().String("proxy-id", "", "Proxy ID")
495+
browserPoolsCreateCmd.Flags().String("chrome-policy", "", "JSON object of Chrome enterprise policy overrides to apply to all browsers in the pool")
475496
browserPoolsCreateCmd.Flags().StringSlice("extension", []string{}, "Extension IDs or names")
476497
browserPoolsCreateCmd.Flags().String("viewport", "", "Viewport size (e.g. 1280x800)")
477498

@@ -488,6 +509,7 @@ func init() {
488509
browserPoolsUpdateCmd.Flags().String("profile-name", "", "Profile name")
489510
browserPoolsUpdateCmd.Flags().Bool("save-changes", false, "Save changes to profile")
490511
browserPoolsUpdateCmd.Flags().String("proxy-id", "", "Proxy ID")
512+
browserPoolsUpdateCmd.Flags().String("chrome-policy", "", "JSON object of Chrome enterprise policy overrides to apply to all browsers in the pool")
491513
browserPoolsUpdateCmd.Flags().StringSlice("extension", []string{}, "Extension IDs or names")
492514
browserPoolsUpdateCmd.Flags().String("viewport", "", "Viewport size (e.g. 1280x800)")
493515
browserPoolsUpdateCmd.Flags().Bool("discard-all-idle", false, "Discard all idle browsers")
@@ -539,6 +561,7 @@ func runBrowserPoolsCreate(cmd *cobra.Command, args []string) error {
539561
profileName, _ := cmd.Flags().GetString("profile-name")
540562
saveChanges, _ := cmd.Flags().GetBool("save-changes")
541563
proxyID, _ := cmd.Flags().GetString("proxy-id")
564+
chromePolicy, _ := cmd.Flags().GetString("chrome-policy")
542565
extensions, _ := cmd.Flags().GetStringSlice("extension")
543566
viewport, _ := cmd.Flags().GetString("viewport")
544567
output, _ := cmd.Flags().GetString("output")
@@ -555,6 +578,7 @@ func runBrowserPoolsCreate(cmd *cobra.Command, args []string) error {
555578
ProfileName: profileName,
556579
ProfileSaveChanges: BoolFlag{Set: cmd.Flags().Changed("save-changes"), Value: saveChanges},
557580
ProxyID: proxyID,
581+
ChromePolicy: chromePolicy,
558582
Extensions: extensions,
559583
Viewport: viewport,
560584
Output: output,
@@ -585,6 +609,7 @@ func runBrowserPoolsUpdate(cmd *cobra.Command, args []string) error {
585609
profileName, _ := cmd.Flags().GetString("profile-name")
586610
saveChanges, _ := cmd.Flags().GetBool("save-changes")
587611
proxyID, _ := cmd.Flags().GetString("proxy-id")
612+
chromePolicy, _ := cmd.Flags().GetString("chrome-policy")
588613
extensions, _ := cmd.Flags().GetStringSlice("extension")
589614
viewport, _ := cmd.Flags().GetString("viewport")
590615
discardIdle, _ := cmd.Flags().GetBool("discard-all-idle")
@@ -603,6 +628,7 @@ func runBrowserPoolsUpdate(cmd *cobra.Command, args []string) error {
603628
ProfileName: profileName,
604629
ProfileSaveChanges: BoolFlag{Set: cmd.Flags().Changed("save-changes"), Value: saveChanges},
605630
ProxyID: proxyID,
631+
ChromePolicy: chromePolicy,
606632
Extensions: extensions,
607633
Viewport: viewport,
608634
DiscardAllIdle: BoolFlag{Set: cmd.Flags().Changed("discard-all-idle"), Value: discardIdle},
@@ -687,6 +713,23 @@ func buildExtensionsParam(extensions []string) []kernel.BrowserExtensionParam {
687713
return result
688714
}
689715

716+
func parseChromePolicy(raw string) (map[string]any, error) {
717+
raw = strings.TrimSpace(raw)
718+
if raw == "" {
719+
return nil, nil
720+
}
721+
722+
var policy map[string]any
723+
if err := json.Unmarshal([]byte(raw), &policy); err != nil {
724+
return nil, fmt.Errorf("invalid --chrome-policy JSON: %w", err)
725+
}
726+
if policy == nil {
727+
return nil, fmt.Errorf("--chrome-policy must be a JSON object")
728+
}
729+
730+
return policy, nil
731+
}
732+
690733
func buildViewportParam(viewport string) (*kernel.BrowserViewportParam, error) {
691734
if viewport == "" {
692735
return nil, nil
@@ -735,6 +778,19 @@ func formatExtensions(extensions []kernel.BrowserExtension) string {
735778
return util.JoinOrDash(names...)
736779
}
737780

781+
func formatChromePolicy(policy map[string]any) string {
782+
if len(policy) == 0 {
783+
return "-"
784+
}
785+
786+
data, err := json.Marshal(policy)
787+
if err != nil {
788+
return fmt.Sprintf("%v", policy)
789+
}
790+
791+
return string(data)
792+
}
793+
738794
func formatViewport(viewport kernel.BrowserViewport) string {
739795
if viewport.Width == 0 || viewport.Height == 0 {
740796
return "-"

cmd/browser_pools_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/kernel/kernel-go-sdk"
8+
"github.com/kernel/kernel-go-sdk/option"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
type fakeBrowserPoolsService struct {
13+
newFunc func(ctx context.Context, body kernel.BrowserPoolNewParams, opts ...option.RequestOption) (*kernel.BrowserPool, error)
14+
}
15+
16+
func (f *fakeBrowserPoolsService) List(ctx context.Context, opts ...option.RequestOption) (*[]kernel.BrowserPool, error) {
17+
return &[]kernel.BrowserPool{}, nil
18+
}
19+
20+
func (f *fakeBrowserPoolsService) New(ctx context.Context, body kernel.BrowserPoolNewParams, opts ...option.RequestOption) (*kernel.BrowserPool, error) {
21+
if f.newFunc != nil {
22+
return f.newFunc(ctx, body, opts...)
23+
}
24+
return &kernel.BrowserPool{}, nil
25+
}
26+
27+
func (f *fakeBrowserPoolsService) Get(ctx context.Context, id string, opts ...option.RequestOption) (*kernel.BrowserPool, error) {
28+
return &kernel.BrowserPool{}, nil
29+
}
30+
31+
func (f *fakeBrowserPoolsService) Update(ctx context.Context, id string, body kernel.BrowserPoolUpdateParams, opts ...option.RequestOption) (*kernel.BrowserPool, error) {
32+
return &kernel.BrowserPool{}, nil
33+
}
34+
35+
func (f *fakeBrowserPoolsService) Delete(ctx context.Context, id string, body kernel.BrowserPoolDeleteParams, opts ...option.RequestOption) error {
36+
return nil
37+
}
38+
39+
func (f *fakeBrowserPoolsService) Acquire(ctx context.Context, id string, body kernel.BrowserPoolAcquireParams, opts ...option.RequestOption) (*kernel.BrowserPoolAcquireResponse, error) {
40+
return &kernel.BrowserPoolAcquireResponse{}, nil
41+
}
42+
43+
func (f *fakeBrowserPoolsService) Release(ctx context.Context, id string, body kernel.BrowserPoolReleaseParams, opts ...option.RequestOption) error {
44+
return nil
45+
}
46+
47+
func (f *fakeBrowserPoolsService) Flush(ctx context.Context, id string, opts ...option.RequestOption) error {
48+
return nil
49+
}
50+
51+
func TestBrowserPoolsCreate_MapsChromePolicy(t *testing.T) {
52+
setupStdoutCapture(t)
53+
54+
fake := &fakeBrowserPoolsService{
55+
newFunc: func(ctx context.Context, body kernel.BrowserPoolNewParams, opts ...option.RequestOption) (*kernel.BrowserPool, error) {
56+
assert.Equal(t, map[string]any{
57+
"HomepageLocation": "https://example.com",
58+
"ShowHomeButton": true,
59+
}, body.ChromePolicy)
60+
return &kernel.BrowserPool{ID: "pool_123", Name: "test-pool"}, nil
61+
},
62+
}
63+
64+
cmd := BrowserPoolsCmd{client: fake}
65+
err := cmd.Create(context.Background(), BrowserPoolsCreateInput{
66+
Name: "test-pool",
67+
Size: 1,
68+
ChromePolicy: `{"HomepageLocation":"https://example.com","ShowHomeButton":true}`,
69+
})
70+
71+
assert.NoError(t, err)
72+
}

cmd/browsers.go

Lines changed: 83 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,23 @@ func parseViewport(viewport string) (width, height, refreshRate int64, err error
170170
return w, h, refreshRate, nil
171171
}
172172

173+
func parseStringMapFlag(values []string, flagName string) (map[string]string, error) {
174+
if len(values) == 0 {
175+
return nil, nil
176+
}
177+
178+
parsed := make(map[string]string, len(values))
179+
for _, pair := range values {
180+
parts := strings.SplitN(pair, "=", 2)
181+
if len(parts) != 2 {
182+
return nil, fmt.Errorf("invalid %s value: %s (expected KEY=value)", flagName, pair)
183+
}
184+
parsed[parts[0]] = parts[1]
185+
}
186+
187+
return parsed, nil
188+
}
189+
173190
// Inputs for each command
174191
type BrowsersCreateInput struct {
175192
PersistenceID string
@@ -1257,18 +1274,23 @@ type BrowsersProcessExecInput struct {
12571274
Timeout int
12581275
AsUser string
12591276
AsRoot BoolFlag
1277+
Env []string
12601278
Output string
12611279
}
12621280

12631281
type BrowsersProcessSpawnInput struct {
1264-
Identifier string
1265-
Command string
1266-
Args []string
1267-
Cwd string
1268-
Timeout int
1269-
AsUser string
1270-
AsRoot BoolFlag
1271-
Output string
1282+
Identifier string
1283+
Command string
1284+
Args []string
1285+
Cwd string
1286+
Timeout int
1287+
AsUser string
1288+
AsRoot BoolFlag
1289+
AllocateTTY BoolFlag
1290+
Cols int64
1291+
Rows int64
1292+
Env []string
1293+
Output string
12721294
}
12731295

12741296
type BrowsersProcessKillInput struct {
@@ -1396,6 +1418,13 @@ func (b BrowsersCmd) ProcessExec(ctx context.Context, in BrowsersProcessExecInpu
13961418
if in.AsRoot.Set {
13971419
params.AsRoot = kernel.Opt(in.AsRoot.Value)
13981420
}
1421+
env, err := parseStringMapFlag(in.Env, "--env")
1422+
if err != nil {
1423+
return err
1424+
}
1425+
if len(env) > 0 {
1426+
params.Env = env
1427+
}
13991428
res, err := b.process.Exec(ctx, br.SessionID, params)
14001429
if err != nil {
14011430
return util.CleanedUpSdkError{Err: err}
@@ -1463,6 +1492,27 @@ func (b BrowsersCmd) ProcessSpawn(ctx context.Context, in BrowsersProcessSpawnIn
14631492
if in.AsRoot.Set {
14641493
params.AsRoot = kernel.Opt(in.AsRoot.Value)
14651494
}
1495+
if in.AllocateTTY.Set {
1496+
params.AllocateTty = kernel.Opt(in.AllocateTTY.Value)
1497+
}
1498+
if in.Cols > 0 || in.Rows > 0 {
1499+
if !in.AllocateTTY.Set || !in.AllocateTTY.Value {
1500+
return fmt.Errorf("--cols and --rows require --allocate-tty")
1501+
}
1502+
if in.Cols > 0 {
1503+
params.Cols = kernel.Opt(in.Cols)
1504+
}
1505+
if in.Rows > 0 {
1506+
params.Rows = kernel.Opt(in.Rows)
1507+
}
1508+
}
1509+
env, err := parseStringMapFlag(in.Env, "--env")
1510+
if err != nil {
1511+
return err
1512+
}
1513+
if len(env) > 0 {
1514+
params.Env = env
1515+
}
14661516
res, err := b.process.Spawn(ctx, br.SessionID, params)
14671517
if err != nil {
14681518
return util.CleanedUpSdkError{Err: err}
@@ -2297,6 +2347,7 @@ func init() {
22972347
procExec.Flags().Int("timeout", 0, "Timeout in seconds")
22982348
procExec.Flags().String("as-user", "", "Run as user")
22992349
procExec.Flags().Bool("as-root", false, "Run as root")
2350+
procExec.Flags().StringArray("env", nil, "Environment variable in KEY=value format (repeatable)")
23002351
procExec.Flags().StringP("output", "o", "", "Output format: json for raw API response")
23012352
procSpawn := &cobra.Command{Use: "spawn <id> [--] [command...]", Short: "Execute a command asynchronously", Args: cobra.MinimumNArgs(1), RunE: runBrowsersProcessSpawn}
23022353
procSpawn.Flags().String("command", "", "Command to execute (optional; if omitted, trailing args are executed via /bin/bash -c)")
@@ -2305,6 +2356,10 @@ func init() {
23052356
procSpawn.Flags().Int("timeout", 0, "Timeout in seconds")
23062357
procSpawn.Flags().String("as-user", "", "Run as user")
23072358
procSpawn.Flags().Bool("as-root", false, "Run as root")
2359+
procSpawn.Flags().Bool("allocate-tty", false, "Allocate a pseudo-terminal (PTY) for interactive shells")
2360+
procSpawn.Flags().Int64("cols", 0, "Initial terminal columns when --allocate-tty is enabled")
2361+
procSpawn.Flags().Int64("rows", 0, "Initial terminal rows when --allocate-tty is enabled")
2362+
procSpawn.Flags().StringArray("env", nil, "Environment variable in KEY=value format (repeatable)")
23082363
procSpawn.Flags().StringP("output", "o", "", "Output format: json for raw API response")
23092364
procKill := &cobra.Command{Use: "kill <id> <process-id>", Short: "Send a signal to a process", Args: cobra.ExactArgs(2), RunE: runBrowsersProcessKill}
23102365
procKill.Flags().String("signal", "TERM", "Signal to send (TERM, KILL, INT, HUP)")
@@ -2806,6 +2861,7 @@ func runBrowsersProcessExec(cmd *cobra.Command, args []string) error {
28062861
timeout, _ := cmd.Flags().GetInt("timeout")
28072862
asUser, _ := cmd.Flags().GetString("as-user")
28082863
asRoot, _ := cmd.Flags().GetBool("as-root")
2864+
env, _ := cmd.Flags().GetStringArray("env")
28092865
if command == "" && len(args) > 1 {
28102866
// Treat trailing args after identifier as a shell command
28112867
shellCmd := strings.Join(args[1:], " ")
@@ -2814,7 +2870,7 @@ func runBrowsersProcessExec(cmd *cobra.Command, args []string) error {
28142870
}
28152871
output, _ := cmd.Flags().GetString("output")
28162872
b := BrowsersCmd{browsers: &svc, process: &svc.Process}
2817-
return b.ProcessExec(cmd.Context(), BrowsersProcessExecInput{Identifier: args[0], Command: command, Args: argv, Cwd: cwd, Timeout: timeout, AsUser: asUser, AsRoot: BoolFlag{Set: cmd.Flags().Changed("as-root"), Value: asRoot}, Output: output})
2873+
return b.ProcessExec(cmd.Context(), BrowsersProcessExecInput{Identifier: args[0], Command: command, Args: argv, Cwd: cwd, Timeout: timeout, AsUser: asUser, AsRoot: BoolFlag{Set: cmd.Flags().Changed("as-root"), Value: asRoot}, Env: env, Output: output})
28182874
}
28192875

28202876
func runBrowsersProcessSpawn(cmd *cobra.Command, args []string) error {
@@ -2826,14 +2882,31 @@ func runBrowsersProcessSpawn(cmd *cobra.Command, args []string) error {
28262882
timeout, _ := cmd.Flags().GetInt("timeout")
28272883
asUser, _ := cmd.Flags().GetString("as-user")
28282884
asRoot, _ := cmd.Flags().GetBool("as-root")
2885+
allocateTTY, _ := cmd.Flags().GetBool("allocate-tty")
2886+
cols, _ := cmd.Flags().GetInt64("cols")
2887+
rows, _ := cmd.Flags().GetInt64("rows")
2888+
env, _ := cmd.Flags().GetStringArray("env")
28292889
if command == "" && len(args) > 1 {
28302890
shellCmd := strings.Join(args[1:], " ")
28312891
command = "/bin/bash"
28322892
argv = []string{"-c", shellCmd}
28332893
}
28342894
output, _ := cmd.Flags().GetString("output")
28352895
b := BrowsersCmd{browsers: &svc, process: &svc.Process}
2836-
return b.ProcessSpawn(cmd.Context(), BrowsersProcessSpawnInput{Identifier: args[0], Command: command, Args: argv, Cwd: cwd, Timeout: timeout, AsUser: asUser, AsRoot: BoolFlag{Set: cmd.Flags().Changed("as-root"), Value: asRoot}, Output: output})
2896+
return b.ProcessSpawn(cmd.Context(), BrowsersProcessSpawnInput{
2897+
Identifier: args[0],
2898+
Command: command,
2899+
Args: argv,
2900+
Cwd: cwd,
2901+
Timeout: timeout,
2902+
AsUser: asUser,
2903+
AsRoot: BoolFlag{Set: cmd.Flags().Changed("as-root"), Value: asRoot},
2904+
AllocateTTY: BoolFlag{Set: cmd.Flags().Changed("allocate-tty"), Value: allocateTTY},
2905+
Cols: cols,
2906+
Rows: rows,
2907+
Env: env,
2908+
Output: output,
2909+
})
28372910
}
28382911

28392912
func runBrowsersProcessKill(cmd *cobra.Command, args []string) error {

0 commit comments

Comments
 (0)