44 "context"
55 "errors"
66 "fmt"
7+ "net/http"
78 "os"
89 "os/signal"
910 "slices"
@@ -15,6 +16,7 @@ import (
1516 "github.com/kernel/cli/pkg/util"
1617 kernel "github.com/kernel/kernel-go-sdk"
1718 "github.com/kernel/kernel-go-sdk/option"
19+ "github.com/kernel/kernel-go-sdk/packages/pagination"
1820 "github.com/kernel/kernel-go-sdk/packages/ssestream"
1921 "github.com/pterm/pterm"
2022 "github.com/spf13/cobra"
@@ -23,13 +25,24 @@ import (
2325// BrowserTelemetryService defines the subset we use for browser telemetry streaming.
2426type BrowserTelemetryService interface {
2527 StreamStreaming (ctx context.Context , id string , query kernel.BrowserTelemetryStreamParams , opts ... option.RequestOption ) (stream * ssestream.Stream [kernel.BrowserTelemetryStreamResponse ])
28+ Events (ctx context.Context , id string , query kernel.BrowserTelemetryEventsParams , opts ... option.RequestOption ) (res * pagination.OffsetPagination [kernel.BrowserTelemetryEventsResponse ], err error )
2629}
2730
2831type BrowsersTelemetryStreamInput struct {
2932 Identifier string
3033 Categories []string
3134 Types []string
3235 Seq int64
36+ Replay string
37+ Output string
38+ }
39+
40+ type BrowsersTelemetryEventsInput struct {
41+ Identifier string
42+ Limit int64
43+ Offset int64
44+ Since string
45+ Until string
3346 Output string
3447}
3548
@@ -180,6 +193,9 @@ func (b BrowsersCmd) TelemetryStream(ctx context.Context, in BrowsersTelemetrySt
180193 return fmt .Errorf ("invalid --categories value %q: must be one of %s" , c , strings .Join (streamFilterCategories , ", " ))
181194 }
182195 }
196+ if in .Replay != "" && in .Replay != "all" {
197+ return fmt .Errorf ("invalid --replay value %q: only \" all\" is supported (omit --replay to stream from now)" , in .Replay )
198+ }
183199 ctx , stop := signal .NotifyContext (ctx , os .Interrupt , syscall .SIGTERM )
184200 defer stop ()
185201 br , err := b .browsers .Get (ctx , in .Identifier , kernel.BrowserGetParams {})
@@ -190,6 +206,9 @@ func (b BrowsersCmd) TelemetryStream(ctx context.Context, in BrowsersTelemetrySt
190206 if in .Seq >= 0 {
191207 params .LastEventID = kernel .Opt (strconv .FormatInt (in .Seq , 10 ))
192208 }
209+ if in .Replay != "" {
210+ params .Replay = kernel .Opt (in .Replay )
211+ }
193212 stream := b .telemetry .StreamStreaming (ctx , br .SessionID , params )
194213 defer stream .Close ()
195214 for stream .Next () {
@@ -223,12 +242,105 @@ func runBrowsersTelemetryStream(cmd *cobra.Command, args []string) error {
223242 categories , _ := cmd .Flags ().GetStringSlice ("categories" )
224243 types , _ := cmd .Flags ().GetStringSlice ("types" )
225244 seq , _ := cmd .Flags ().GetInt64 ("seq" )
245+ replay , _ := cmd .Flags ().GetString ("replay" )
226246 b := BrowsersCmd {browsers : & svc , telemetry : & svc .Telemetry }
227247 return b .TelemetryStream (cmd .Context (), BrowsersTelemetryStreamInput {
228248 Identifier : args [0 ],
229249 Categories : categories ,
230250 Types : types ,
231251 Seq : seq ,
252+ Replay : replay ,
253+ Output : out ,
254+ })
255+ }
256+
257+ func (b BrowsersCmd ) TelemetryEvents (ctx context.Context , in BrowsersTelemetryEventsInput ) error {
258+ if b .telemetry == nil {
259+ return fmt .Errorf ("telemetry service not available" )
260+ }
261+ if err := validateJSONOutput (in .Output ); err != nil {
262+ return err
263+ }
264+
265+ br , err := b .browsers .Get (ctx , in .Identifier , kernel.BrowserGetParams {})
266+ if err != nil {
267+ return util.CleanedUpSdkError {Err : err }
268+ }
269+
270+ params := kernel.BrowserTelemetryEventsParams {}
271+ if in .Limit > 0 {
272+ params .Limit = kernel .Opt (in .Limit )
273+ }
274+ if in .Offset > 0 {
275+ params .Offset = kernel .Opt (in .Offset )
276+ }
277+ if in .Since != "" {
278+ params .Since = kernel .Opt (in .Since )
279+ }
280+ if in .Until != "" {
281+ params .Until = kernel .Opt (in .Until )
282+ }
283+
284+ var raw * http.Response
285+ page , err := b .telemetry .Events (ctx , br .SessionID , params , option .WithResponseInto (& raw ))
286+ if err != nil {
287+ return util.CleanedUpSdkError {Err : err }
288+ }
289+
290+ var items []kernel.BrowserTelemetryEventsResponse
291+ if page != nil {
292+ items = page .Items
293+ }
294+
295+ if in .Output == "json" {
296+ if len (items ) == 0 {
297+ fmt .Println ("[]" )
298+ return nil
299+ }
300+ return util .PrintPrettyJSONSlice (items )
301+ }
302+
303+ if len (items ) == 0 {
304+ pterm .Info .Println ("No telemetry events found" )
305+ return nil
306+ }
307+
308+ rows := pterm.TableData {{"Seq" , "Time" , "Category" , "Type" }}
309+ for _ , it := range items {
310+ ts := time .UnixMicro (it .Event .Ts ).Local ().Format ("2006-01-02 15:04:05" )
311+ rows = append (rows , []string {
312+ strconv .FormatInt (it .Seq , 10 ),
313+ ts ,
314+ it .Event .Category ,
315+ it .Event .Type ,
316+ })
317+ }
318+ PrintTableNoPad (rows , true )
319+ // The next-page cursor is the opaque X-Next-Offset header; surface it so
320+ // --offset is actually usable for paging.
321+ if raw != nil {
322+ if next := raw .Header .Get ("X-Next-Offset" ); next != "" && next != "0" {
323+ pterm .Info .Printf ("More events available — re-run with --offset %s\n " , next )
324+ }
325+ }
326+ return nil
327+ }
328+
329+ func runBrowsersTelemetryEvents (cmd * cobra.Command , args []string ) error {
330+ client := getKernelClient (cmd )
331+ svc := client .Browsers
332+ out , _ := cmd .Flags ().GetString ("output" )
333+ limit , _ := cmd .Flags ().GetInt64 ("limit" )
334+ offset , _ := cmd .Flags ().GetInt64 ("offset" )
335+ since , _ := cmd .Flags ().GetString ("since" )
336+ until , _ := cmd .Flags ().GetString ("until" )
337+ b := BrowsersCmd {browsers : & svc , telemetry : & svc .Telemetry }
338+ return b .TelemetryEvents (cmd .Context (), BrowsersTelemetryEventsInput {
339+ Identifier : args [0 ],
340+ Limit : limit ,
341+ Offset : offset ,
342+ Since : since ,
343+ Until : until ,
232344 Output : out ,
233345 })
234346}
0 commit comments