From 3aacb0dedff611430c23c68612f18583a81b795e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sun, 3 May 2026 18:00:37 +0200 Subject: [PATCH 01/11] add desktop login import --- cmd/bbctl/authconfig.go | 2 +- cmd/bbctl/desktopauth.go | 207 +++++++++++++++++++++++++++++++++++++++ cmd/bbctl/login-email.go | 68 +++++++++++-- cmd/bbctl/main.go | 1 + go.mod | 2 + go.sum | 6 ++ 6 files changed, 279 insertions(+), 7 deletions(-) create mode 100644 cmd/bbctl/desktopauth.go diff --git a/cmd/bbctl/authconfig.go b/cmd/bbctl/authconfig.go index 33bc77b..982331c 100644 --- a/cmd/bbctl/authconfig.go +++ b/cmd/bbctl/authconfig.go @@ -32,7 +32,7 @@ type EnvConfig struct { } func (ec *EnvConfig) HasCredentials() bool { - return strings.HasPrefix(ec.AccessToken, "syt_") + return strings.HasPrefix(ec.AccessToken, "syt_") || strings.HasPrefix(ec.AccessToken, "bat_") } type EnvConfigs map[string]*EnvConfig diff --git a/cmd/bbctl/desktopauth.go b/cmd/bbctl/desktopauth.go new file mode 100644 index 0000000..6caf250 --- /dev/null +++ b/cmd/bbctl/desktopauth.go @@ -0,0 +1,207 @@ +package main + +import ( + "context" + "database/sql" + "errors" + "fmt" + "net/url" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/urfave/cli/v2" + "go.mau.fi/util/dbutil" + "maunium.net/go/mautrix/id" + + "github.com/beeper/bridge-manager/api/beeperapi" + + _ "go.mau.fi/util/dbutil/litestream" +) + +var loginDesktopCommand = &cli.Command{ + Name: "login-desktop", + Usage: "Import Beeper Desktop's Matrix credentials into bbctl config", + Action: loginDesktop, + Flags: desktopLoginFlags(), +} + +func desktopLoginFlags() []cli.Flag { + return []cli.Flag{ + &cli.StringFlag{ + Name: "profile", + EnvVars: []string{"BEEPER_PROFILE"}, + Usage: "Beeper Desktop profile name, equivalent to BEEPER_PROFILE in Desktop", + }, + &cli.StringFlag{ + Name: "desktop-data-dir", + EnvVars: []string{"BBCTL_DESKTOP_DATA_DIR"}, + Usage: "Read Matrix credentials from this Beeper Desktop user data directory", + }, + &cli.StringFlag{ + Name: "desktop-account-db", + EnvVars: []string{"BBCTL_DESKTOP_ACCOUNT_DB"}, + Usage: "Read Matrix credentials from this Beeper Desktop account.db", + }, + } +} + +type DesktopAccount struct { + UserID id.UserID + DeviceID id.DeviceID + AccessToken string + Homeserver string +} + +func getDesktopAccountDBPath(ctx *cli.Context) (string, bool) { + if dbPath := ctx.String("desktop-account-db"); dbPath != "" { + return dbPath, true + } + if dataDir := ctx.String("desktop-data-dir"); dataDir != "" { + return filepath.Join(dataDir, "account.db"), true + } + return "", false +} + +func resolveDesktopDataDir(profile string) (string, error) { + appName := "BeeperTexts" + if profile != "" { + appName += "-" + profile + } + switch runtime.GOOS { + case "darwin": + home, err := os.UserHomeDir() + if err != nil { + return "", err + } + return filepath.Join(home, "Library", "Application Support", appName), nil + case "windows": + if appData := os.Getenv("APPDATA"); appData != "" { + return filepath.Join(appData, appName), nil + } + home, err := os.UserHomeDir() + if err != nil { + return "", err + } + return filepath.Join(home, appName), nil + default: + configHome := os.Getenv("XDG_CONFIG_HOME") + if configHome == "" { + home, err := os.UserHomeDir() + if err != nil { + return "", err + } + configHome = filepath.Join(home, ".config") + } + return filepath.Join(configHome, appName), nil + } +} + +func getLoginDesktopAccountDBPath(ctx *cli.Context) (string, error) { + if dbPath, ok := getDesktopAccountDBPath(ctx); ok { + return dbPath, nil + } + dataDir, err := resolveDesktopDataDir(ctx.String("profile")) + if err != nil { + return "", fmt.Errorf("failed to resolve desktop data directory: %w", err) + } + return filepath.Join(dataDir, "account.db"), nil +} + +func readDesktopAccount(ctx context.Context, dbPath string) (*DesktopAccount, error) { + dbURI := (&url.URL{ + Scheme: "file", + Path: dbPath, + RawQuery: "mode=ro", + }).String() + db, err := dbutil.NewWithDialect(dbURI, "sqlite3-fk-wal") + if err != nil { + return nil, fmt.Errorf("failed to open desktop account database: %w", err) + } + defer db.Close() + + var account DesktopAccount + err = db.QueryRow(ctx, "SELECT user_id, device_id, access_token, homeserver FROM account LIMIT 1"). + Scan(&account.UserID, &account.DeviceID, &account.AccessToken, &account.Homeserver) + if errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("desktop account database has no logged-in account") + } else if err != nil { + return nil, fmt.Errorf("failed to read desktop account database: %w", err) + } else if account.UserID == "" || account.AccessToken == "" { + return nil, fmt.Errorf("desktop account database has incomplete credentials") + } + return &account, nil +} + +func desktopAccountHomeserverDomain(account *DesktopAccount) (string, error) { + if account.Homeserver == "" { + return "", nil + } + parsed, err := url.Parse(account.Homeserver) + if err != nil { + return "", fmt.Errorf("desktop account has invalid homeserver URL %q: %w", account.Homeserver, err) + } + return strings.TrimPrefix(parsed.Host, "matrix."), nil +} + +func envForHomeserverDomain(domain string) string { + for env, envDomain := range envs { + if domain == envDomain { + return env + } + } + return "" +} + +func saveDesktopLogin(ctx *cli.Context, account *DesktopAccount) (string, string, error) { + homeserver, err := desktopAccountHomeserverDomain(account) + if err != nil { + return "", "", err + } + env := ctx.String("env") + if homeserverEnv := envForHomeserverDomain(homeserver); homeserverEnv != "" { + env = homeserverEnv + homeserver = envs[env] + } else if homeserver == "" { + homeserver = ctx.String("homeserver") + } + + whoami, err := beeperapi.Whoami(homeserver, account.AccessToken) + if err != nil { + return "", "", fmt.Errorf("failed to verify desktop credentials with whoami: %w", err) + } + + cfg := GetConfig(ctx) + envCfg := cfg.Environments.Get(env) + envCfg.ClusterID = whoami.UserInfo.BridgeClusterID + envCfg.Username = whoami.UserInfo.Username + envCfg.AccessToken = account.AccessToken + envCfg.BridgeDataDir = filepath.Join(UserDataDir, "bbctl", env) + err = cfg.Save() + if err != nil { + return "", "", fmt.Errorf("failed to save config: %w", err) + } + + return env, homeserver, nil +} + +func loginDesktop(ctx *cli.Context) error { + dbPath, err := getLoginDesktopAccountDBPath(ctx) + if err != nil { + return err + } + + account, err := readDesktopAccount(ctx.Context, dbPath) + if err != nil { + return err + } + + env, homeserver, err := saveDesktopLogin(ctx, account) + if err != nil { + return err + } + + fmt.Printf("Imported Desktop login for %s into bbctl env %q (%s)\n", account.UserID, env, homeserver) + return nil +} diff --git a/cmd/bbctl/login-email.go b/cmd/bbctl/login-email.go index 51ac065..6bbee79 100644 --- a/cmd/bbctl/login-email.go +++ b/cmd/bbctl/login-email.go @@ -11,29 +11,85 @@ import ( "maunium.net/go/mautrix" "github.com/beeper/bridge-manager/api/beeperapi" - "github.com/beeper/bridge-manager/cli/interactive" ) var loginCommand = &cli.Command{ Name: "login", Aliases: []string{"l"}, Usage: "Log into the Beeper server", - Before: interactive.Ask, Action: beeperLogin, Flags: []cli.Flag{ - interactive.Flag{Flag: &cli.StringFlag{ + &cli.StringFlag{ Name: "email", EnvVars: []string{"BEEPER_EMAIL"}, Usage: "The Beeper account email to log in with", - }, Survey: &survey.Input{ - Message: "Email:", - }}, + }, + &cli.BoolFlag{ + Name: "no-desktop", + EnvVars: []string{"BBCTL_NO_DESKTOP_LOGIN"}, + Usage: "Skip checking for an existing Beeper Desktop login", + }, }, } +func init() { + loginCommand.Flags = append(loginCommand.Flags, desktopLoginFlags()...) +} + +func maybeUseDesktopLogin(ctx *cli.Context) (bool, error) { + if ctx.Bool("no-desktop") { + return false, nil + } + dbPath, err := getLoginDesktopAccountDBPath(ctx) + if err != nil { + return false, err + } + account, err := readDesktopAccount(ctx.Context, dbPath) + if err != nil { + if ctx.IsSet("desktop-account-db") || ctx.IsSet("desktop-data-dir") { + return false, err + } + return false, nil + } + + useDesktop := false + err = survey.AskOne(&survey.Confirm{ + Message: fmt.Sprintf("Use Beeper Desktop login for %s?", account.UserID), + Default: true, + }, &useDesktop) + if err != nil { + return false, err + } + if !useDesktop { + return false, nil + } + + env, homeserver, err := saveDesktopLogin(ctx, account) + if err != nil { + return false, err + } + fmt.Printf("Imported Desktop login for %s into bbctl env %q (%s)\n", account.UserID, env, homeserver) + return true, nil +} + func beeperLogin(ctx *cli.Context) error { + didLogin, err := maybeUseDesktopLogin(ctx) + if err != nil { + return err + } else if didLogin { + return nil + } + homeserver := ctx.String("homeserver") email := ctx.String("email") + if email == "" { + err = survey.AskOne(&survey.Input{ + Message: "Email:", + }, &email) + if err != nil { + return err + } + } startLogin, err := beeperapi.StartLogin(homeserver) if err != nil { diff --git a/cmd/bbctl/main.go b/cmd/bbctl/main.go index 2c127f5..e127b7c 100644 --- a/cmd/bbctl/main.go +++ b/cmd/bbctl/main.go @@ -140,6 +140,7 @@ var app = &cli.App{ Before: prepareApp, Commands: []*cli.Command{ loginCommand, + loginDesktopCommand, loginPasswordCommand, logoutCommand, registerCommand, diff --git a/go.mod b/go.mod index 29fe6b9..ca4f28c 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,9 @@ require ( github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-sqlite3 v1.14.34 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect + github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/tidwall/match v1.1.1 // indirect diff --git a/go.sum b/go.sum index 8e76255..34b141d 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM= @@ -34,10 +36,14 @@ 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/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk= +github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= +github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 h1:KPpdlQLZcHfTMQRi6bFQ7ogNO0ltFT4PmtwTLW4W+14= +github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= From c479795d6d2c1cfeac81e5d22ff2bd315b137cb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sun, 3 May 2026 20:12:06 +0200 Subject: [PATCH 02/11] Support Beeper Desktop login integration Add support for using Beeper Desktop credentials for bbctl without persisting desktop access tokens. Introduce DesktopDataDir on EnvConfig and UsesDesktopLogin() to mark desktop-based envs; make AccessToken optional in config. When saving config, copy environments and omit AccessToken for desktop-logins. Add getDesktopDataDir helper, improve resolution of the desktop data directory, and enhance readDesktopAccount with better error handling for DB close and clearer local variables. Rename and refactor saveDesktopLogin to configureDesktopLogin and add loadDesktopLogin to populate runtime credentials from DesktopDataDir (loaded automatically in prepareApp for non-login commands). Update CLI flag usage text and messages, change maybeUseDesktopLogin to call the refactored configure flow, and clear DesktopDataDir on normal Matrix/email logins. Add isLoginCommand helper to avoid loading desktop creds during login flows. --- cmd/bbctl/authconfig.go | 31 +++++++++++--- cmd/bbctl/desktopauth.go | 89 ++++++++++++++++++++++++++++------------ cmd/bbctl/login-email.go | 7 ++-- cmd/bbctl/main.go | 15 +++++++ 4 files changed, 106 insertions(+), 36 deletions(-) diff --git a/cmd/bbctl/authconfig.go b/cmd/bbctl/authconfig.go index 982331c..6e45cf7 100644 --- a/cmd/bbctl/authconfig.go +++ b/cmd/bbctl/authconfig.go @@ -24,17 +24,22 @@ var envs = map[string]string{ } type EnvConfig struct { - ClusterID string `json:"cluster_id"` - Username string `json:"username"` - AccessToken string `json:"access_token"` - BridgeDataDir string `json:"bridge_data_dir"` - DatabaseDir string `json:"database_dir,omitempty"` + ClusterID string `json:"cluster_id"` + Username string `json:"username"` + AccessToken string `json:"access_token,omitempty"` + BridgeDataDir string `json:"bridge_data_dir"` + DatabaseDir string `json:"database_dir,omitempty"` + DesktopDataDir string `json:"desktop_data_dir,omitempty"` } func (ec *EnvConfig) HasCredentials() bool { return strings.HasPrefix(ec.AccessToken, "syt_") || strings.HasPrefix(ec.AccessToken, "bat_") } +func (ec *EnvConfig) UsesDesktopLogin() bool { + return ec.DesktopDataDir != "" +} + type EnvConfigs map[string]*EnvConfig func (ec EnvConfigs) Get(env string) *EnvConfig { @@ -157,7 +162,21 @@ func (cfg *Config) Save() error { if err != nil { return fmt.Errorf("failed to open config at %s for writing: %v", cfg.Path, err) } - err = json.NewEncoder(file).Encode(cfg) + saveCfg := *cfg + if cfg.Environments != nil { + saveCfg.Environments = make(EnvConfigs, len(cfg.Environments)) + for key, env := range cfg.Environments { + if env == nil { + continue + } + envCopy := *env + if envCopy.UsesDesktopLogin() { + envCopy.AccessToken = "" + } + saveCfg.Environments[key] = &envCopy + } + } + err = json.NewEncoder(file).Encode(&saveCfg) if err != nil { return fmt.Errorf("failed to write config to %s: %v", cfg.Path, err) } diff --git a/cmd/bbctl/desktopauth.go b/cmd/bbctl/desktopauth.go index 6caf250..c6087c7 100644 --- a/cmd/bbctl/desktopauth.go +++ b/cmd/bbctl/desktopauth.go @@ -22,7 +22,7 @@ import ( var loginDesktopCommand = &cli.Command{ Name: "login-desktop", - Usage: "Import Beeper Desktop's Matrix credentials into bbctl config", + Usage: "Use Beeper Desktop's credentials for bbctl", Action: loginDesktop, Flags: desktopLoginFlags(), } @@ -37,12 +37,7 @@ func desktopLoginFlags() []cli.Flag { &cli.StringFlag{ Name: "desktop-data-dir", EnvVars: []string{"BBCTL_DESKTOP_DATA_DIR"}, - Usage: "Read Matrix credentials from this Beeper Desktop user data directory", - }, - &cli.StringFlag{ - Name: "desktop-account-db", - EnvVars: []string{"BBCTL_DESKTOP_ACCOUNT_DB"}, - Usage: "Read Matrix credentials from this Beeper Desktop account.db", + Usage: "Read credentials from this Beeper Desktop user data directory", }, } } @@ -54,14 +49,15 @@ type DesktopAccount struct { Homeserver string } -func getDesktopAccountDBPath(ctx *cli.Context) (string, bool) { - if dbPath := ctx.String("desktop-account-db"); dbPath != "" { - return dbPath, true - } +func getDesktopDataDir(ctx *cli.Context) (string, bool, error) { if dataDir := ctx.String("desktop-data-dir"); dataDir != "" { - return filepath.Join(dataDir, "account.db"), true + return dataDir, true, nil + } + dataDir, err := resolveDesktopDataDir(ctx.String("profile")) + if err != nil { + return "", false, err } - return "", false + return dataDir, false, nil } func resolveDesktopDataDir(profile string) (string, error) { @@ -99,17 +95,14 @@ func resolveDesktopDataDir(profile string) (string, error) { } func getLoginDesktopAccountDBPath(ctx *cli.Context) (string, error) { - if dbPath, ok := getDesktopAccountDBPath(ctx); ok { - return dbPath, nil - } - dataDir, err := resolveDesktopDataDir(ctx.String("profile")) + dataDir, _, err := getDesktopDataDir(ctx) if err != nil { return "", fmt.Errorf("failed to resolve desktop data directory: %w", err) } return filepath.Join(dataDir, "account.db"), nil } -func readDesktopAccount(ctx context.Context, dbPath string) (*DesktopAccount, error) { +func readDesktopAccount(ctx context.Context, dbPath string) (account *DesktopAccount, err error) { dbURI := (&url.URL{ Scheme: "file", Path: dbPath, @@ -119,19 +112,27 @@ func readDesktopAccount(ctx context.Context, dbPath string) (*DesktopAccount, er if err != nil { return nil, fmt.Errorf("failed to open desktop account database: %w", err) } - defer db.Close() + defer func() { + if closeErr := db.Close(); closeErr != nil { + if err != nil { + err = fmt.Errorf("%w; failed to close desktop account database: %v", err, closeErr) + } else { + err = fmt.Errorf("failed to close desktop account database: %w", closeErr) + } + } + }() - var account DesktopAccount + var desktopAccount DesktopAccount err = db.QueryRow(ctx, "SELECT user_id, device_id, access_token, homeserver FROM account LIMIT 1"). - Scan(&account.UserID, &account.DeviceID, &account.AccessToken, &account.Homeserver) + Scan(&desktopAccount.UserID, &desktopAccount.DeviceID, &desktopAccount.AccessToken, &desktopAccount.Homeserver) if errors.Is(err, sql.ErrNoRows) { return nil, fmt.Errorf("desktop account database has no logged-in account") } else if err != nil { return nil, fmt.Errorf("failed to read desktop account database: %w", err) - } else if account.UserID == "" || account.AccessToken == "" { + } else if desktopAccount.UserID == "" || desktopAccount.AccessToken == "" { return nil, fmt.Errorf("desktop account database has incomplete credentials") } - return &account, nil + return &desktopAccount, nil } func desktopAccountHomeserverDomain(account *DesktopAccount) (string, error) { @@ -154,7 +155,7 @@ func envForHomeserverDomain(domain string) string { return "" } -func saveDesktopLogin(ctx *cli.Context, account *DesktopAccount) (string, string, error) { +func configureDesktopLogin(ctx *cli.Context, account *DesktopAccount) (string, string, error) { homeserver, err := desktopAccountHomeserverDomain(account) if err != nil { return "", "", err @@ -176,8 +177,13 @@ func saveDesktopLogin(ctx *cli.Context, account *DesktopAccount) (string, string envCfg := cfg.Environments.Get(env) envCfg.ClusterID = whoami.UserInfo.BridgeClusterID envCfg.Username = whoami.UserInfo.Username - envCfg.AccessToken = account.AccessToken + envCfg.AccessToken = "" envCfg.BridgeDataDir = filepath.Join(UserDataDir, "bbctl", env) + dataDir, _, err := getDesktopDataDir(ctx) + if err != nil { + return "", "", fmt.Errorf("failed to resolve desktop data directory: %w", err) + } + envCfg.DesktopDataDir = dataDir err = cfg.Save() if err != nil { return "", "", fmt.Errorf("failed to save config: %w", err) @@ -186,6 +192,35 @@ func saveDesktopLogin(ctx *cli.Context, account *DesktopAccount) (string, string return env, homeserver, nil } +func loadDesktopLogin(ctx *cli.Context, envConfig *EnvConfig) error { + if envConfig.DesktopDataDir == "" { + return nil + } + dbPath := filepath.Join(envConfig.DesktopDataDir, "account.db") + account, err := readDesktopAccount(ctx.Context, dbPath) + if err != nil { + return err + } + homeserver, err := desktopAccountHomeserverDomain(account) + if err != nil { + return err + } + if homeserver == "" { + homeserver = ctx.String("homeserver") + } + whoami, err := beeperapi.Whoami(homeserver, account.AccessToken) + if err != nil { + return fmt.Errorf("failed to verify desktop credentials with whoami: %w", err) + } + envConfig.ClusterID = whoami.UserInfo.BridgeClusterID + envConfig.Username = whoami.UserInfo.Username + envConfig.AccessToken = account.AccessToken + if envConfig.BridgeDataDir == "" { + envConfig.BridgeDataDir = filepath.Join(UserDataDir, "bbctl", ctx.String("env")) + } + return nil +} + func loginDesktop(ctx *cli.Context) error { dbPath, err := getLoginDesktopAccountDBPath(ctx) if err != nil { @@ -197,11 +232,11 @@ func loginDesktop(ctx *cli.Context) error { return err } - env, homeserver, err := saveDesktopLogin(ctx, account) + env, homeserver, err := configureDesktopLogin(ctx, account) if err != nil { return err } - fmt.Printf("Imported Desktop login for %s into bbctl env %q (%s)\n", account.UserID, env, homeserver) + fmt.Printf("Using Beeper Desktop login for %s in bbctl env %q (%s)\n", account.UserID, env, homeserver) return nil } diff --git a/cmd/bbctl/login-email.go b/cmd/bbctl/login-email.go index 6bbee79..6c439bd 100644 --- a/cmd/bbctl/login-email.go +++ b/cmd/bbctl/login-email.go @@ -46,7 +46,7 @@ func maybeUseDesktopLogin(ctx *cli.Context) (bool, error) { } account, err := readDesktopAccount(ctx.Context, dbPath) if err != nil { - if ctx.IsSet("desktop-account-db") || ctx.IsSet("desktop-data-dir") { + if ctx.IsSet("desktop-data-dir") { return false, err } return false, nil @@ -64,11 +64,11 @@ func maybeUseDesktopLogin(ctx *cli.Context) (bool, error) { return false, nil } - env, homeserver, err := saveDesktopLogin(ctx, account) + env, homeserver, err := configureDesktopLogin(ctx, account) if err != nil { return false, err } - fmt.Printf("Imported Desktop login for %s into bbctl env %q (%s)\n", account.UserID, env, homeserver) + fmt.Printf("Using Beeper Desktop login for %s in bbctl env %q (%s)\n", account.UserID, env, homeserver) return true, nil } @@ -147,6 +147,7 @@ func doMatrixLogin(ctx *cli.Context, req *mautrix.ReqLogin, whoami *beeperapi.Re envCfg.ClusterID = whoami.UserInfo.BridgeClusterID envCfg.Username = whoami.UserInfo.Username envCfg.AccessToken = resp.AccessToken + envCfg.DesktopDataDir = "" envCfg.BridgeDataDir = filepath.Join(UserDataDir, "bbctl", ctx.String("env")) err = cfg.Save() if err != nil { diff --git a/cmd/bbctl/main.go b/cmd/bbctl/main.go index e127b7c..ce3a7a2 100644 --- a/cmd/bbctl/main.go +++ b/cmd/bbctl/main.go @@ -81,6 +81,12 @@ func prepareApp(ctx *cli.Context) error { envConfig := cfg.Environments.Get(env) ctx.Context = context.WithValue(ctx.Context, contextKeyConfig, cfg) ctx.Context = context.WithValue(ctx.Context, contextKeyEnvConfig, envConfig) + if envConfig.UsesDesktopLogin() && !isLoginCommand(ctx) { + err = loadDesktopLogin(ctx, envConfig) + if err != nil { + return fmt.Errorf("failed to use Beeper Desktop login: %w", err) + } + } if envConfig.HasCredentials() { if envConfig.Username == "" { log.Printf("Fetching whoami to fill missing env config details") @@ -95,6 +101,15 @@ func prepareApp(ctx *cli.Context) error { return nil } +func isLoginCommand(ctx *cli.Context) bool { + switch ctx.Args().First() { + case "login", "l", "login-desktop", "login-password", "p": + return true + default: + return false + } +} + var app = &cli.App{ Name: "bbctl", Usage: "Manage self-hosted bridges for Beeper", From d2167a381a8827d8709a5a672d58db338b36ea3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sun, 3 May 2026 20:19:51 +0200 Subject: [PATCH 03/11] Refactor desktop login handling and data dir Simplify and clean up Beeper Desktop login integration: remove DeviceID usage and stop clearing desktop access tokens when saving config. Unify desktop data directory handling by changing getDesktopDataDir to return a single dataDir, and update callers to pass dataDir through to readDesktopAccount (which now reads account.db from the data dir) and configureDesktopLogin. Update SQL query to omit device_id. Extract whoami verification into applyDesktopWhoami and call it only when cluster/username metadata is missing; configureDesktopLogin now sets BridgeDataDir and DesktopDataDir and saves the config. Improve error messages when resolving the desktop data dir. Also: append desktop flags to the login command inline, and make isLoginCommand detect login commands via Command.HasName(). --- cmd/bbctl/authconfig.go | 3 --- cmd/bbctl/desktopauth.go | 2 +- cmd/bbctl/login-email.go | 32 ++++++++++++-------------------- 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/cmd/bbctl/authconfig.go b/cmd/bbctl/authconfig.go index 6e45cf7..2023cea 100644 --- a/cmd/bbctl/authconfig.go +++ b/cmd/bbctl/authconfig.go @@ -170,9 +170,6 @@ func (cfg *Config) Save() error { continue } envCopy := *env - if envCopy.UsesDesktopLogin() { - envCopy.AccessToken = "" - } saveCfg.Environments[key] = &envCopy } } diff --git a/cmd/bbctl/desktopauth.go b/cmd/bbctl/desktopauth.go index c6087c7..275a2e6 100644 --- a/cmd/bbctl/desktopauth.go +++ b/cmd/bbctl/desktopauth.go @@ -177,7 +177,7 @@ func configureDesktopLogin(ctx *cli.Context, account *DesktopAccount) (string, s envCfg := cfg.Environments.Get(env) envCfg.ClusterID = whoami.UserInfo.BridgeClusterID envCfg.Username = whoami.UserInfo.Username - envCfg.AccessToken = "" + envCfg.AccessToken = account.AccessToken envCfg.BridgeDataDir = filepath.Join(UserDataDir, "bbctl", env) dataDir, _, err := getDesktopDataDir(ctx) if err != nil { diff --git a/cmd/bbctl/login-email.go b/cmd/bbctl/login-email.go index 6c439bd..ed56a8f 100644 --- a/cmd/bbctl/login-email.go +++ b/cmd/bbctl/login-email.go @@ -11,40 +11,40 @@ import ( "maunium.net/go/mautrix" "github.com/beeper/bridge-manager/api/beeperapi" + "github.com/beeper/bridge-manager/cli/interactive" ) var loginCommand = &cli.Command{ Name: "login", Aliases: []string{"l"}, Usage: "Log into the Beeper server", + Before: interactive.Ask, Action: beeperLogin, - Flags: []cli.Flag{ - &cli.StringFlag{ + Flags: append([]cli.Flag{ + interactive.Flag{Flag: &cli.StringFlag{ Name: "email", EnvVars: []string{"BEEPER_EMAIL"}, Usage: "The Beeper account email to log in with", - }, + }, Survey: &survey.Input{ + Message: "Email:", + }}, &cli.BoolFlag{ Name: "no-desktop", EnvVars: []string{"BBCTL_NO_DESKTOP_LOGIN"}, Usage: "Skip checking for an existing Beeper Desktop login", }, - }, -} - -func init() { - loginCommand.Flags = append(loginCommand.Flags, desktopLoginFlags()...) + }, desktopLoginFlags()...), } func maybeUseDesktopLogin(ctx *cli.Context) (bool, error) { if ctx.Bool("no-desktop") { return false, nil } - dbPath, err := getLoginDesktopAccountDBPath(ctx) + dataDir, err := getDesktopDataDir(ctx) if err != nil { - return false, err + return false, fmt.Errorf("failed to resolve desktop data directory: %w", err) } - account, err := readDesktopAccount(ctx.Context, dbPath) + account, err := readDesktopAccount(ctx.Context, dataDir) if err != nil { if ctx.IsSet("desktop-data-dir") { return false, err @@ -64,7 +64,7 @@ func maybeUseDesktopLogin(ctx *cli.Context) (bool, error) { return false, nil } - env, homeserver, err := configureDesktopLogin(ctx, account) + env, homeserver, err := configureDesktopLogin(ctx, account, dataDir) if err != nil { return false, err } @@ -82,14 +82,6 @@ func beeperLogin(ctx *cli.Context) error { homeserver := ctx.String("homeserver") email := ctx.String("email") - if email == "" { - err = survey.AskOne(&survey.Input{ - Message: "Email:", - }, &email) - if err != nil { - return err - } - } startLogin, err := beeperapi.StartLogin(homeserver) if err != nil { From 3fb98b2c26e552178dcf3a177d1076c3348cc2ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sun, 3 May 2026 20:20:03 +0200 Subject: [PATCH 04/11] Update login-email.go --- cmd/bbctl/login-email.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/cmd/bbctl/login-email.go b/cmd/bbctl/login-email.go index ed56a8f..f7ce5f8 100644 --- a/cmd/bbctl/login-email.go +++ b/cmd/bbctl/login-email.go @@ -20,7 +20,7 @@ var loginCommand = &cli.Command{ Usage: "Log into the Beeper server", Before: interactive.Ask, Action: beeperLogin, - Flags: append([]cli.Flag{ + Flags: []cli.Flag{ interactive.Flag{Flag: &cli.StringFlag{ Name: "email", EnvVars: []string{"BEEPER_EMAIL"}, @@ -33,18 +33,22 @@ var loginCommand = &cli.Command{ EnvVars: []string{"BBCTL_NO_DESKTOP_LOGIN"}, Usage: "Skip checking for an existing Beeper Desktop login", }, - }, desktopLoginFlags()...), + }, +} + +func init() { + loginCommand.Flags = append(loginCommand.Flags, desktopLoginFlags()...) } func maybeUseDesktopLogin(ctx *cli.Context) (bool, error) { if ctx.Bool("no-desktop") { return false, nil } - dataDir, err := getDesktopDataDir(ctx) + dbPath, err := getLoginDesktopAccountDBPath(ctx) if err != nil { - return false, fmt.Errorf("failed to resolve desktop data directory: %w", err) + return false, err } - account, err := readDesktopAccount(ctx.Context, dataDir) + account, err := readDesktopAccount(ctx.Context, dbPath) if err != nil { if ctx.IsSet("desktop-data-dir") { return false, err @@ -64,7 +68,7 @@ func maybeUseDesktopLogin(ctx *cli.Context) (bool, error) { return false, nil } - env, homeserver, err := configureDesktopLogin(ctx, account, dataDir) + env, homeserver, err := configureDesktopLogin(ctx, account) if err != nil { return false, err } From e86056927a70051d508669b127fef73f42bc08ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sun, 3 May 2026 20:20:41 +0200 Subject: [PATCH 05/11] Update authconfig.go --- cmd/bbctl/authconfig.go | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/cmd/bbctl/authconfig.go b/cmd/bbctl/authconfig.go index 2023cea..ed4e73e 100644 --- a/cmd/bbctl/authconfig.go +++ b/cmd/bbctl/authconfig.go @@ -26,7 +26,7 @@ var envs = map[string]string{ type EnvConfig struct { ClusterID string `json:"cluster_id"` Username string `json:"username"` - AccessToken string `json:"access_token,omitempty"` + AccessToken string `json:"access_token"` BridgeDataDir string `json:"bridge_data_dir"` DatabaseDir string `json:"database_dir,omitempty"` DesktopDataDir string `json:"desktop_data_dir,omitempty"` @@ -162,18 +162,7 @@ func (cfg *Config) Save() error { if err != nil { return fmt.Errorf("failed to open config at %s for writing: %v", cfg.Path, err) } - saveCfg := *cfg - if cfg.Environments != nil { - saveCfg.Environments = make(EnvConfigs, len(cfg.Environments)) - for key, env := range cfg.Environments { - if env == nil { - continue - } - envCopy := *env - saveCfg.Environments[key] = &envCopy - } - } - err = json.NewEncoder(file).Encode(&saveCfg) + err = json.NewEncoder(file).Encode(cfg) if err != nil { return fmt.Errorf("failed to write config to %s: %v", cfg.Path, err) } From e7f774a79e3c6077e0e26973c9961e142668bc1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sun, 3 May 2026 20:24:27 +0200 Subject: [PATCH 06/11] Update desktopauth.go --- cmd/bbctl/desktopauth.go | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/cmd/bbctl/desktopauth.go b/cmd/bbctl/desktopauth.go index 275a2e6..6a54bb9 100644 --- a/cmd/bbctl/desktopauth.go +++ b/cmd/bbctl/desktopauth.go @@ -13,7 +13,6 @@ import ( "github.com/urfave/cli/v2" "go.mau.fi/util/dbutil" - "maunium.net/go/mautrix/id" "github.com/beeper/bridge-manager/api/beeperapi" @@ -43,21 +42,16 @@ func desktopLoginFlags() []cli.Flag { } type DesktopAccount struct { - UserID id.UserID - DeviceID id.DeviceID + UserID string AccessToken string Homeserver string } -func getDesktopDataDir(ctx *cli.Context) (string, bool, error) { +func getDesktopDataDir(ctx *cli.Context) (string, error) { if dataDir := ctx.String("desktop-data-dir"); dataDir != "" { - return dataDir, true, nil + return dataDir, nil } - dataDir, err := resolveDesktopDataDir(ctx.String("profile")) - if err != nil { - return "", false, err - } - return dataDir, false, nil + return resolveDesktopDataDir(ctx.String("profile")) } func resolveDesktopDataDir(profile string) (string, error) { @@ -95,7 +89,7 @@ func resolveDesktopDataDir(profile string) (string, error) { } func getLoginDesktopAccountDBPath(ctx *cli.Context) (string, error) { - dataDir, _, err := getDesktopDataDir(ctx) + dataDir, err := getDesktopDataDir(ctx) if err != nil { return "", fmt.Errorf("failed to resolve desktop data directory: %w", err) } @@ -123,8 +117,8 @@ func readDesktopAccount(ctx context.Context, dbPath string) (account *DesktopAcc }() var desktopAccount DesktopAccount - err = db.QueryRow(ctx, "SELECT user_id, device_id, access_token, homeserver FROM account LIMIT 1"). - Scan(&desktopAccount.UserID, &desktopAccount.DeviceID, &desktopAccount.AccessToken, &desktopAccount.Homeserver) + err = db.QueryRow(ctx, "SELECT user_id, access_token, homeserver FROM account LIMIT 1"). + Scan(&desktopAccount.UserID, &desktopAccount.AccessToken, &desktopAccount.Homeserver) if errors.Is(err, sql.ErrNoRows) { return nil, fmt.Errorf("desktop account database has no logged-in account") } else if err != nil { @@ -179,7 +173,7 @@ func configureDesktopLogin(ctx *cli.Context, account *DesktopAccount) (string, s envCfg.Username = whoami.UserInfo.Username envCfg.AccessToken = account.AccessToken envCfg.BridgeDataDir = filepath.Join(UserDataDir, "bbctl", env) - dataDir, _, err := getDesktopDataDir(ctx) + dataDir, err := getDesktopDataDir(ctx) if err != nil { return "", "", fmt.Errorf("failed to resolve desktop data directory: %w", err) } From b2ef08dc4edf10712cdb12499c6dd5173f001125 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sun, 3 May 2026 20:25:20 +0200 Subject: [PATCH 07/11] Update login-email.go --- cmd/bbctl/login-email.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/cmd/bbctl/login-email.go b/cmd/bbctl/login-email.go index f7ce5f8..6c439bd 100644 --- a/cmd/bbctl/login-email.go +++ b/cmd/bbctl/login-email.go @@ -11,23 +11,19 @@ import ( "maunium.net/go/mautrix" "github.com/beeper/bridge-manager/api/beeperapi" - "github.com/beeper/bridge-manager/cli/interactive" ) var loginCommand = &cli.Command{ Name: "login", Aliases: []string{"l"}, Usage: "Log into the Beeper server", - Before: interactive.Ask, Action: beeperLogin, Flags: []cli.Flag{ - interactive.Flag{Flag: &cli.StringFlag{ + &cli.StringFlag{ Name: "email", EnvVars: []string{"BEEPER_EMAIL"}, Usage: "The Beeper account email to log in with", - }, Survey: &survey.Input{ - Message: "Email:", - }}, + }, &cli.BoolFlag{ Name: "no-desktop", EnvVars: []string{"BBCTL_NO_DESKTOP_LOGIN"}, @@ -86,6 +82,14 @@ func beeperLogin(ctx *cli.Context) error { homeserver := ctx.String("homeserver") email := ctx.String("email") + if email == "" { + err = survey.AskOne(&survey.Input{ + Message: "Email:", + }, &email) + if err != nil { + return err + } + } startLogin, err := beeperapi.StartLogin(homeserver) if err != nil { From 0e66b49451a15b1845309fe99edb6301c596118c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Sun, 3 May 2026 20:42:26 +0200 Subject: [PATCH 08/11] Skip Matrix logout for desktop logins Check the environment config and, when UsesDesktopLogin is true, avoid calling the Matrix Logout API so the Beeper Desktop session remains active. The environment is still removed from the CLI config and saved (with an inline error check). Print a specific message indicating the desktop session was not affected; otherwise keep the original success message. --- cmd/bbctl/logout.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/cmd/bbctl/logout.go b/cmd/bbctl/logout.go index 7ec88b3..aa2b0c0 100644 --- a/cmd/bbctl/logout.go +++ b/cmd/bbctl/logout.go @@ -22,16 +22,22 @@ var logoutCommand = &cli.Command{ } func beeperLogout(ctx *cli.Context) error { - _, err := GetMatrixClient(ctx).Logout(ctx.Context) - if err != nil && !ctx.Bool("force") { - return fmt.Errorf("error logging out: %w", err) + envCfg := GetEnvConfig(ctx) + if !envCfg.UsesDesktopLogin() { + _, err := GetMatrixClient(ctx).Logout(ctx.Context) + if err != nil && !ctx.Bool("force") { + return fmt.Errorf("error logging out: %w", err) + } } cfg := GetConfig(ctx) delete(cfg.Environments, ctx.String("env")) - err = cfg.Save() - if err != nil { + if err := cfg.Save(); err != nil { return fmt.Errorf("error saving config: %w", err) } + if envCfg.UsesDesktopLogin() { + fmt.Println("Logged out of bbctl successfully. Your Beeper Desktop session was not affected.") + return nil + } fmt.Println("Logged out successfully") return nil } From 7227de4cedec6d4a16ef93679314080b13c18026 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Mon, 4 May 2026 15:59:50 +0200 Subject: [PATCH 09/11] Update cmd/bbctl/desktopauth.go Co-authored-by: Tulir Asokan --- cmd/bbctl/desktopauth.go | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/cmd/bbctl/desktopauth.go b/cmd/bbctl/desktopauth.go index 6a54bb9..5b5ae05 100644 --- a/cmd/bbctl/desktopauth.go +++ b/cmd/bbctl/desktopauth.go @@ -59,33 +59,11 @@ func resolveDesktopDataDir(profile string) (string, error) { if profile != "" { appName += "-" + profile } - switch runtime.GOOS { - case "darwin": - home, err := os.UserHomeDir() - if err != nil { - return "", err - } - return filepath.Join(home, "Library", "Application Support", appName), nil - case "windows": - if appData := os.Getenv("APPDATA"); appData != "" { - return filepath.Join(appData, appName), nil - } - home, err := os.UserHomeDir() - if err != nil { - return "", err - } - return filepath.Join(home, appName), nil - default: - configHome := os.Getenv("XDG_CONFIG_HOME") - if configHome == "" { - home, err := os.UserHomeDir() - if err != nil { - return "", err - } - configHome = filepath.Join(home, ".config") - } - return filepath.Join(configHome, appName), nil + dataDir, err := os.UserConfigDir() + if err != nil { + return "", err } + return filepath.Join(dataDir, appName), nil } func getLoginDesktopAccountDBPath(ctx *cli.Context) (string, error) { From 76a0cb79fcd4e08f5bfb3b7af90fa532ad2fc302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Mon, 4 May 2026 16:07:41 +0200 Subject: [PATCH 10/11] Remove login-desktop command and refine check Delete the login-desktop CLI command and its handler, and stop registering it with the app. Update prepareApp to use a new isRecoveryCommand check (renamed from isLoginCommand) so desktop credentials are skipped for recovery-related commands; adjust the command list so "login-desktop" is no longer treated as a login case and add "logout" to the recovery set. Kept desktopLoginFlags and loadDesktopLogin for other flows. --- cmd/bbctl/desktopauth.go | 28 ---------------------------- cmd/bbctl/main.go | 7 +++---- 2 files changed, 3 insertions(+), 32 deletions(-) diff --git a/cmd/bbctl/desktopauth.go b/cmd/bbctl/desktopauth.go index 5b5ae05..887ff35 100644 --- a/cmd/bbctl/desktopauth.go +++ b/cmd/bbctl/desktopauth.go @@ -8,7 +8,6 @@ import ( "net/url" "os" "path/filepath" - "runtime" "strings" "github.com/urfave/cli/v2" @@ -19,13 +18,6 @@ import ( _ "go.mau.fi/util/dbutil/litestream" ) -var loginDesktopCommand = &cli.Command{ - Name: "login-desktop", - Usage: "Use Beeper Desktop's credentials for bbctl", - Action: loginDesktop, - Flags: desktopLoginFlags(), -} - func desktopLoginFlags() []cli.Flag { return []cli.Flag{ &cli.StringFlag{ @@ -192,23 +184,3 @@ func loadDesktopLogin(ctx *cli.Context, envConfig *EnvConfig) error { } return nil } - -func loginDesktop(ctx *cli.Context) error { - dbPath, err := getLoginDesktopAccountDBPath(ctx) - if err != nil { - return err - } - - account, err := readDesktopAccount(ctx.Context, dbPath) - if err != nil { - return err - } - - env, homeserver, err := configureDesktopLogin(ctx, account) - if err != nil { - return err - } - - fmt.Printf("Using Beeper Desktop login for %s in bbctl env %q (%s)\n", account.UserID, env, homeserver) - return nil -} diff --git a/cmd/bbctl/main.go b/cmd/bbctl/main.go index ce3a7a2..879ebf8 100644 --- a/cmd/bbctl/main.go +++ b/cmd/bbctl/main.go @@ -81,7 +81,7 @@ func prepareApp(ctx *cli.Context) error { envConfig := cfg.Environments.Get(env) ctx.Context = context.WithValue(ctx.Context, contextKeyConfig, cfg) ctx.Context = context.WithValue(ctx.Context, contextKeyEnvConfig, envConfig) - if envConfig.UsesDesktopLogin() && !isLoginCommand(ctx) { + if envConfig.UsesDesktopLogin() && !isRecoveryCommand(ctx) { err = loadDesktopLogin(ctx, envConfig) if err != nil { return fmt.Errorf("failed to use Beeper Desktop login: %w", err) @@ -101,9 +101,9 @@ func prepareApp(ctx *cli.Context) error { return nil } -func isLoginCommand(ctx *cli.Context) bool { +func isRecoveryCommand(ctx *cli.Context) bool { switch ctx.Args().First() { - case "login", "l", "login-desktop", "login-password", "p": + case "login", "l", "login-password", "p", "logout": return true default: return false @@ -155,7 +155,6 @@ var app = &cli.App{ Before: prepareApp, Commands: []*cli.Command{ loginCommand, - loginDesktopCommand, loginPasswordCommand, logoutCommand, registerCommand, From 4cb3037f04672b5f6df308c42a1b40a9e81773fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?batuhan=20i=C3=A7=C3=B6z?= Date: Mon, 4 May 2026 16:42:36 +0200 Subject: [PATCH 11/11] Normalize SQLite file URI paths for Windows --- cmd/bbctl/desktopauth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/bbctl/desktopauth.go b/cmd/bbctl/desktopauth.go index 887ff35..73a18b1 100644 --- a/cmd/bbctl/desktopauth.go +++ b/cmd/bbctl/desktopauth.go @@ -69,7 +69,7 @@ func getLoginDesktopAccountDBPath(ctx *cli.Context) (string, error) { func readDesktopAccount(ctx context.Context, dbPath string) (account *DesktopAccount, err error) { dbURI := (&url.URL{ Scheme: "file", - Path: dbPath, + Path: filepath.ToSlash(dbPath), RawQuery: "mode=ro", }).String() db, err := dbutil.NewWithDialect(dbURI, "sqlite3-fk-wal")