From 517750e88087280406b2a28c8e2172e75921ae04 Mon Sep 17 00:00:00 2001 From: Nikhil Date: Fri, 27 Mar 2026 12:05:34 -0700 Subject: [PATCH] feat: add PostHog to CLI (#603) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add PostHog usage analytics to CLI Add anonymous command-level analytics to browseros-cli using the PostHog Go SDK. Tracks which commands are executed, their success/failure status, and duration — no PII or person profiles. - New analytics package with Init/Track/Close singleton - Distinct ID resolves from server's browseros_id (server.json), falls back to CLI-generated UUID (~/.config/browseros-cli/install_id) - API key injected at build time via ldflags (dev builds = silent no-op) - Server now writes browseros_id into server.json for cross-surface identity correlation * fix: address PR review feedback for #603 - Return "unknown" for unrecognized args in commandName to avoid sending arbitrary user input to PostHog - Revert goreleaser to {{ .Env.POSTHOG_API_KEY }} (intentional hard fail — release builds must have the key set) - go mod tidy to fix posthog-go direct/indirect marker - Add POSTHOG_API_KEY to .env.production.example --- .../apps/cli/.env.production.example | 5 +- .../browseros-agent/apps/cli/.goreleaser.yml | 2 +- packages/browseros-agent/apps/cli/Makefile | 6 +- .../apps/cli/analytics/analytics.go | 129 +++++++++++++++++ .../apps/cli/analytics/analytics_test.go | 132 ++++++++++++++++++ packages/browseros-agent/apps/cli/cmd/root.go | 19 ++- .../browseros-agent/apps/cli/cmd/root_test.go | 25 ++++ packages/browseros-agent/apps/cli/go.mod | 8 +- packages/browseros-agent/apps/cli/go.sum | 23 ++- .../browseros-agent/apps/server/src/main.ts | 1 + .../shared/src/types/server-config.ts | 1 + 11 files changed, 342 insertions(+), 9 deletions(-) create mode 100644 packages/browseros-agent/apps/cli/analytics/analytics.go create mode 100644 packages/browseros-agent/apps/cli/analytics/analytics_test.go create mode 100644 packages/browseros-agent/apps/cli/cmd/root_test.go diff --git a/packages/browseros-agent/apps/cli/.env.production.example b/packages/browseros-agent/apps/cli/.env.production.example index 8679791fb..492bac471 100644 --- a/packages/browseros-agent/apps/cli/.env.production.example +++ b/packages/browseros-agent/apps/cli/.env.production.example @@ -1,5 +1,8 @@ -# Production upload env for CLI installer scripts +# Production build env for CLI +POSTHOG_API_KEY= + +# Upload env for CLI installer scripts R2_ACCOUNT_ID= R2_ACCESS_KEY_ID= R2_SECRET_ACCESS_KEY= diff --git a/packages/browseros-agent/apps/cli/.goreleaser.yml b/packages/browseros-agent/apps/cli/.goreleaser.yml index a47d20dd5..66d27e7e1 100644 --- a/packages/browseros-agent/apps/cli/.goreleaser.yml +++ b/packages/browseros-agent/apps/cli/.goreleaser.yml @@ -13,7 +13,7 @@ builds: flags: - -trimpath ldflags: - - -s -w -X main.version={{ .Version }} + - -s -w -X main.version={{ .Version }} -X browseros-cli/analytics.posthogAPIKey={{ .Env.POSTHOG_API_KEY }} targets: - darwin_amd64 - darwin_arm64 diff --git a/packages/browseros-agent/apps/cli/Makefile b/packages/browseros-agent/apps/cli/Makefile index c27b510ac..2a287657c 100644 --- a/packages/browseros-agent/apps/cli/Makefile +++ b/packages/browseros-agent/apps/cli/Makefile @@ -1,14 +1,16 @@ BINARY := browseros-cli SOURCES := $(shell find . -name '*.go') VERSION ?= dev +POSTHOG_API_KEY ?= +LDFLAGS := -X main.version=$(VERSION) -X browseros-cli/analytics.posthogAPIKey=$(POSTHOG_API_KEY) $(BINARY): $(SOURCES) - go build -ldflags "-X main.version=$(VERSION)" -o $(BINARY) . + go build -ldflags "$(LDFLAGS)" -o $(BINARY) . .PHONY: install clean vet test install: - go install -ldflags "-X main.version=$(VERSION)" . + go install -ldflags "$(LDFLAGS)" . clean: rm -f $(BINARY) diff --git a/packages/browseros-agent/apps/cli/analytics/analytics.go b/packages/browseros-agent/apps/cli/analytics/analytics.go new file mode 100644 index 000000000..a3ac301ea --- /dev/null +++ b/packages/browseros-agent/apps/cli/analytics/analytics.go @@ -0,0 +1,129 @@ +package analytics + +import ( + "crypto/rand" + "encoding/json" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "time" + + "browseros-cli/config" + + "github.com/posthog/posthog-go" +) + +var ( + posthogAPIKey string // set via ldflags + posthogHost = "https://us.i.posthog.com" +) + +const eventPrefix = "browseros.cli." + +var svc *service + +type service struct { + client posthog.Client + distinctID string +} + +func Init(version string) { + if posthogAPIKey == "" { + return + } + + distinctID := resolveDistinctID() + if distinctID == "" { + return + } + + client, err := posthog.NewWithConfig(posthogAPIKey, posthog.Config{ + Endpoint: posthogHost, + BatchSize: 10, + ShutdownTimeout: 3 * time.Second, + DefaultEventProperties: posthog.NewProperties(). + Set("cli_version", version). + Set("os", runtime.GOOS). + Set("arch", runtime.GOARCH), + }) + if err != nil { + return + } + + svc = &service{client: client, distinctID: distinctID} +} + +func Track(command string, success bool, duration time.Duration) { + if svc == nil { + return + } + svc.client.Enqueue(posthog.Capture{ + DistinctId: svc.distinctID, + Event: eventPrefix + "command_executed", + Properties: posthog.NewProperties(). + Set("command", command). + Set("success", success). + Set("duration_ms", duration.Milliseconds()). + Set("$process_person_profile", false), + }) +} + +func Close() { + if svc == nil { + return + } + svc.client.Close() + svc = nil +} + +func resolveDistinctID() string { + if id := loadBrowserosID(); id != "" { + return id + } + return loadOrCreateInstallID() +} + +func loadBrowserosID() string { + home, err := os.UserHomeDir() + if err != nil { + return "" + } + data, err := os.ReadFile(filepath.Join(home, ".browseros", "server.json")) + if err != nil { + return "" + } + var sc struct { + BrowserosID string `json:"browseros_id"` + } + if json.Unmarshal(data, &sc) != nil { + return "" + } + return sc.BrowserosID +} + +func loadOrCreateInstallID() string { + dir := config.Dir() + idPath := filepath.Join(dir, "install_id") + + data, err := os.ReadFile(idPath) + if err == nil { + if id := strings.TrimSpace(string(data)); id != "" { + return id + } + } + + id := generateUUID() + os.MkdirAll(dir, 0755) + os.WriteFile(idPath, []byte(id), 0644) + return id +} + +func generateUUID() string { + var b [16]byte + rand.Read(b[:]) + b[6] = (b[6] & 0x0f) | 0x40 // version 4 + b[8] = (b[8] & 0x3f) | 0x80 // variant 2 + return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) +} diff --git a/packages/browseros-agent/apps/cli/analytics/analytics_test.go b/packages/browseros-agent/apps/cli/analytics/analytics_test.go new file mode 100644 index 000000000..5e11477be --- /dev/null +++ b/packages/browseros-agent/apps/cli/analytics/analytics_test.go @@ -0,0 +1,132 @@ +package analytics + +import ( + "encoding/json" + "os" + "path/filepath" + "regexp" + "testing" + "time" +) + +func TestGenerateUUID(t *testing.T) { + id := generateUUID() + uuidRe := regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`) + if !uuidRe.MatchString(id) { + t.Errorf("generateUUID() = %q, does not match UUID v4 pattern", id) + } + + id2 := generateUUID() + if id == id2 { + t.Error("generateUUID() returned the same value twice") + } +} + +func TestLoadBrowserosID(t *testing.T) { + tmp := t.TempDir() + t.Setenv("HOME", tmp) + + // No server.json → empty + if got := loadBrowserosID(); got != "" { + t.Errorf("loadBrowserosID() = %q, want empty", got) + } + + // server.json without browseros_id → empty + dir := filepath.Join(tmp, ".browseros") + os.MkdirAll(dir, 0755) + data, _ := json.Marshal(map[string]any{"server_port": 9100, "url": "http://127.0.0.1:9100"}) + os.WriteFile(filepath.Join(dir, "server.json"), data, 0644) + + if got := loadBrowserosID(); got != "" { + t.Errorf("loadBrowserosID() = %q, want empty (no browseros_id field)", got) + } + + // server.json with browseros_id → returns it + data, _ = json.Marshal(map[string]any{ + "server_port": 9100, + "url": "http://127.0.0.1:9100", + "browseros_id": "test-uuid-1234", + }) + os.WriteFile(filepath.Join(dir, "server.json"), data, 0644) + + if got := loadBrowserosID(); got != "test-uuid-1234" { + t.Errorf("loadBrowserosID() = %q, want %q", got, "test-uuid-1234") + } +} + +func TestLoadOrCreateInstallID(t *testing.T) { + tmp := t.TempDir() + configDir := filepath.Join(tmp, "browseros-cli") + t.Setenv("XDG_CONFIG_HOME", tmp) + + // First call creates the file + id := loadOrCreateInstallID() + if id == "" { + t.Fatal("loadOrCreateInstallID() returned empty string") + } + + // File was persisted + data, err := os.ReadFile(filepath.Join(configDir, "install_id")) + if err != nil { + t.Fatalf("install_id file not created: %v", err) + } + if string(data) != id { + t.Errorf("persisted id = %q, want %q", string(data), id) + } + + // Second call returns the same ID + id2 := loadOrCreateInstallID() + if id2 != id { + t.Errorf("loadOrCreateInstallID() = %q, want stable %q", id2, id) + } +} + +func TestResolveDistinctID_PrefersBrowserosID(t *testing.T) { + tmp := t.TempDir() + t.Setenv("HOME", tmp) + t.Setenv("XDG_CONFIG_HOME", tmp) + + // Write server.json with browseros_id + dir := filepath.Join(tmp, ".browseros") + os.MkdirAll(dir, 0755) + data, _ := json.Marshal(map[string]any{"browseros_id": "server-uuid"}) + os.WriteFile(filepath.Join(dir, "server.json"), data, 0644) + + got := resolveDistinctID() + if got != "server-uuid" { + t.Errorf("resolveDistinctID() = %q, want %q", got, "server-uuid") + } +} + +func TestResolveDistinctID_FallsBackToInstallID(t *testing.T) { + tmp := t.TempDir() + t.Setenv("HOME", tmp) + t.Setenv("XDG_CONFIG_HOME", tmp) + + // No server.json → should generate install_id + got := resolveDistinctID() + if got == "" { + t.Error("resolveDistinctID() returned empty string") + } +} + +func TestInitNoopsWithoutAPIKey(t *testing.T) { + old := posthogAPIKey + posthogAPIKey = "" + defer func() { posthogAPIKey = old }() + + Init("1.0.0") + if svc != nil { + t.Error("Init() created service without API key") + } +} + +func TestTrackAndCloseNoopWithoutInit(t *testing.T) { + old := svc + svc = nil + defer func() { svc = old }() + + // Should not panic + Track("test", true, time.Second) + Close() +} diff --git a/packages/browseros-agent/apps/cli/cmd/root.go b/packages/browseros-agent/apps/cli/cmd/root.go index d7a704971..a736e7576 100644 --- a/packages/browseros-agent/apps/cli/cmd/root.go +++ b/packages/browseros-agent/apps/cli/cmd/root.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "browseros-cli/analytics" "browseros-cli/config" "browseros-cli/mcp" "browseros-cli/output" @@ -113,11 +114,27 @@ var rootCmd = &cobra.Command{ } func Execute() { - if err := rootCmd.Execute(); err != nil { + analytics.Init(version) + start := time.Now() + + err := rootCmd.Execute() + + analytics.Track(commandName(os.Args[1:]), err == nil, time.Since(start)) + analytics.Close() + + if err != nil { os.Exit(1) } } +func commandName(args []string) string { + cmd, _, err := rootCmd.Find(args) + if err != nil || cmd == rootCmd { + return "unknown" + } + return cmd.CommandPath() +} + func init() { cobra.AddTemplateFunc("helpHeader", helpHeader) cobra.AddTemplateFunc("helpCmdCol", helpCmdCol) diff --git a/packages/browseros-agent/apps/cli/cmd/root_test.go b/packages/browseros-agent/apps/cli/cmd/root_test.go new file mode 100644 index 000000000..87b665169 --- /dev/null +++ b/packages/browseros-agent/apps/cli/cmd/root_test.go @@ -0,0 +1,25 @@ +package cmd + +import "testing" + +func TestCommandName(t *testing.T) { + tests := []struct { + name string + args []string + want string + }{ + {"empty args", nil, "unknown"}, + {"known command", []string{"health"}, "browseros-cli health"}, + {"unknown command", []string{"nonexistent"}, "unknown"}, + {"subcommand", []string{"bookmark", "search"}, "browseros-cli bookmark search"}, + {"known with extra args", []string{"snap", "--enhanced"}, "browseros-cli snap"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := commandName(tt.args) + if got != tt.want { + t.Errorf("commandName(%v) = %q, want %q", tt.args, got, tt.want) + } + }) + } +} diff --git a/packages/browseros-agent/apps/cli/go.mod b/packages/browseros-agent/apps/cli/go.mod index 99b1f3ab4..1144d5164 100644 --- a/packages/browseros-agent/apps/cli/go.mod +++ b/packages/browseros-agent/apps/cli/go.mod @@ -4,20 +4,24 @@ go 1.25.7 require ( github.com/fatih/color v1.18.0 + github.com/modelcontextprotocol/go-sdk v1.4.0 + github.com/posthog/posthog-go v1.11.2 github.com/spf13/cobra v1.10.2 + gopkg.in/yaml.v3 v3.0.1 ) require ( + github.com/goccy/go-json v0.10.5 // indirect github.com/google/jsonschema-go v0.4.2 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/modelcontextprotocol/go-sdk v1.4.0 // indirect github.com/segmentio/asm v1.1.3 // indirect github.com/segmentio/encoding v0.5.3 // indirect github.com/spf13/pflag v1.0.9 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sys v0.40.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/packages/browseros-agent/apps/cli/go.sum b/packages/browseros-agent/apps/cli/go.sum index 82283e39c..c874ad6b5 100644 --- a/packages/browseros-agent/apps/cli/go.sum +++ b/packages/browseros-agent/apps/cli/go.sum @@ -1,8 +1,20 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -12,6 +24,10 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modelcontextprotocol/go-sdk v1.4.0 h1:u0kr8lbJc1oBcawK7Df+/ajNMpIDFE41OEPxdeTLOn8= github.com/modelcontextprotocol/go-sdk v1.4.0/go.mod h1:Nxc2n+n/GdCebUaqCOhTetptS17SXXNu9IfNTaLDi1E= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posthog/posthog-go v1.11.2 h1:ApKTtOhIeWhUBc4ByO+mlbg2o0iZaEGJnJHX2QDnn5Q= +github.com/posthog/posthog-go v1.11.2/go.mod h1:xsVOW9YImilUcazwPNEq4PJDqEZf2KeCS758zXjwkPg= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= @@ -21,6 +37,8 @@ github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= @@ -28,10 +46,11 @@ golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/packages/browseros-agent/apps/server/src/main.ts b/packages/browseros-agent/apps/server/src/main.ts index 5865d02ad..3e63b8cb7 100644 --- a/packages/browseros-agent/apps/server/src/main.ts +++ b/packages/browseros-agent/apps/server/src/main.ts @@ -116,6 +116,7 @@ export class Application { server_version: VERSION, browseros_version: this.config.instanceBrowserosVersion, chromium_version: this.config.instanceChromiumVersion, + browseros_id: identity.getBrowserOSId(), }) } catch (error) { logger.warn('Failed to write server config for auto-discovery', { diff --git a/packages/browseros-agent/packages/shared/src/types/server-config.ts b/packages/browseros-agent/packages/shared/src/types/server-config.ts index 886bfaf1e..b0dd69344 100644 --- a/packages/browseros-agent/packages/shared/src/types/server-config.ts +++ b/packages/browseros-agent/packages/shared/src/types/server-config.ts @@ -13,4 +13,5 @@ export interface ServerDiscoveryConfig { server_version: string browseros_version?: string chromium_version?: string + browseros_id?: string }