Skip to content

Commit 9e4ef95

Browse files
committed
feat: add browsers telemetry set, status subcommands and simplify start/stop
1 parent 1ccc8c9 commit 9e4ef95

3 files changed

Lines changed: 191 additions & 13 deletions

File tree

README.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ Commands with JSON output support:
211211
- `--start-url <url>` - Initial page to open on launch
212212
- `--pool-id <id>` - Acquire a browser from the specified pool (mutually exclusive with --pool-name; ignores other session flags)
213213
- `--pool-name <name>` - Acquire a browser from the pool name (mutually exclusive with --pool-id; ignores other session flags)
214-
- `--telemetry` - Enable telemetry capture on the new session (see `kernel browsers telemetry stream`)
214+
- `--telemetry` - Enable telemetry capture on the new session (`enabled: true`)
215215
- `--output json`, `-o json` - Output raw JSON object
216216
- _Note: When a pool is specified, omit other session configuration flags—pool settings determine profile, proxy, viewport, etc._
217217
- `kernel browsers delete <id>` - Delete a browser
@@ -283,15 +283,20 @@ Commands with JSON output support:
283283

284284
### Browser Telemetry
285285

286+
- `kernel browsers telemetry start <id>` - Start telemetry capture (`enabled: true`)
287+
- `-o, --output json` - Output raw API response
288+
- `kernel browsers telemetry stop <id>` - Stop telemetry capture (`enabled: false`)
289+
- `-o, --output json` - Output raw API response
290+
- `kernel browsers telemetry set <id>` - Set per-category telemetry config
291+
- `--categories <assignments>` - Comma-separated `name=on|off` pairs, e.g. `network=on,page=off` (console, interaction, network, page)
292+
- `-o, --output json` - Output raw API response
293+
- `kernel browsers telemetry status <id>` - Show current telemetry configuration
294+
- `-o, --output json` - Output raw JSON telemetry config
286295
- `kernel browsers telemetry stream <id>` - Stream live telemetry events
287296
- `--categories <list>` - Filter by category (console,network,page,interaction,monitor)
288297
- `--types <list>` - Filter by event type (e.g. network_response,console_error)
289298
- `--seq <n>` - Resume stream from sequence number (Last-Event-ID)
290299
- `-o, --output json` - Output newline-delimited JSON envelopes
291-
- `kernel browsers telemetry start <id>` - Start telemetry capture on a running session
292-
- `-o, --output json` - Output raw API response
293-
- `kernel browsers telemetry stop <id>` - Stop telemetry capture on a running session
294-
- `-o, --output json` - Output raw API response
295300

296301
### Browser Process Control
297302

cmd/browsers.go

Lines changed: 114 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,8 @@ type BrowsersCreateInput struct {
193193
StartURL string
194194
Extensions []string
195195
Viewport string
196-
TelemetryEnabled bool
197-
Output string
196+
TelemetryEnabled bool
197+
Output string
198198
}
199199

200200
type BrowsersDeleteInput struct {
@@ -234,6 +234,17 @@ type BrowsersTelemetryStopInput struct {
234234
Output string
235235
}
236236

237+
type BrowsersTelemetrySetInput struct {
238+
Identifier string
239+
Categories string // e.g. "network=on,page=off"
240+
Output string
241+
}
242+
243+
type BrowsersTelemetryStatusInput struct {
244+
Identifier string
245+
Output string
246+
}
247+
237248
type BrowsersTelemetryStreamInput struct {
238249
Identifier string
239250
Categories []string
@@ -715,6 +726,78 @@ func (b BrowsersCmd) TelemetryStop(ctx context.Context, in BrowsersTelemetryStop
715726
return nil
716727
}
717728

729+
func (b BrowsersCmd) TelemetrySet(ctx context.Context, in BrowsersTelemetrySetInput) error {
730+
if in.Output != "" && in.Output != "json" {
731+
return fmt.Errorf("unsupported --output value: use 'json'")
732+
}
733+
p := kernel.BrowserTelemetryCategoriesConfigParam{}
734+
for _, part := range strings.Split(in.Categories, ",") {
735+
kv := strings.SplitN(strings.TrimSpace(part), "=", 2)
736+
if len(kv) != 2 {
737+
return fmt.Errorf("invalid category assignment %q: expected name=on or name=off", part)
738+
}
739+
name, val := strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1])
740+
var enabled bool
741+
switch val {
742+
case "on":
743+
enabled = true
744+
case "off":
745+
enabled = false
746+
default:
747+
return fmt.Errorf("invalid value %q for category %q: must be 'on' or 'off'", val, name)
748+
}
749+
switch name {
750+
case "console":
751+
p.Console = kernel.BrowserTelemetryCategoryConfigParam{Enabled: kernel.Opt(enabled)}
752+
case "interaction":
753+
p.Interaction = kernel.BrowserTelemetryCategoryConfigParam{Enabled: kernel.Opt(enabled)}
754+
case "network":
755+
p.Network = kernel.BrowserTelemetryCategoryConfigParam{Enabled: kernel.Opt(enabled)}
756+
case "page":
757+
p.Page = kernel.BrowserTelemetryCategoryConfigParam{Enabled: kernel.Opt(enabled)}
758+
default:
759+
return fmt.Errorf("unknown category %q: must be one of console, interaction, network, page", name)
760+
}
761+
}
762+
res, err := b.browsers.Update(ctx, in.Identifier, kernel.BrowserUpdateParams{
763+
Telemetry: kernel.BrowserTelemetryRequestConfigParam{Browser: p},
764+
})
765+
if err != nil {
766+
return util.CleanedUpSdkError{Err: err}
767+
}
768+
if in.Output == "json" {
769+
return util.PrintPrettyJSON(res)
770+
}
771+
pterm.Success.Printf("Updated telemetry categories for browser %s\n", in.Identifier)
772+
return nil
773+
}
774+
775+
func (b BrowsersCmd) TelemetryStatus(ctx context.Context, in BrowsersTelemetryStatusInput) error {
776+
if in.Output != "" && in.Output != "json" {
777+
return fmt.Errorf("unsupported --output value: use 'json'")
778+
}
779+
browser, err := b.browsers.Get(ctx, in.Identifier, kernel.BrowserGetParams{})
780+
if err != nil {
781+
return util.CleanedUpSdkError{Err: err}
782+
}
783+
if in.Output == "json" {
784+
return util.PrintPrettyJSON(browser.Telemetry)
785+
}
786+
cfg := browser.Telemetry.Browser
787+
pterm.Printf("console: %s\n", onOff(cfg.Console.Enabled))
788+
pterm.Printf("interaction: %s\n", onOff(cfg.Interaction.Enabled))
789+
pterm.Printf("network: %s\n", onOff(cfg.Network.Enabled))
790+
pterm.Printf("page: %s\n", onOff(cfg.Page.Enabled))
791+
return nil
792+
}
793+
794+
func onOff(v bool) string {
795+
if v {
796+
return "on"
797+
}
798+
return "off"
799+
}
800+
718801
// eventCategory derives the category prefix from a telemetry event type string.
719802
// e.g. "network_response" -> "network", "monitor_screenshot" -> "monitor".
720803
func eventCategory(eventType string) string {
@@ -2400,11 +2483,17 @@ func init() {
24002483
telemetryStream.Flags().StringSlice("types", []string{}, "Filter by event type (e.g. network_response,console_error)")
24012484
telemetryStream.Flags().Int64("seq", 0, "Resume stream from sequence number (Last-Event-ID)")
24022485
telemetryStream.Flags().StringP("output", "o", "", "Output format: json for newline-delimited JSON envelopes")
2403-
telemetryStart := &cobra.Command{Use: "start <id>", Short: "Start telemetry capture", Args: cobra.ExactArgs(1), RunE: runBrowsersTelemetryStart}
2486+
telemetryStart := &cobra.Command{Use: "start <id>", Short: "Start telemetry capture (enabled: true)", Args: cobra.ExactArgs(1), RunE: runBrowsersTelemetryStart}
24042487
telemetryStart.Flags().StringP("output", "o", "", "Output format: json for raw API response")
2405-
telemetryStop := &cobra.Command{Use: "stop <id>", Short: "Stop telemetry capture", Args: cobra.ExactArgs(1), RunE: runBrowsersTelemetryStop}
2488+
telemetryStop := &cobra.Command{Use: "stop <id>", Short: "Stop telemetry capture (enabled: false)", Args: cobra.ExactArgs(1), RunE: runBrowsersTelemetryStop}
24062489
telemetryStop.Flags().StringP("output", "o", "", "Output format: json for raw API response")
2407-
telemetryRoot.AddCommand(telemetryStream, telemetryStart, telemetryStop)
2490+
telemetrySet := &cobra.Command{Use: "set <id>", Short: "Set per-category telemetry config", Args: cobra.ExactArgs(1), RunE: runBrowsersTelemetrySet}
2491+
telemetrySet.Flags().String("categories", "", "Per-category assignments, e.g. network=on,page=off (console, interaction, network, page)")
2492+
_ = telemetrySet.MarkFlagRequired("categories")
2493+
telemetrySet.Flags().StringP("output", "o", "", "Output format: json for raw API response")
2494+
telemetryStatus := &cobra.Command{Use: "status <id>", Short: "Show current telemetry configuration", Args: cobra.ExactArgs(1), RunE: runBrowsersTelemetryStatus}
2495+
telemetryStatus.Flags().StringP("output", "o", "", "Output format: json for raw API response")
2496+
telemetryRoot.AddCommand(telemetryStream, telemetryStart, telemetryStop, telemetrySet, telemetryStatus)
24082497
browsersCmd.AddCommand(telemetryRoot)
24092498

24102499
// process
@@ -2636,7 +2725,7 @@ func init() {
26362725
browsersCreateCmd.Flags().Bool("viewport-interactive", false, "Interactively select viewport size from list")
26372726
browsersCreateCmd.Flags().String("pool-id", "", "Browser pool ID to acquire from (mutually exclusive with --pool-name)")
26382727
browsersCreateCmd.Flags().String("pool-name", "", "Browser pool name to acquire from (mutually exclusive with --pool-id)")
2639-
browsersCreateCmd.Flags().Bool("telemetry", false, "Enable telemetry capture on the new session")
2728+
browsersCreateCmd.Flags().Bool("telemetry", false, "Enable telemetry capture on the new session (enabled: true)")
26402729

26412730
// curl
26422731
curlCmd := &cobra.Command{
@@ -2816,8 +2905,8 @@ func runBrowsersCreate(cmd *cobra.Command, args []string) error {
28162905
StartURL: startURL,
28172906
Extensions: extensions,
28182907
Viewport: viewport,
2819-
TelemetryEnabled: telemetry,
2820-
Output: output,
2908+
TelemetryEnabled: telemetry,
2909+
Output: output,
28212910
}
28222911

28232912
svc := client.Browsers
@@ -2957,6 +3046,23 @@ func runBrowsersTelemetryStop(cmd *cobra.Command, args []string) error {
29573046
return b.TelemetryStop(cmd.Context(), BrowsersTelemetryStopInput{Identifier: args[0], Output: out})
29583047
}
29593048

3049+
func runBrowsersTelemetrySet(cmd *cobra.Command, args []string) error {
3050+
client := getKernelClient(cmd)
3051+
svc := client.Browsers
3052+
out, _ := cmd.Flags().GetString("output")
3053+
categories, _ := cmd.Flags().GetString("categories")
3054+
b := BrowsersCmd{browsers: &svc}
3055+
return b.TelemetrySet(cmd.Context(), BrowsersTelemetrySetInput{Identifier: args[0], Categories: categories, Output: out})
3056+
}
3057+
3058+
func runBrowsersTelemetryStatus(cmd *cobra.Command, args []string) error {
3059+
client := getKernelClient(cmd)
3060+
svc := client.Browsers
3061+
out, _ := cmd.Flags().GetString("output")
3062+
b := BrowsersCmd{browsers: &svc}
3063+
return b.TelemetryStatus(cmd.Context(), BrowsersTelemetryStatusInput{Identifier: args[0], Output: out})
3064+
}
3065+
29603066
func runBrowsersTelemetryStream(cmd *cobra.Command, args []string) error {
29613067
client := getKernelClient(cmd)
29623068
svc := client.Browsers

cmd/browsers_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1802,6 +1802,46 @@ func TestBrowsersTelemetryStop_SendsDisablePayload(t *testing.T) {
18021802
assert.Contains(t, outBuf.String(), "Stopped telemetry for browser session123")
18031803
}
18041804

1805+
func TestBrowsersTelemetrySet_PartialCategories(t *testing.T) {
1806+
setupStdoutCapture(t)
1807+
var captured kernel.BrowserUpdateParams
1808+
fake := &FakeBrowsersService{UpdateFunc: func(ctx context.Context, id string, body kernel.BrowserUpdateParams, opts ...option.RequestOption) (*kernel.BrowserUpdateResponse, error) {
1809+
captured = body
1810+
return &kernel.BrowserUpdateResponse{SessionID: id}, nil
1811+
}}
1812+
b := BrowsersCmd{browsers: fake}
1813+
1814+
err := b.TelemetrySet(context.Background(), BrowsersTelemetrySetInput{Identifier: "session123", Categories: "network=on,page=off"})
1815+
1816+
assert.NoError(t, err)
1817+
assert.False(t, captured.Telemetry.Enabled.Valid())
1818+
assert.True(t, captured.Telemetry.Browser.Network.Enabled.Valid())
1819+
assert.True(t, captured.Telemetry.Browser.Network.Enabled.Value)
1820+
assert.True(t, captured.Telemetry.Browser.Page.Enabled.Valid())
1821+
assert.False(t, captured.Telemetry.Browser.Page.Enabled.Value)
1822+
// Unspecified categories omitted — server retains their state
1823+
assert.False(t, captured.Telemetry.Browser.Console.Enabled.Valid())
1824+
assert.False(t, captured.Telemetry.Browser.Interaction.Enabled.Valid())
1825+
}
1826+
1827+
func TestBrowsersTelemetrySet_InvalidCategory(t *testing.T) {
1828+
b := BrowsersCmd{browsers: &FakeBrowsersService{}}
1829+
1830+
err := b.TelemetrySet(context.Background(), BrowsersTelemetrySetInput{Identifier: "session123", Categories: "foo=on"})
1831+
1832+
assert.Error(t, err)
1833+
assert.Contains(t, err.Error(), "unknown category")
1834+
}
1835+
1836+
func TestBrowsersTelemetrySet_InvalidValue(t *testing.T) {
1837+
b := BrowsersCmd{browsers: &FakeBrowsersService{}}
1838+
1839+
err := b.TelemetrySet(context.Background(), BrowsersTelemetrySetInput{Identifier: "session123", Categories: "network=yes"})
1840+
1841+
assert.Error(t, err)
1842+
assert.Contains(t, err.Error(), "must be 'on' or 'off'")
1843+
}
1844+
18051845
func TestBrowsersTelemetryStop_UnsupportedOutputErrors(t *testing.T) {
18061846
fake := &FakeBrowsersService{}
18071847
b := BrowsersCmd{browsers: fake}
@@ -1812,6 +1852,33 @@ func TestBrowsersTelemetryStop_UnsupportedOutputErrors(t *testing.T) {
18121852
assert.Contains(t, err.Error(), "unsupported --output value")
18131853
}
18141854

1855+
func TestBrowsersTelemetryStatus_PrintsCategories(t *testing.T) {
1856+
setupStdoutCapture(t)
1857+
fake := &FakeBrowsersService{GetFunc: func(ctx context.Context, id string, query kernel.BrowserGetParams, opts ...option.RequestOption) (*kernel.BrowserGetResponse, error) {
1858+
return &kernel.BrowserGetResponse{
1859+
SessionID: id,
1860+
Telemetry: kernel.BrowserTelemetryConfig{
1861+
Browser: kernel.BrowserTelemetryCategoriesConfig{
1862+
Console: kernel.BrowserTelemetryCategoryConfig{Enabled: true},
1863+
Interaction: kernel.BrowserTelemetryCategoryConfig{Enabled: false},
1864+
Network: kernel.BrowserTelemetryCategoryConfig{Enabled: true},
1865+
Page: kernel.BrowserTelemetryCategoryConfig{Enabled: false},
1866+
},
1867+
},
1868+
}, nil
1869+
}}
1870+
b := BrowsersCmd{browsers: fake}
1871+
1872+
err := b.TelemetryStatus(context.Background(), BrowsersTelemetryStatusInput{Identifier: "session123"})
1873+
1874+
assert.NoError(t, err)
1875+
out := outBuf.String()
1876+
assert.Contains(t, out, "console: on")
1877+
assert.Contains(t, out, "interaction: off")
1878+
assert.Contains(t, out, "network: on")
1879+
assert.Contains(t, out, "page: off")
1880+
}
1881+
18151882
func TestEventCategory(t *testing.T) {
18161883
cases := map[string]string{
18171884
"network_response": "network",

0 commit comments

Comments
 (0)