From d07dbf0fd51d0cef6dc7a2549c7fd949bd38c074 Mon Sep 17 00:00:00 2001 From: Wes McKinney Date: Mon, 2 Feb 2026 11:17:30 -0600 Subject: [PATCH] Show helpful OAuth setup instructions when client_secrets is missing Fixes #1. Replace the cryptic "oauth.client_secrets not configured in config.toml" error with step-by-step setup instructions and a link to the guide. Also make the OAuth prerequisite more prominent in the README Quick Start section. Co-Authored-By: Claude Opus 4.5 --- README.md | 6 +++--- cmd/msgvault/cmd/addaccount.go | 4 ++-- cmd/msgvault/cmd/deletions.go | 4 ++-- cmd/msgvault/cmd/root.go | 24 ++++++++++++++++++++++++ cmd/msgvault/cmd/sync.go | 4 ++-- cmd/msgvault/cmd/syncfull.go | 4 ++-- cmd/msgvault/cmd/verify.go | 4 ++-- 7 files changed, 37 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index d71a127f..e396291a 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,9 @@ make install ## Quick Start +> **Prerequisites:** You need a Google Cloud OAuth credential before adding an account. +> Follow the **[OAuth Setup Guide](https://msgvault.io/guides/oauth-setup/)** to create one (~5 minutes). + ```bash msgvault init-db msgvault add-account you@gmail.com # opens browser for OAuth @@ -53,9 +56,6 @@ msgvault sync-full you@gmail.com --limit 100 msgvault tui ``` -OAuth requires a Google Cloud project with the Gmail API enabled. -See the **[Setup Guide](https://msgvault.io/guides/oauth-setup/)** for step-by-step instructions. - ## Commands | Command | Description | diff --git a/cmd/msgvault/cmd/addaccount.go b/cmd/msgvault/cmd/addaccount.go index 64e15e33..5f72d0cb 100644 --- a/cmd/msgvault/cmd/addaccount.go +++ b/cmd/msgvault/cmd/addaccount.go @@ -29,7 +29,7 @@ Example: // Validate config if cfg.OAuth.ClientSecrets == "" { - return fmt.Errorf("oauth.client_secrets not configured in config.toml") + return errOAuthNotConfigured() } // Initialize database (in case it's new) @@ -47,7 +47,7 @@ Example: // Create OAuth manager oauthMgr, err := oauth.NewManager(cfg.OAuth.ClientSecrets, cfg.TokensDir(), logger) if err != nil { - return fmt.Errorf("create oauth manager: %w", err) + return wrapOAuthError(fmt.Errorf("create oauth manager: %w", err)) } // Check if already authorized diff --git a/cmd/msgvault/cmd/deletions.go b/cmd/msgvault/cmd/deletions.go index 9c39213b..5a63f996 100644 --- a/cmd/msgvault/cmd/deletions.go +++ b/cmd/msgvault/cmd/deletions.go @@ -212,7 +212,7 @@ Examples: // Validate config if cfg.OAuth.ClientSecrets == "" { - return fmt.Errorf("oauth.client_secrets not configured in config.toml") + return errOAuthNotConfigured() } // Collect unique accounts from manifests @@ -280,7 +280,7 @@ Examples: // Create OAuth manager with appropriate scopes oauthMgr, err := oauth.NewManagerWithScopes(cfg.OAuth.ClientSecrets, cfg.TokensDir(), logger, scopes) if err != nil { - return fmt.Errorf("create oauth manager: %w", err) + return wrapOAuthError(fmt.Errorf("create oauth manager: %w", err)) } tokenSource, err := oauthMgr.TokenSource(ctx, account) diff --git a/cmd/msgvault/cmd/root.go b/cmd/msgvault/cmd/root.go index 4b62eec5..54355d35 100644 --- a/cmd/msgvault/cmd/root.go +++ b/cmd/msgvault/cmd/root.go @@ -1,6 +1,7 @@ package cmd import ( + "errors" "fmt" "log/slog" "os" @@ -54,6 +55,29 @@ func Execute() error { return rootCmd.Execute() } +// oauthSetupHint is the common help text for OAuth configuration issues. +const oauthSetupHint = ` +To use msgvault, you need a Google Cloud OAuth credential: + 1. Follow the setup guide: https://msgvault.io/guides/oauth-setup/ + 2. Download the client_secret.json file + 3. Add to your config.toml: + [oauth] + client_secrets = "/path/to/client_secret.json"` + +// errOAuthNotConfigured returns a helpful error when OAuth client secrets are missing. +func errOAuthNotConfigured() error { + return fmt.Errorf("OAuth client secrets not configured." + oauthSetupHint) +} + +// wrapOAuthError wraps an oauth/client-secrets error with setup instructions +// if the root cause is a missing or unreadable secrets file. +func wrapOAuthError(err error) error { + if errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("OAuth client secrets file not found." + oauthSetupHint) + } + return err +} + func init() { rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default: ~/.msgvault/config.toml)") rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output") diff --git a/cmd/msgvault/cmd/sync.go b/cmd/msgvault/cmd/sync.go index 5d8d20f8..6f1be22b 100644 --- a/cmd/msgvault/cmd/sync.go +++ b/cmd/msgvault/cmd/sync.go @@ -35,7 +35,7 @@ Examples: // Validate config if cfg.OAuth.ClientSecrets == "" { - return fmt.Errorf("oauth.client_secrets not configured in config.toml") + return errOAuthNotConfigured() } // Open database @@ -65,7 +65,7 @@ Examples: // Create OAuth manager and get token source oauthMgr, err := oauth.NewManager(cfg.OAuth.ClientSecrets, cfg.TokensDir(), logger) if err != nil { - return fmt.Errorf("create oauth manager: %w", err) + return wrapOAuthError(fmt.Errorf("create oauth manager: %w", err)) } // Set up context with cancellation diff --git a/cmd/msgvault/cmd/syncfull.go b/cmd/msgvault/cmd/syncfull.go index 164168ce..7fd275b8 100644 --- a/cmd/msgvault/cmd/syncfull.go +++ b/cmd/msgvault/cmd/syncfull.go @@ -46,7 +46,7 @@ Examples: // Validate config if cfg.OAuth.ClientSecrets == "" { - return fmt.Errorf("oauth.client_secrets not configured in config.toml") + return errOAuthNotConfigured() } // Open database @@ -64,7 +64,7 @@ Examples: // Create OAuth manager and get token source oauthMgr, err := oauth.NewManager(cfg.OAuth.ClientSecrets, cfg.TokensDir(), logger) if err != nil { - return fmt.Errorf("create oauth manager: %w", err) + return wrapOAuthError(fmt.Errorf("create oauth manager: %w", err)) } // Set up context with cancellation diff --git a/cmd/msgvault/cmd/verify.go b/cmd/msgvault/cmd/verify.go index baedbb8e..b93602df 100644 --- a/cmd/msgvault/cmd/verify.go +++ b/cmd/msgvault/cmd/verify.go @@ -37,7 +37,7 @@ Examples: // Validate config if cfg.OAuth.ClientSecrets == "" { - return fmt.Errorf("oauth.client_secrets not configured in config.toml") + return errOAuthNotConfigured() } // Open database @@ -51,7 +51,7 @@ Examples: // Create OAuth manager and get token source oauthMgr, err := oauth.NewManager(cfg.OAuth.ClientSecrets, cfg.TokensDir(), logger) if err != nil { - return fmt.Errorf("create oauth manager: %w", err) + return wrapOAuthError(fmt.Errorf("create oauth manager: %w", err)) } // Set up context with cancellation