Repository: mbrt/gmailctl
Pull request #210 (2021-11-06 18:16:44)

Move pkg to internal
Make all implementation details internal. This makes sure people don't start importing gmailctl packages and start depending on them.

To make this possible, the APIProvider interface used by `gmailctl/cmd` doesn't mention `pkg/api` anymore. The end result is much cleaner and should allow additional API providers to be added without too much effort.


Patch:
diff --git a/.golangci.yml b/.golangci.yml
index be095d6..7adf693 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,7 +1,7 @@
 run:
   deadline: "3m"
   skip-dirs:
-    - pkg/graph
+    - internal/graph
 
 linters:
   enable-all: false
diff --git a/cmd/gmailctl-config-migrate/import.go b/cmd/gmailctl-config-migrate/import.go
index 7149850..540d1e8 100644
--- a/cmd/gmailctl-config-migrate/import.go
+++ b/cmd/gmailctl-config-migrate/import.go
@@ -10,7 +10,7 @@ import (
 	"github.com/mbrt/gmailctl/cmd/gmailctl-config-migrate/v1alpha1"
 	"github.com/mbrt/gmailctl/cmd/gmailctl-config-migrate/v1alpha2"
 	"github.com/mbrt/gmailctl/cmd/gmailctl-config-migrate/v1alpha3"
-	"github.com/mbrt/gmailctl/pkg/rimport"
+	"github.com/mbrt/gmailctl/internal/rimport"
 )
 
 const header = `// This file is generated by gmailctl-config-migrate.
diff --git a/cmd/gmailctl-config-migrate/v1alpha1/config.go b/cmd/gmailctl-config-migrate/v1alpha1/config.go
index 845a3fa..dfc30ae 100644
--- a/cmd/gmailctl-config-migrate/v1alpha1/config.go
+++ b/cmd/gmailctl-config-migrate/v1alpha1/config.go
@@ -1,7 +1,7 @@
 package v1alpha1
 
 import (
-	"github.com/mbrt/gmailctl/pkg/gmail"
+	"github.com/mbrt/gmailctl/internal/gmail"
 )
 
 // Version is the config format version supported by this package.
diff --git a/cmd/gmailctl-config-migrate/v1alpha2/config.go b/cmd/gmailctl-config-migrate/v1alpha2/config.go
index 2f1fa02..3420ff7 100644
--- a/cmd/gmailctl-config-migrate/v1alpha2/config.go
+++ b/cmd/gmailctl-config-migrate/v1alpha2/config.go
@@ -6,7 +6,7 @@ import (
 	"strings"
 
 	"github.com/mbrt/gmailctl/cmd/gmailctl-config-migrate/v1alpha1"
-	"github.com/mbrt/gmailctl/pkg/gmail"
+	"github.com/mbrt/gmailctl/internal/gmail"
 )
 
 // Version is the latest supported version.
diff --git a/cmd/gmailctl-config-migrate/v1alpha3/alias.go b/cmd/gmailctl-config-migrate/v1alpha3/alias.go
index 313f43f..cb3ba02 100644
--- a/cmd/gmailctl-config-migrate/v1alpha3/alias.go
+++ b/cmd/gmailctl-config-migrate/v1alpha3/alias.go
@@ -1,6 +1,6 @@
 package v1alpha3
 
-import cfg "github.com/mbrt/gmailctl/pkg/config/v1alpha3"
+import cfg "github.com/mbrt/gmailctl/internal/config/v1alpha3"
 
 // Config is an alias pointing to the actual v1alpha3.Config type.
 type Config = cfg.Config
diff --git a/cmd/gmailctl-config-migrate/v1alpha3/import.go b/cmd/gmailctl-config-migrate/v1alpha3/import.go
index 3f29fa4..e4270dd 100644
--- a/cmd/gmailctl-config-migrate/v1alpha3/import.go
+++ b/cmd/gmailctl-config-migrate/v1alpha3/import.go
@@ -6,7 +6,7 @@ import (
 	"github.com/hashicorp/go-multierror"
 
 	"github.com/mbrt/gmailctl/cmd/gmailctl-config-migrate/v1alpha2"
-	"github.com/mbrt/gmailctl/pkg/config/v1alpha3"
+	"github.com/mbrt/gmailctl/internal/config/v1alpha3"
 )
 
 var dummyFilter = v1alpha3.FilterNode{}
diff --git a/cmd/gmailctl/cmd/api_provider.go b/cmd/gmailctl/cmd/api_provider.go
new file mode 100644
index 0000000..862f524
--- /dev/null
+++ b/cmd/gmailctl/cmd/api_provider.go
@@ -0,0 +1,43 @@
+package cmd
+
+import (
+	"context"
+	"fmt"
+
+	"google.golang.org/api/gmail/v1"
+
+	"github.com/mbrt/gmailctl/internal/api"
+)
+
+// APIProvider is the APIProvider used by all gmailctl commands.
+var APIProvider GmailAPIProvider
+
+// GmailAPIProvider is the integration point between gmailctl commands and GMail
+// APIs providers.
+type GmailAPIProvider interface {
+	// Service returns the GMail API service.
+	Service(ctx context.Context, cfgDir string) (*gmail.Service, error)
+	// ResetConfig cleans up the configuration.
+	ResetConfig(cfgDir string) error
+	// InitConfig initializes the configuration.
+	InitConfig(cfgDir string) error
+}
+
+// APIKeyProvider is the interface implemented by API providers with
+// an API key.
+type APIKeyProvider interface {
+	// APIKey returns the API key used to authenticate with GMail APIs.
+	// Returns an empty string if not available.
+	APIKey() string
+}
+
+func openAPI() (*api.GmailAPI, error) {
+	srv, err := APIProvider.Service(context.Background(), cfgDir)
+	if err != nil {
+		return nil, fmt.Errorf("in Authenticator.Service: %w", err)
+	}
+	if kprov, ok := APIProvider.(APIKeyProvider); ok {
+		return api.NewWithAPIKey(srv, kprov.APIKey()), nil
+	}
+	return api.NewFromService(srv), nil
+}
diff --git a/cmd/gmailctl/cmd/apply_cmd.go b/cmd/gmailctl/cmd/apply_cmd.go
index 0595558..42194b9 100644
--- a/cmd/gmailctl/cmd/apply_cmd.go
+++ b/cmd/gmailctl/cmd/apply_cmd.go
@@ -5,8 +5,8 @@ import (
 
 	"github.com/spf13/cobra"
 
-	papply "github.com/mbrt/gmailctl/pkg/apply"
-	"github.com/mbrt/gmailctl/pkg/errors"
+	papply "github.com/mbrt/gmailctl/internal/apply"
+	"github.com/mbrt/gmailctl/internal/errors"
 )
 
 var (
diff --git a/cmd/gmailctl/cmd/config.go b/cmd/gmailctl/cmd/config.go
index 0e2e821..4aa2c5b 100644
--- a/cmd/gmailctl/cmd/config.go
+++ b/cmd/gmailctl/cmd/config.go
@@ -6,10 +6,10 @@ import (
 	"os"
 	"path"
 
-	papply "github.com/mbrt/gmailctl/pkg/apply"
-	"github.com/mbrt/gmailctl/pkg/cfgtest"
-	"github.com/mbrt/gmailctl/pkg/config"
-	"github.com/mbrt/gmailctl/pkg/config/v1alpha3"
+	papply "github.com/mbrt/gmailctl/internal/apply"
+	"github.com/mbrt/gmailctl/internal/cfgtest"
+	"github.com/mbrt/gmailctl/internal/config"
+	"github.com/mbrt/gmailctl/internal/config/v1alpha3"
 )
 
 type parseResult struct {
diff --git a/cmd/gmailctl/cmd/debug_cmd.go b/cmd/gmailctl/cmd/debug_cmd.go
index 25a0a13..0d028fb 100644
--- a/cmd/gmailctl/cmd/debug_cmd.go
+++ b/cmd/gmailctl/cmd/debug_cmd.go
@@ -7,7 +7,7 @@ import (
 	"github.com/spf13/cobra"
 	"gopkg.in/yaml.v2"
 
-	"github.com/mbrt/gmailctl/pkg/filter"
+	"github.com/mbrt/gmailctl/internal/filter"
 )
 
 var (
diff --git a/cmd/gmailctl/cmd/diff_cmd.go b/cmd/gmailctl/cmd/diff_cmd.go
index 40dc3cf..bc0a92b 100644
--- a/cmd/gmailctl/cmd/diff_cmd.go
+++ b/cmd/gmailctl/cmd/diff_cmd.go
@@ -5,7 +5,7 @@ import (
 
 	"github.com/spf13/cobra"
 
-	papply "github.com/mbrt/gmailctl/pkg/apply"
+	papply "github.com/mbrt/gmailctl/internal/apply"
 )
 
 var (
diff --git a/cmd/gmailctl/cmd/download_cmd.go b/cmd/gmailctl/cmd/download_cmd.go
index d0dec4f..b15ab5d 100644
--- a/cmd/gmailctl/cmd/download_cmd.go
+++ b/cmd/gmailctl/cmd/download_cmd.go
@@ -7,7 +7,7 @@ import (
 
 	"github.com/spf13/cobra"
 
-	"github.com/mbrt/gmailctl/pkg/rimport"
+	"github.com/mbrt/gmailctl/internal/rimport"
 )
 
 const downloadHeader = `// Auto-imported filters by 'gmailctl download'.
diff --git a/cmd/gmailctl/cmd/edit_cmd.go b/cmd/gmailctl/cmd/edit_cmd.go
index cd6666c..2b6c547 100644
--- a/cmd/gmailctl/cmd/edit_cmd.go
+++ b/cmd/gmailctl/cmd/edit_cmd.go
@@ -10,10 +10,10 @@ import (
 
 	"github.com/spf13/cobra"
 
-	"github.com/mbrt/gmailctl/pkg/api"
-	papply "github.com/mbrt/gmailctl/pkg/apply"
-	"github.com/mbrt/gmailctl/pkg/config"
-	"github.com/mbrt/gmailctl/pkg/errors"
+	"github.com/mbrt/gmailctl/internal/api"
+	papply "github.com/mbrt/gmailctl/internal/apply"
+	"github.com/mbrt/gmailctl/internal/config"
+	"github.com/mbrt/gmailctl/internal/errors"
 )
 
 // Parameters
diff --git a/cmd/gmailctl/cmd/export_cmd.go b/cmd/gmailctl/cmd/export_cmd.go
index 1181a55..2bb7b0d 100644
--- a/cmd/gmailctl/cmd/export_cmd.go
+++ b/cmd/gmailctl/cmd/export_cmd.go
@@ -7,7 +7,7 @@ import (
 
 	"github.com/spf13/cobra"
 
-	"github.com/mbrt/gmailctl/pkg/export/xml"
+	"github.com/mbrt/gmailctl/internal/export/xml"
 )
 
 var (
diff --git a/cmd/gmailctl/cmd/init_cmd.go b/cmd/gmailctl/cmd/init_cmd.go
index 6531a2f..84101f3 100644
--- a/cmd/gmailctl/cmd/init_cmd.go
+++ b/cmd/gmailctl/cmd/init_cmd.go
@@ -1,57 +1,14 @@
 package cmd
 
 import (
-	"context"
 	"fmt"
 	"os"
 	"path"
 
 	"github.com/spf13/cobra"
-
-	"github.com/mbrt/gmailctl/pkg/api"
-)
-
-var (
-	initReset bool
 )
 
-const (
-	credentialsMissingMsg = `The credentials are not initialized.
-
-To do so, head to https://console.developers.google.com
-
-1. Create a new project if you don't have one.
-1. Go to 'Enable API and services' and select Gmail.
-2. Go to credentials and create a new one, by selecting 'Help me choose'.
-   2a. Select the Gmail API.
-   2b. Select 'Other UI'.
-   2c. Access 'User data'.
-3. Go to 'OAuth consent screen'.
-   3a. If your account is managed by an organization, you have to
-       select 'Internal' as 'User Type' and Create (otherwise ignore).
-   3b. Set an application name (e.g. 'gmailctl').
-   3c. Update 'Scopes for Google API', by adding:
-       * https://www.googleapis.com/auth/gmail.labels
-       * https://www.googleapis.com/auth/gmail.settings.basic
-5. IMPORTANT: you don't need to submit your changes for verification, as
-   you're only going to access your own data. Save and 'Go back to
-   Dashboard'.
-   5a. Make sure that the 'Publishig status' is set to 'In production'.
-       If it's set to 'Testing', Publish the app and ignore the
-	   verification. Using the testing mode will make your tokens
-	   expire every 7 days and require re-authentication.
-6. Go back to Credentials.
-   6a. Click 'Create credentials'.
-   6b. Select 'OAuth client ID'.
-   6c. Select 'Desktop app' as 'Application type' and give it a name.
-   6d. Create.
-7. Download the credentials file into '%s' and execute the 'init'
-   command again.
-
-Documentation about Gmail API authorization can be found
-at: https://developers.google.com/gmail/api/auth/about-auth
-`
-)
+var initReset bool
 
 // initCmd represents the init command
 var initCmd = &cobra.Command{
@@ -81,10 +38,7 @@ func init() {
 }
 
 func resetConfig() error {
-	if err := deleteFile(credentialsPath); err != nil {
-		return err
-	}
-	if err := deleteFile(tokenPath); err != nil {
+	if err := APIProvider.ResetConfig(cfgDir); err != nil {
 		return err
 	}
 	fmt.Println("Configuration reset.")
@@ -95,21 +49,9 @@ func continueConfig() error {
 	if err := handleCfgDir(); err != nil {
 		return fmt.Errorf("configuring the main config directory: %w", err)
 	}
-	auth, err := openCredentials()
-	if err != nil {
-		stderrPrintf("%v\n\n", err)
-		fmt.Printf(credentialsMissingMsg, credentialsPath)
-		return nil
-	}
-	_, err = openToken(context.Background(), auth)
-	if err != nil {
-		stderrPrintf("%v\n\n", err)
-		err = setupToken(auth)
-	}
-	if err != nil {
+	if err := APIProvider.InitConfig(cfgDir); err != nil {
 		return err
 	}
-
 	fmt.Println("\nYou have correctly configured gmailctl to use Gmail APIs.")
 	return nil
 }
@@ -154,42 +96,3 @@ func createDefault(path, contents string) (err error) {
 	}
 	return nil
 }
-
-func setupToken(auth *api.Authenticator) error {
-	fmt.Printf("Go to the following link in your browser then type the "+
-		"authorization code: \n%v\nAuthorization code: ", auth.AuthURL())
-
-	var authCode string
-	if _, err := fmt.Scan(&authCode); err != nil {
-		return fmt.Errorf("unable to retrieve token from web: %w", err)
-	}
-
-	if err := saveToken(tokenPath, authCode, auth); err != nil {
-		return fmt.Errorf("caching token: %w", err)
-	}
-	return nil
-}
-
-func saveToken(path, authCode string, auth *api.Authenticator) (err error) {
-	fmt.Printf("Saving credential file to %s\n", path)
-	f, e := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
-	if e != nil {
-		return fmt.Errorf("creating token file: %w", err)
-	}
-	defer func() {
-		e = f.Close()
-		// do not hide more important error
-		if err == nil {
-			err = e
-		}
-	}()
-
-	return auth.CacheToken(context.Background(), authCode, f)
-}
-
-func deleteFile(path string) error {
-	if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
-		return nil
-	}
-	return os.Remove(path)
-}
diff --git a/cmd/gmailctl/cmd/initapi.go b/cmd/gmailctl/cmd/initapi.go
deleted file mode 100644
index 1c577b7..0000000
--- a/cmd/gmailctl/cmd/initapi.go
+++ /dev/null
@@ -1,49 +0,0 @@
-package cmd
-
-import (
-	"context"
-	"fmt"
-	"os"
-
-	"github.com/mbrt/gmailctl/pkg/api"
-)
-
-// Authenticator is the APIProvider used by all gmailctl commands.
-var Authenticator APIProvider = localCredentialsProvider{}
-
-// APIProvider is the integration point between gmailctl commands and GMail
-// APIs providers.
-type APIProvider interface {
-	Open(ctx context.Context) (*api.GmailAPI, error)
-}
-
-type localCredentialsProvider struct{}
-
-func (l localCredentialsProvider) Open(ctx context.Context) (*api.GmailAPI, error) {
-	auth, err := openCredentials()
-	if err != nil {
-		return nil, fmt.Errorf("invalid credentials: %w", err)
-	}
-	return openToken(ctx, auth)
-}
-
-func openAPI() (*api.GmailAPI, error) {
-	return Authenticator.Open(context.Background())
-}
-
-func openCredentials() (*api.Authenticator, error) {
-	cred, err := os.Open(credentialsPath)
-	if err != nil {
-		return nil, fmt.Errorf("opening credentials: %w", err)
-	}
-	return api.NewAuthenticator(cred)
-}
-
-func openToken(ctx context.Context, auth *api.Authenticator) (*api.GmailAPI, error) {
-	token, err := os.Open(tokenPath)
-	if err != nil {
-		return nil, fmt.Errorf("missing or invalid cached token: %w", err)
-	}
-
-	return auth.API(ctx, token)
-}
diff --git a/cmd/gmailctl/cmd/io.go b/cmd/gmailctl/cmd/io.go
index f69df2f..12f3a16 100644
--- a/cmd/gmailctl/cmd/io.go
+++ b/cmd/gmailctl/cmd/io.go
@@ -6,7 +6,7 @@ import (
 	"os"
 	"strings"
 
-	"github.com/mbrt/gmailctl/pkg/errors"
+	"github.com/mbrt/gmailctl/internal/errors"
 )
 
 func askYN(prompt string) bool {
diff --git a/cmd/gmailctl/cmd/root_cmd.go b/cmd/gmailctl/cmd/root_cmd.go
index 83b7917..884052b 100644
--- a/cmd/gmailctl/cmd/root_cmd.go
+++ b/cmd/gmailctl/cmd/root_cmd.go
@@ -9,11 +9,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-var (
-	cfgDir          string
-	credentialsPath string
-	tokenPath       string
-)
+var cfgDir string
 
 // rootCmd is the command run when executing without subcommands.
 var rootCmd = &cobra.Command{
@@ -71,6 +67,4 @@ func initConfig() {
 		}
 		cfgDir = path.Join(usr.HomeDir, ".gmailctl")
 	}
-	credentialsPath = path.Join(cfgDir, "credentials.json")
-	tokenPath = path.Join(cfgDir, "token.json")
 }
diff --git a/cmd/gmailctl/cmd/upstream.go b/cmd/gmailctl/cmd/upstream.go
index 72c8598..050ca28 100644
--- a/cmd/gmailctl/cmd/upstream.go
+++ b/cmd/gmailctl/cmd/upstream.go
@@ -1,8 +1,8 @@
 package cmd
 
 import (
-	"github.com/mbrt/gmailctl/pkg/api"
-	papply "github.com/mbrt/gmailctl/pkg/apply"
+	"github.com/mbrt/gmailctl/internal/api"
+	papply "github.com/mbrt/gmailctl/internal/apply"
 )
 
 func upstreamConfig(gmailapi *api.GmailAPI) (papply.GmailConfig, error) {
diff --git a/cmd/gmailctl/localcred/local_provider.go b/cmd/gmailctl/localcred/local_provider.go
new file mode 100644
index 0000000..175a0b2
--- /dev/null
+++ b/cmd/gmailctl/localcred/local_provider.go
@@ -0,0 +1,160 @@
+package localcred
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"path"
+
+	"google.golang.org/api/gmail/v1"
+
+	"github.com/mbrt/gmailctl/cmd/gmailctl/cmd"
+	"github.com/mbrt/gmailctl/internal/api"
+)
+
+const (
+	credentialsMissingMsg = `The credentials are not initialized.
+
+To do so, head to https://console.developers.google.com
+
+1. Create a new project if you don't have one.
+1. Go to 'Enable API and services' and select Gmail.
+2. Go to credentials and create a new one, by selecting 'Help me choose'.
+   2a. Select the Gmail API.
+   2b. Select 'Other UI'.
+   2c. Access 'User data'.
+3. Go to 'OAuth consent screen'.
+   3a. If your account is managed by an organization, you have to
+       select 'Internal' as 'User Type' and Create (otherwise ignore).
+   3b. Set an application name (e.g. 'gmailctl').
+   3c. Update 'Scopes for Google API', by adding:
+       * https://www.googleapis.com/auth/gmail.labels
+       * https://www.googleapis.com/auth/gmail.settings.basic
+5. IMPORTANT: you don't need to submit your changes for verification, as
+   you're only going to access your own data. Save and 'Go back to
+   Dashboard'.
+   5a. Make sure that the 'Publishig status' is set to 'In production'.
+       If it's set to 'Testing', Publish the app and ignore the
+	   verification. Using the testing mode will make your tokens
+	   expire every 7 days and require re-authentication.
+6. Go back to Credentials.
+   6a. Click 'Create credentials'.
+   6b. Select 'OAuth client ID'.
+   6c. Select 'Desktop app' as 'Application type' and give it a name.
+   6d. Create.
+7. Download the credentials file into '%s' and execute the 'init'
+   command again.
+
+Documentation about Gmail API authorization can be found
+at: https://developers.google.com/gmail/api/auth/about-auth
+`
+)
+
+// Provider is a GMail credential provider that uses the local filesystem.
+type Provider struct{}
+
+func (Provider) Service(ctx context.Context, cfgDir string) (*gmail.Service, error) {
+	auth, err := openCredentials(credentialsPath(cfgDir))
+	if err != nil {
+		return nil, err
+	}
+	return openToken(ctx, auth, tokenPath(cfgDir))
+}
+
+func (Provider) InitConfig(cfgDir string) error {
+	cpath := credentialsPath(cfgDir)
+	tpath := tokenPath(cfgDir)
+
+	auth, err := openCredentials(cpath)
+	if err != nil {
+		fmt.Printf(credentialsMissingMsg, cpath)
+		return err
+	}
+	_, err = openToken(context.Background(), auth, tpath)
+	if err != nil {
+		stderrPrintf("%v\n\n", err)
+		err = setupToken(auth, tpath)
+	}
+	return err
+}
+
+func (Provider) ResetConfig(cfgDir string) error {
+	if err := deleteFile(credentialsPath(cfgDir)); err != nil {
+		return err
+	}
+	if err := deleteFile(tokenPath(cfgDir)); err != nil {
+		return err
+	}
+	return nil
+}
+
+func openCredentials(path string) (*api.Authenticator, error) {
+	cred, err := os.Open(path)
+	if err != nil {
+		return nil, fmt.Errorf("opening credentials: %w", err)
+	}
+	return api.NewAuthenticator(cred)
+}
+
+func openToken(ctx context.Context, auth *api.Authenticator, path string) (*gmail.Service, error) {
+	token, err := os.Open(path)
+	if err != nil {
+		return nil, fmt.Errorf("missing or invalid cached token: %w", err)
+	}
+	return auth.Service(ctx, token)
+}
+
+func setupToken(auth *api.Authenticator, path string) error {
+	fmt.Printf("Go to the following link in your browser then type the "+
+		"authorization code: \n%v\nAuthorization code: ", auth.AuthURL())
+
+	var authCode string
+	if _, err := fmt.Scan(&authCode); err != nil {
+		return fmt.Errorf("unable to retrieve token from web: %w", err)
+	}
+
+	if err := saveToken(path, authCode, auth); err != nil {
+		return fmt.Errorf("caching token: %w", err)
+	}
+	return nil
+}
+
+func saveToken(path, authCode string, auth *api.Authenticator) (err error) {
+	fmt.Printf("Saving credential file to %s\n", path)
+	f, e := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
+	if e != nil {
+		return fmt.Errorf("creating token file: %w", e)
+	}
+	defer func() {
+		e = f.Close()
+		// do not hide more important error
+		if err == nil {
+			err = e
+		}
+	}()
+
+	return auth.CacheToken(context.Background(), authCode, f)
+}
+
+func credentialsPath(cfgDir string) string {
+	return path.Join(cfgDir, "credentials.json")
+}
+
+func tokenPath(cfgDir string) string {
+	return path.Join(cfgDir, "token.json")
+}
+
+func deleteFile(path string) error {
+	if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
+		return nil
+	}
+	return os.Remove(path)
+}
+
+func stderrPrintf(format string, a ...interface{}) {
+	/* #nosec */
+	_, _ = fmt.Fprintf(os.Stderr, format, a...)
+}
+
+// Verify that the interface is implemented.
+var _ cmd.GmailAPIProvider = Provider{}
diff --git a/cmd/gmailctl/main.go b/cmd/gmailctl/main.go
index 4545da3..574b1b1 100644
--- a/cmd/gmailctl/main.go
+++ b/cmd/gmailctl/main.go
@@ -1,7 +1,11 @@
 package main
 
-import "github.com/mbrt/gmailctl/cmd/gmailctl/cmd"
+import (
+	"github.com/mbrt/gmailctl/cmd/gmailctl/cmd"
+	"github.com/mbrt/gmailctl/cmd/gmailctl/localcred"
+)
 
 func main() {
+	cmd.APIProvider = localcred.Provider{}
 	cmd.Execute()
 }
diff --git a/pkg/api/api.go b/internal/api/api.go
similarity index 95%
rename from pkg/api/api.go
rename to internal/api/api.go
index cfd2568..2828a23 100644
--- a/pkg/api/api.go
+++ b/internal/api/api.go
@@ -7,10 +7,10 @@ import (
 	"google.golang.org/api/gmail/v1"
 	"google.golang.org/api/googleapi"
 
-	"github.com/mbrt/gmailctl/pkg/errors"
-	exportapi "github.com/mbrt/gmailctl/pkg/export/api"
-	"github.com/mbrt/gmailctl/pkg/filter"
-	"github.com/mbrt/gmailctl/pkg/label"
+	"github.com/mbrt/gmailctl/internal/errors"
+	exportapi "github.com/mbrt/gmailctl/internal/export/api"
+	"github.com/mbrt/gmailctl/internal/filter"
+	"github.com/mbrt/gmailctl/internal/label"
 )
 
 const (
diff --git a/pkg/api/auth.go b/internal/api/auth.go
similarity index 87%
rename from pkg/api/auth.go
rename to internal/api/auth.go
index c0e2b9c..ae1724a 100644
--- a/pkg/api/auth.go
+++ b/internal/api/auth.go
@@ -36,21 +36,27 @@ type Authenticator struct {
 	cfg   *oauth2.Config
 }
 
-// API creates a GmailAPI instance from a token JSON file contents.
+// Service creates a Gmail API service from a token JSON file contents.
 //
 // If no token is available, AuthURL and CacheToken can be used to
 // obtain one.
-func (a Authenticator) API(ctx context.Context, token io.Reader) (*GmailAPI, error) {
+func (a Authenticator) Service(ctx context.Context, token io.Reader) (*gmail.Service, error) {
 	tok, err := parseToken(token)
 	if err != nil {
 		return nil, fmt.Errorf("decoding token: %w", err)
 	}
+	return gmail.NewService(ctx, option.WithTokenSource(a.cfg.TokenSource(ctx, tok)))
+}
 
-	srv, err := gmail.NewService(ctx, option.WithTokenSource(a.cfg.TokenSource(ctx, tok)))
+// API creates a GmailAPI instance from a token JSON file contents.
+//
+// If no token is available, AuthURL and CacheToken can be used to
+// obtain one.
+func (a Authenticator) API(ctx context.Context, token io.Reader) (*GmailAPI, error) {
+	srv, err := a.Service(ctx, token)
 	if err != nil {
 		return nil, fmt.Errorf("creating gmail client: %w", err)
 	}
-
 	return NewFromService(srv), nil
 }
 
diff --git a/pkg/apply/apply.go b/internal/apply/apply.go
similarity index 97%
rename from pkg/apply/apply.go
rename to internal/apply/apply.go
index c725990..bfa2347 100644
--- a/pkg/apply/apply.go
+++ b/internal/apply/apply.go
@@ -5,10 +5,10 @@ import (
 	"sort"
 	"strings"
 
-	"github.com/mbrt/gmailctl/pkg/config/v1alpha3"
-	"github.com/mbrt/gmailctl/pkg/filter"
-	"github.com/mbrt/gmailctl/pkg/label"
-	"github.com/mbrt/gmailctl/pkg/parser"
+	"github.com/mbrt/gmailctl/internal/config/v1alpha3"
+	"github.com/mbrt/gmailctl/internal/filter"
+	"github.com/mbrt/gmailctl/internal/label"
+	"github.com/mbrt/gmailctl/internal/parser"
 )
 
 // GmailConfig represents a Gmail configuration.
diff --git a/pkg/config/read.go b/internal/config/read.go
similarity index 97%
rename from pkg/config/read.go
rename to internal/config/read.go
index 1714ee9..e7d388f 100644
--- a/pkg/config/read.go
+++ b/internal/config/read.go
@@ -10,8 +10,8 @@ import (
 
 	"github.com/google/go-jsonnet"
 
-	"github.com/mbrt/gmailctl/pkg/config/v1alpha3"
-	"github.com/mbrt/gmailctl/pkg/errors"
+	"github.com/mbrt/gmailctl/internal/config/v1alpha3"
+	"github.com/mbrt/gmailctl/internal/errors"
 )
 
 const (
diff --git a/pkg/config/testdata/chainfilters.json b/internal/config/testdata/chainfilters.json
similarity index 100%
rename from pkg/config/testdata/chainfilters.json
rename to internal/config/testdata/chainfilters.json
diff --git a/pkg/config/testdata/chainfilters.jsonnet b/internal/config/testdata/chainfilters.jsonnet
similarity index 100%
rename from pkg/config/testdata/chainfilters.jsonnet
rename to internal/config/testdata/chainfilters.jsonnet
diff --git a/pkg/config/testdata/gmailctl.libsonnet b/internal/config/testdata/gmailctl.libsonnet
similarity index 100%
rename from pkg/config/testdata/gmailctl.libsonnet
rename to internal/config/testdata/gmailctl.libsonnet
diff --git a/pkg/config/v1alpha3/config.go b/internal/config/v1alpha3/config.go
similarity index 99%
rename from pkg/config/v1alpha3/config.go
rename to internal/config/v1alpha3/config.go
index 5691fd1..40848f3 100644
--- a/pkg/config/v1alpha3/config.go
+++ b/internal/config/v1alpha3/config.go
@@ -4,7 +4,7 @@ import (
 	"reflect"
 	"strings"
 
-	"github.com/mbrt/gmailctl/pkg/gmail"
+	"github.com/mbrt/gmailctl/internal/gmail"
 )
 
 // Version is the latest supported version.
diff --git a/pkg/errors/err.go b/internal/errors/err.go
similarity index 100%
rename from pkg/errors/err.go
rename to internal/errors/err.go
diff --git a/pkg/errors/err_test.go b/internal/errors/err_test.go
similarity index 100%
rename from pkg/errors/err_test.go
rename to internal/errors/err_test.go
diff --git a/pkg/export/api/api_export.go b/internal/export/api/api_export.go
similarity index 94%
rename from pkg/export/api/api_export.go
rename to internal/export/api/api_export.go
index a8814ef..1fd6b30 100644
--- a/pkg/export/api/api_export.go
+++ b/internal/export/api/api_export.go
@@ -5,10 +5,10 @@ import (
 
 	gmailv1 "google.golang.org/api/gmail/v1"
 
-	"github.com/mbrt/gmailctl/pkg/errors"
-	"github.com/mbrt/gmailctl/pkg/filter"
-	"github.com/mbrt/gmailctl/pkg/gmail"
-	"github.com/mbrt/gmailctl/pkg/reporting"
+	"github.com/mbrt/gmailctl/internal/errors"
+	"github.com/mbrt/gmailctl/internal/filter"
+	"github.com/mbrt/gmailctl/internal/gmail"
+	"github.com/mbrt/gmailctl/internal/reporting"
 )
 
 // Export exports Gmail filters into Gmail API objects
diff --git a/pkg/export/api/api_import.go b/internal/export/api/api_import.go
similarity index 98%
rename from pkg/export/api/api_import.go
rename to internal/export/api/api_import.go
index 75303d5..85b773e 100644
--- a/pkg/export/api/api_import.go
+++ b/internal/export/api/api_import.go
@@ -9,8 +9,8 @@ import (
 	"github.com/hashicorp/go-multierror"
 	gmailv1 "google.golang.org/api/gmail/v1"
 
-	"github.com/mbrt/gmailctl/pkg/filter"
-	"github.com/mbrt/gmailctl/pkg/gmail"
+	"github.com/mbrt/gmailctl/internal/filter"
+	"github.com/mbrt/gmailctl/internal/gmail"
 )
 
 var (
diff --git a/pkg/export/api/api_test.go b/internal/export/api/api_test.go
similarity index 100%
rename from pkg/export/api/api_test.go
rename to internal/export/api/api_test.go
diff --git a/pkg/export/api/label.go b/internal/export/api/label.go
similarity index 96%
rename from pkg/export/api/label.go
rename to internal/export/api/label.go
index 8b14430..755a08e 100644
--- a/pkg/export/api/label.go
+++ b/internal/export/api/label.go
@@ -1,6 +1,6 @@
 package api
 
-import "github.com/mbrt/gmailctl/pkg/label"
+import "github.com/mbrt/gmailctl/internal/label"
 
 const (
 	labelIDInbox     = "INBOX"
diff --git a/pkg/export/xml/consts.go b/internal/export/xml/consts.go
similarity index 97%
rename from pkg/export/xml/consts.go
rename to internal/export/xml/consts.go
index 6d5483d..f820d6d 100644
--- a/pkg/export/xml/consts.go
+++ b/internal/export/xml/consts.go
@@ -4,7 +4,7 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/mbrt/gmailctl/pkg/gmail"
+	"github.com/mbrt/gmailctl/internal/gmail"
 )
 
 // Property values
diff --git a/pkg/export/xml/marshal.go b/internal/export/xml/marshal.go
similarity index 97%
rename from pkg/export/xml/marshal.go
rename to internal/export/xml/marshal.go
index d7d1b86..a3820b9 100644
--- a/pkg/export/xml/marshal.go
+++ b/internal/export/xml/marshal.go
@@ -5,8 +5,8 @@ import (
 	"io"
 	"time"
 
-	"github.com/mbrt/gmailctl/pkg/config/v1alpha3"
-	"github.com/mbrt/gmailctl/pkg/filter"
+	"github.com/mbrt/gmailctl/internal/config/v1alpha3"
+	"github.com/mbrt/gmailctl/internal/filter"
 )
 
 // DefaultExporter returns a default XML exporter.
diff --git a/pkg/filter/convert.go b/internal/filter/convert.go
similarity index 99%
rename from pkg/filter/convert.go
rename to internal/filter/convert.go
index 0f01752..f64a65b 100644
--- a/pkg/filter/convert.go
+++ b/internal/filter/convert.go
@@ -5,7 +5,7 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/mbrt/gmailctl/pkg/parser"
+	"github.com/mbrt/gmailctl/internal/parser"
 )
 
 // There's no documented limit on filter size on Gmail, but this educated guess
diff --git a/pkg/filter/diff.go b/internal/filter/diff.go
similarity index 99%
rename from pkg/filter/diff.go
rename to internal/filter/diff.go
index 31dc31a..5d797c5 100644
--- a/pkg/filter/diff.go
+++ b/internal/filter/diff.go
@@ -8,7 +8,7 @@ import (
 
 	"github.com/pmezard/go-difflib/difflib"
 
-	"github.com/mbrt/gmailctl/pkg/graph"
+	"github.com/mbrt/gmailctl/internal/graph"
 )
 
 // Diff computes the diff between two lists of filters.
diff --git a/pkg/filter/filter.go b/internal/filter/filter.go
similarity index 98%
rename from pkg/filter/filter.go
rename to internal/filter/filter.go
index 4de220c..61c5289 100644
--- a/pkg/filter/filter.go
+++ b/internal/filter/filter.go
@@ -4,7 +4,7 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/mbrt/gmailctl/pkg/gmail"
+	"github.com/mbrt/gmailctl/internal/gmail"
 )
 
 // Filters is a list of filters created in Gmail.
diff --git a/pkg/gmail/consts.go b/internal/gmail/consts.go
similarity index 100%
rename from pkg/gmail/consts.go
rename to internal/gmail/consts.go
diff --git a/pkg/graph/LICENSE b/internal/graph/LICENSE
similarity index 100%
rename from pkg/graph/LICENSE
rename to internal/graph/LICENSE
diff --git a/pkg/graph/README.md b/internal/graph/README.md
similarity index 100%
rename from pkg/graph/README.md
rename to internal/graph/README.md
diff --git a/pkg/graph/deps.go b/internal/graph/deps.go
similarity index 100%
rename from pkg/graph/deps.go
rename to internal/graph/deps.go
diff --git a/pkg/graph/import.sh b/internal/graph/import.sh
similarity index 100%
rename from pkg/graph/import.sh
rename to internal/graph/import.sh
diff --git a/pkg/graph/munkres.go b/internal/graph/munkres.go
similarity index 100%
rename from pkg/graph/munkres.go
rename to internal/graph/munkres.go
diff --git a/pkg/label/diff.go b/internal/label/diff.go
similarity index 98%
rename from pkg/label/diff.go
rename to internal/label/diff.go
index 279baa4..41ea0cf 100644
--- a/pkg/label/diff.go
+++ b/internal/label/diff.go
@@ -7,7 +7,7 @@ import (
 
 	"github.com/pmezard/go-difflib/difflib"
 
-	"github.com/mbrt/gmailctl/pkg/filter"
+	"github.com/mbrt/gmailctl/internal/filter"
 )
 
 // Diff computes the diff between two lists of labels.
diff --git a/pkg/label/label.go b/internal/label/label.go
similarity index 97%
rename from pkg/label/label.go
rename to internal/label/label.go
index f7ca657..ae91669 100644
--- a/pkg/label/label.go
+++ b/internal/label/label.go
@@ -5,7 +5,7 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/mbrt/gmailctl/pkg/config/v1alpha3"
+	"github.com/mbrt/gmailctl/internal/config/v1alpha3"
 )
 
 // Labels is a list of labels.
diff --git a/pkg/label/label_test.go b/internal/label/label_test.go
similarity index 100%
rename from pkg/label/label_test.go
rename to internal/label/label_test.go
diff --git a/pkg/label/testdata/local.added.diff b/internal/label/testdata/local.added.diff
similarity index 100%
rename from pkg/label/testdata/local.added.diff
rename to internal/label/testdata/local.added.diff
diff --git a/pkg/label/testdata/local.added.json b/internal/label/testdata/local.added.json
similarity index 100%
rename from pkg/label/testdata/local.added.json
rename to internal/label/testdata/local.added.json
diff --git a/pkg/label/testdata/local.deleted.diff b/internal/label/testdata/local.deleted.diff
similarity index 100%
rename from pkg/label/testdata/local.deleted.diff
rename to internal/label/testdata/local.deleted.diff
diff --git a/pkg/label/testdata/local.deleted.json b/internal/label/testdata/local.deleted.json
similarity index 100%
rename from pkg/label/testdata/local.deleted.json
rename to internal/label/testdata/local.deleted.json
diff --git a/pkg/label/testdata/local.modified.diff b/internal/label/testdata/local.modified.diff
similarity index 100%
rename from pkg/label/testdata/local.modified.diff
rename to internal/label/testdata/local.modified.diff
diff --git a/pkg/label/testdata/local.modified.json b/internal/label/testdata/local.modified.json
similarity index 100%
rename from pkg/label/testdata/local.modified.json
rename to internal/label/testdata/local.modified.json
diff --git a/pkg/label/testdata/local.nodiff.diff b/internal/label/testdata/local.nodiff.diff
similarity index 100%
rename from pkg/label/testdata/local.nodiff.diff
rename to internal/label/testdata/local.nodiff.diff
diff --git a/pkg/label/testdata/local.nodiff.json b/internal/label/testdata/local.nodiff.json
similarity index 100%
rename from pkg/label/testdata/local.nodiff.json
rename to internal/label/testdata/local.nodiff.json
diff --git a/pkg/label/testdata/upstream.json b/internal/label/testdata/upstream.json
similarity index 100%
rename from pkg/label/testdata/upstream.json
rename to internal/label/testdata/upstream.json
diff --git a/pkg/parser/ast.go b/internal/parser/ast.go
similarity index 100%
rename from pkg/parser/ast.go
rename to internal/parser/ast.go
diff --git a/pkg/parser/ast_test.go b/internal/parser/ast_test.go
similarity index 100%
rename from pkg/parser/ast_test.go
rename to internal/parser/ast_test.go
diff --git a/pkg/parser/lib_test.go b/internal/parser/lib_test.go
similarity index 100%
rename from pkg/parser/lib_test.go
rename to internal/parser/lib_test.go
diff --git a/pkg/parser/parser.go b/internal/parser/parser.go
similarity index 95%
rename from pkg/parser/parser.go
rename to internal/parser/parser.go
index 1603f6c..e2b2c12 100644
--- a/pkg/parser/parser.go
+++ b/internal/parser/parser.go
@@ -4,9 +4,9 @@ import (
 	"fmt"
 	"strings"
 
-	cfg "github.com/mbrt/gmailctl/pkg/config/v1alpha3"
-	"github.com/mbrt/gmailctl/pkg/errors"
-	"github.com/mbrt/gmailctl/pkg/reporting"
+	cfg "github.com/mbrt/gmailctl/internal/config/v1alpha3"
+	"github.com/mbrt/gmailctl/internal/errors"
+	"github.com/mbrt/gmailctl/internal/reporting"
 )
 
 // Rule is an intermediate representation of a Gmail filter.
diff --git a/pkg/parser/testdata/example.jsonnet b/internal/parser/testdata/example.jsonnet
similarity index 100%
rename from pkg/parser/testdata/example.jsonnet
rename to internal/parser/testdata/example.jsonnet
diff --git a/pkg/reporting/pretty.go b/internal/reporting/pretty.go
similarity index 100%
rename from pkg/reporting/pretty.go
rename to internal/reporting/pretty.go
diff --git a/pkg/rimport/jsonnet.go b/internal/rimport/jsonnet.go
similarity index 100%
rename from pkg/rimport/jsonnet.go
rename to internal/rimport/jsonnet.go
diff --git a/pkg/rimport/rimport.go b/internal/rimport/rimport.go
similarity index 93%
rename from pkg/rimport/rimport.go
rename to internal/rimport/rimport.go
index 419c2e7..0934830 100644
--- a/pkg/rimport/rimport.go
+++ b/internal/rimport/rimport.go
@@ -4,11 +4,11 @@ import (
 	"fmt"
 	"strings"
 
-	config "github.com/mbrt/gmailctl/pkg/config/v1alpha3"
-	"github.com/mbrt/gmailctl/pkg/errors"
-	"github.com/mbrt/gmailctl/pkg/filter"
-	"github.com/mbrt/gmailctl/pkg/label"
-	"github.com/mbrt/gmailctl/pkg/reporting"
+	config "github.com/mbrt/gmailctl/internal/config/v1alpha3"
+	"github.com/mbrt/gmailctl/internal/errors"
+	"github.com/mbrt/gmailctl/internal/filter"
+	"github.com/mbrt/gmailctl/internal/label"
+	"github.com/mbrt/gmailctl/internal/reporting"
 )
 
 // Import converts a list of filters into config rules, best


Test patch:
diff --git a/cmd/gmailctl-config-migrate/v1alpha3/import_test.go b/cmd/gmailctl-config-migrate/v1alpha3/import_test.go
index efa5042..9274851 100644
--- a/cmd/gmailctl-config-migrate/v1alpha3/import_test.go
+++ b/cmd/gmailctl-config-migrate/v1alpha3/import_test.go
@@ -12,7 +12,7 @@ import (
 	"gopkg.in/yaml.v2"
 
 	"github.com/mbrt/gmailctl/cmd/gmailctl-config-migrate/v1alpha2"
-	"github.com/mbrt/gmailctl/pkg/config/v1alpha3"
+	"github.com/mbrt/gmailctl/internal/config/v1alpha3"
 )
 
 func read(path string) []byte {
diff --git a/integration_test.go b/integration_test.go
index 6d1dd69..02fc606 100644
--- a/integration_test.go
+++ b/integration_test.go
@@ -16,13 +16,13 @@ import (
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
+	"github.com/mbrt/gmailctl/internal/api"
+	"github.com/mbrt/gmailctl/internal/apply"
+	"github.com/mbrt/gmailctl/internal/config"
+	"github.com/mbrt/gmailctl/internal/config/v1alpha3"
+	"github.com/mbrt/gmailctl/internal/export/xml"
 	"github.com/mbrt/gmailctl/internal/fakegmail"
-	"github.com/mbrt/gmailctl/pkg/api"
-	"github.com/mbrt/gmailctl/pkg/apply"
-	"github.com/mbrt/gmailctl/pkg/config"
-	"github.com/mbrt/gmailctl/pkg/config/v1alpha3"
-	"github.com/mbrt/gmailctl/pkg/export/xml"
-	"github.com/mbrt/gmailctl/pkg/rimport"
+	"github.com/mbrt/gmailctl/internal/rimport"
 )
 
 // update is useful to regenerate the golden files
diff --git a/pkg/cfgtest/builder.go b/internal/cfgtest/builder.go
similarity index 99%
rename from pkg/cfgtest/builder.go
rename to internal/cfgtest/builder.go
index 33f6557..a82f41c 100644
--- a/pkg/cfgtest/builder.go
+++ b/internal/cfgtest/builder.go
@@ -5,7 +5,7 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/mbrt/gmailctl/pkg/parser"
+	"github.com/mbrt/gmailctl/internal/parser"
 )
 
 // NewEvaluator creates a RuleEvaluator starting from a parser criteria.
diff --git a/pkg/cfgtest/cfgtest.go b/internal/cfgtest/cfgtest.go
similarity index 96%
rename from pkg/cfgtest/cfgtest.go
rename to internal/cfgtest/cfgtest.go
index 911ca6f..06d9356 100644
--- a/pkg/cfgtest/cfgtest.go
+++ b/internal/cfgtest/cfgtest.go
@@ -6,11 +6,11 @@ import (
 
 	"github.com/pmezard/go-difflib/difflib"
 
-	cfg "github.com/mbrt/gmailctl/pkg/config/v1alpha3"
-	"github.com/mbrt/gmailctl/pkg/errors"
-	"github.com/mbrt/gmailctl/pkg/gmail"
-	"github.com/mbrt/gmailctl/pkg/parser"
-	"github.com/mbrt/gmailctl/pkg/reporting"
+	cfg "github.com/mbrt/gmailctl/internal/config/v1alpha3"
+	"github.com/mbrt/gmailctl/internal/errors"
+	"github.com/mbrt/gmailctl/internal/gmail"
+	"github.com/mbrt/gmailctl/internal/parser"
+	"github.com/mbrt/gmailctl/internal/reporting"
 )
 
 // NewFromParserRules translates parser Rules into test Rules.
diff --git a/pkg/cfgtest/eval.go b/internal/cfgtest/eval.go
similarity index 97%
rename from pkg/cfgtest/eval.go
rename to internal/cfgtest/eval.go
index 3fb0290..fa17ba6 100644
--- a/pkg/cfgtest/eval.go
+++ b/internal/cfgtest/eval.go
@@ -3,7 +3,7 @@ package cfgtest
 import (
 	"strings"
 
-	cfg "github.com/mbrt/gmailctl/pkg/config/v1alpha3"
+	cfg "github.com/mbrt/gmailctl/internal/config/v1alpha3"
 )
 
 type matchType int
diff --git a/pkg/cfgtest/eval_test.go b/internal/cfgtest/eval_test.go
similarity index 94%
rename from pkg/cfgtest/eval_test.go
rename to internal/cfgtest/eval_test.go
index e9fcd9c..caba2a1 100644
--- a/pkg/cfgtest/eval_test.go
+++ b/internal/cfgtest/eval_test.go
@@ -5,8 +5,8 @@ import (
 
 	"github.com/stretchr/testify/assert"
 
-	cfg "github.com/mbrt/gmailctl/pkg/config/v1alpha3"
-	"github.com/mbrt/gmailctl/pkg/parser"
+	cfg "github.com/mbrt/gmailctl/internal/config/v1alpha3"
+	"github.com/mbrt/gmailctl/internal/parser"
 )
 
 func TestParseEval(t *testing.T) {
diff --git a/pkg/cfgtest/exec_test.go b/internal/cfgtest/exec_test.go
similarity index 86%
rename from pkg/cfgtest/exec_test.go
rename to internal/cfgtest/exec_test.go
index febe1ba..928f49c 100644
--- a/pkg/cfgtest/exec_test.go
+++ b/internal/cfgtest/exec_test.go
@@ -6,9 +6,9 @@ import (
 
 	"github.com/stretchr/testify/assert"
 
-	"github.com/mbrt/gmailctl/pkg/apply"
-	"github.com/mbrt/gmailctl/pkg/config"
-	"github.com/mbrt/gmailctl/pkg/config/v1alpha3"
+	"github.com/mbrt/gmailctl/internal/apply"
+	"github.com/mbrt/gmailctl/internal/config"
+	"github.com/mbrt/gmailctl/internal/config/v1alpha3"
 )
 
 func readConfig(t *testing.T, path string) v1alpha3.Config {
diff --git a/pkg/cfgtest/lib_test.go b/internal/cfgtest/lib_test.go
similarity index 92%
rename from pkg/cfgtest/lib_test.go
rename to internal/cfgtest/lib_test.go
index 8fc2096..e432aa0 100644
--- a/pkg/cfgtest/lib_test.go
+++ b/internal/cfgtest/lib_test.go
@@ -1,6 +1,6 @@
 package cfgtest
 
-import "github.com/mbrt/gmailctl/pkg/parser"
+import "github.com/mbrt/gmailctl/internal/parser"
 
 func and(children ...parser.CriteriaAST) *parser.Node {
 	return &parser.Node{
diff --git a/pkg/cfgtest/testdata/fail.jsonnet b/internal/cfgtest/testdata/fail.jsonnet
similarity index 100%
rename from pkg/cfgtest/testdata/fail.jsonnet
rename to internal/cfgtest/testdata/fail.jsonnet
diff --git a/pkg/cfgtest/testdata/invalid.jsonnet b/internal/cfgtest/testdata/invalid.jsonnet
similarity index 100%
rename from pkg/cfgtest/testdata/invalid.jsonnet
rename to internal/cfgtest/testdata/invalid.jsonnet
diff --git a/pkg/cfgtest/testdata/pass.jsonnet b/internal/cfgtest/testdata/pass.jsonnet
similarity index 100%
rename from pkg/cfgtest/testdata/pass.jsonnet
rename to internal/cfgtest/testdata/pass.jsonnet
diff --git a/pkg/config/lib_test.go b/internal/config/lib_test.go
similarity index 96%
rename from pkg/config/lib_test.go
rename to internal/config/lib_test.go
index d60c43a..10856cb 100644
--- a/pkg/config/lib_test.go
+++ b/internal/config/lib_test.go
@@ -10,7 +10,7 @@ import (
 
 	"github.com/stretchr/testify/assert"
 
-	"github.com/mbrt/gmailctl/pkg/config/v1alpha3"
+	"github.com/mbrt/gmailctl/internal/config/v1alpha3"
 )
 
 // update is useful to regenerate the json files, whenever necessary.
diff --git a/pkg/export/api/api_export_test.go b/internal/export/api/api_export_test.go
similarity index 95%
rename from pkg/export/api/api_export_test.go
rename to internal/export/api/api_export_test.go
index fb32684..943fc60 100644
--- a/pkg/export/api/api_export_test.go
+++ b/internal/export/api/api_export_test.go
@@ -6,9 +6,9 @@ import (
 	"github.com/stretchr/testify/assert"
 	gmailv1 "google.golang.org/api/gmail/v1"
 
-	"github.com/mbrt/gmailctl/pkg/filter"
-	"github.com/mbrt/gmailctl/pkg/gmail"
-	"github.com/mbrt/gmailctl/pkg/label"
+	"github.com/mbrt/gmailctl/internal/filter"
+	"github.com/mbrt/gmailctl/internal/gmail"
+	"github.com/mbrt/gmailctl/internal/label"
 )
 
 func emptyLabelMap() LabelMap {
diff --git a/pkg/export/api/api_import_test.go b/internal/export/api/api_import_test.go
similarity index 96%
rename from pkg/export/api/api_import_test.go
rename to internal/export/api/api_import_test.go
index 2a1bb18..3144a27 100644
--- a/pkg/export/api/api_import_test.go
+++ b/internal/export/api/api_import_test.go
@@ -6,9 +6,9 @@ import (
 	"github.com/stretchr/testify/assert"
 	gmailv1 "google.golang.org/api/gmail/v1"
 
-	"github.com/mbrt/gmailctl/pkg/filter"
-	"github.com/mbrt/gmailctl/pkg/gmail"
-	"github.com/mbrt/gmailctl/pkg/label"
+	"github.com/mbrt/gmailctl/internal/filter"
+	"github.com/mbrt/gmailctl/internal/gmail"
+	"github.com/mbrt/gmailctl/internal/label"
 )
 
 func TestImportActions(t *testing.T) {
diff --git a/pkg/export/xml/marshal_test.go b/internal/export/xml/marshal_test.go
similarity index 96%
rename from pkg/export/xml/marshal_test.go
rename to internal/export/xml/marshal_test.go
index 2efed34..c8e4409 100644
--- a/pkg/export/xml/marshal_test.go
+++ b/internal/export/xml/marshal_test.go
@@ -8,9 +8,9 @@ import (
 
 	"github.com/stretchr/testify/assert"
 
-	"github.com/mbrt/gmailctl/pkg/config/v1alpha3"
-	"github.com/mbrt/gmailctl/pkg/filter"
-	"github.com/mbrt/gmailctl/pkg/gmail"
+	"github.com/mbrt/gmailctl/internal/config/v1alpha3"
+	"github.com/mbrt/gmailctl/internal/filter"
+	"github.com/mbrt/gmailctl/internal/gmail"
 )
 
 func testNow() time.Time {
diff --git a/internal/fakegmail/fakegmail_test.go b/internal/fakegmail/fakegmail_test.go
index 34a32a4..f94b181 100644
--- a/internal/fakegmail/fakegmail_test.go
+++ b/internal/fakegmail/fakegmail_test.go
@@ -6,11 +6,11 @@ import (
 
 	"github.com/stretchr/testify/assert"
 
+	"github.com/mbrt/gmailctl/internal/api"
 	"github.com/mbrt/gmailctl/internal/fakegmail"
-	"github.com/mbrt/gmailctl/pkg/api"
-	"github.com/mbrt/gmailctl/pkg/filter"
-	"github.com/mbrt/gmailctl/pkg/gmail"
-	"github.com/mbrt/gmailctl/pkg/label"
+	"github.com/mbrt/gmailctl/internal/filter"
+	"github.com/mbrt/gmailctl/internal/gmail"
+	"github.com/mbrt/gmailctl/internal/label"
 )
 
 func TestLabels(t *testing.T) {
diff --git a/pkg/filter/convert_test.go b/internal/filter/convert_test.go
similarity index 98%
rename from pkg/filter/convert_test.go
rename to internal/filter/convert_test.go
index fc39643..12678d7 100644
--- a/pkg/filter/convert_test.go
+++ b/internal/filter/convert_test.go
@@ -5,8 +5,8 @@ import (
 
 	"github.com/stretchr/testify/assert"
 
-	"github.com/mbrt/gmailctl/pkg/gmail"
-	"github.com/mbrt/gmailctl/pkg/parser"
+	"github.com/mbrt/gmailctl/internal/gmail"
+	"github.com/mbrt/gmailctl/internal/parser"
 )
 
 func boolptr(a bool) *bool {
diff --git a/pkg/filter/diff_test.go b/internal/filter/diff_test.go
similarity index 99%
rename from pkg/filter/diff_test.go
rename to internal/filter/diff_test.go
index b0ac2d7..a4d32f8 100644
--- a/pkg/filter/diff_test.go
+++ b/internal/filter/diff_test.go
@@ -6,7 +6,7 @@ import (
 
 	"github.com/stretchr/testify/assert"
 
-	"github.com/mbrt/gmailctl/pkg/gmail"
+	"github.com/mbrt/gmailctl/internal/gmail"
 )
 
 func TestNoDiff(t *testing.T) {
diff --git a/pkg/label/diff_test.go b/internal/label/diff_test.go
similarity index 98%
rename from pkg/label/diff_test.go
rename to internal/label/diff_test.go
index 3374d90..a021049 100644
--- a/pkg/label/diff_test.go
+++ b/internal/label/diff_test.go
@@ -11,7 +11,7 @@ import (
 
 	"github.com/stretchr/testify/assert"
 
-	"github.com/mbrt/gmailctl/pkg/filter"
+	"github.com/mbrt/gmailctl/internal/filter"
 )
 
 // update is useful to regenerate the diff files, whenever necessary.
diff --git a/pkg/parser/parser_test.go b/internal/parser/parser_test.go
similarity index 93%
rename from pkg/parser/parser_test.go
rename to internal/parser/parser_test.go
index 8fd7237..3053fc3 100644
--- a/pkg/parser/parser_test.go
+++ b/internal/parser/parser_test.go
@@ -5,8 +5,8 @@ import (
 
 	"github.com/stretchr/testify/assert"
 
-	"github.com/mbrt/gmailctl/pkg/config"
-	"github.com/mbrt/gmailctl/pkg/config/v1alpha3"
+	"github.com/mbrt/gmailctl/internal/config"
+	"github.com/mbrt/gmailctl/internal/config/v1alpha3"
 )
 
 func readConfig(t *testing.T, path string) v1alpha3.Config {
diff --git a/pkg/rimport/rimport_test.go b/internal/rimport/rimport_test.go
similarity index 96%
rename from pkg/rimport/rimport_test.go
rename to internal/rimport/rimport_test.go
index bb94ff8..58d331d 100644
--- a/pkg/rimport/rimport_test.go
+++ b/internal/rimport/rimport_test.go
@@ -8,7 +8,7 @@ import (
 
 	"github.com/stretchr/testify/assert"
 
-	"github.com/mbrt/gmailctl/pkg/filter"
+	"github.com/mbrt/gmailctl/internal/filter"
 )
 
 // update is useful to regenerate the json files, whenever necessary.