Repository: supabase/cli
Pull request #1091 (2023-05-12 06:39:35)

chore: refactor project ref parsing
## What kind of change does this PR introduce?

chore

## What is the new behavior?

If the command belongs to `groupManagementAPI`, we will prompt for login automatically.
If the command defines `project-ref` flag, we will parse it consistently from linked project.

## Additional context

Add any other context or screenshots.



Patch:
diff --git a/cmd/bans.go b/cmd/bans.go
index 3e8279f7..603e2dd4 100644
--- a/cmd/bans.go
+++ b/cmd/bans.go
@@ -1,13 +1,11 @@
 package cmd
 
 import (
-	"os"
-	"os/signal"
-
 	"github.com/spf13/afero"
 	"github.com/spf13/cobra"
 	"github.com/supabase/cli/internal/bans/get"
 	"github.com/supabase/cli/internal/bans/update"
+	"github.com/supabase/cli/internal/utils/flags"
 )
 
 var (
@@ -26,12 +24,7 @@ The subcommands help you view the current bans, and unblock IPs if desired.`,
 		Use:   "remove",
 		Short: "Remove a network ban",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			fsys := afero.NewOsFs()
-			if err := PromptLogin(fsys); err != nil {
-				return err
-			}
-			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
-			return update.Run(ctx, projectRef, dbIpsToUnban, fsys)
+			return update.Run(cmd.Context(), flags.ProjectRef, dbIpsToUnban, afero.NewOsFs())
 		},
 	}
 
@@ -39,18 +32,13 @@ The subcommands help you view the current bans, and unblock IPs if desired.`,
 		Use:   "get",
 		Short: "Get the current network bans",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			fsys := afero.NewOsFs()
-			if err := PromptLogin(fsys); err != nil {
-				return err
-			}
-			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
-			return get.Run(ctx, projectRef, fsys)
+			return get.Run(cmd.Context(), flags.ProjectRef, afero.NewOsFs())
 		},
 	}
 )
 
 func init() {
-	bansCmd.PersistentFlags().StringVar(&projectRef, "project-ref", "", "Project ref of the Supabase project.")
+	bansCmd.PersistentFlags().StringVar(&flags.ProjectRef, "project-ref", "", "Project ref of the Supabase project.")
 	bansCmd.AddCommand(bansGetCmd)
 	bansRemoveCmd.Flags().StringSliceVar(&dbIpsToUnban, "db-unban-ip", []string{}, "IP to allow DB connections from.")
 	bansCmd.AddCommand(bansRemoveCmd)
diff --git a/cmd/domains.go b/cmd/domains.go
index e15593b2..9ec4a097 100644
--- a/cmd/domains.go
+++ b/cmd/domains.go
@@ -1,9 +1,6 @@
 package cmd
 
 import (
-	"os"
-	"os/signal"
-
 	"github.com/spf13/afero"
 	"github.com/spf13/cobra"
 	"github.com/supabase/cli/internal/hostnames/activate"
@@ -11,6 +8,7 @@ import (
 	"github.com/supabase/cli/internal/hostnames/delete"
 	"github.com/supabase/cli/internal/hostnames/get"
 	"github.com/supabase/cli/internal/hostnames/reverify"
+	"github.com/supabase/cli/internal/utils/flags"
 )
 
 var (
@@ -34,12 +32,7 @@ Use of custom domains and vanity subdomains is mutually exclusive.
 
 Expects your custom hostname to have a CNAME record to your Supabase project's subdomain.`,
 		RunE: func(cmd *cobra.Command, args []string) error {
-			fsys := afero.NewOsFs()
-			if err := PromptLogin(fsys); err != nil {
-				return err
-			}
-			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
-			return create.Run(ctx, projectRef, customHostname, rawOutput, fsys)
+			return create.Run(cmd.Context(), flags.ProjectRef, customHostname, rawOutput, afero.NewOsFs())
 		},
 	}
 
@@ -48,12 +41,7 @@ Expects your custom hostname to have a CNAME record to your Supabase project's s
 		Short: "Get the current custom hostname config",
 		Long:  "Retrieve the custom hostname config for your project, as stored in the Supabase platform.",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			fsys := afero.NewOsFs()
-			if err := PromptLogin(fsys); err != nil {
-				return err
-			}
-			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
-			return get.Run(ctx, projectRef, rawOutput, fsys)
+			return get.Run(cmd.Context(), flags.ProjectRef, rawOutput, afero.NewOsFs())
 		},
 	}
 
@@ -61,8 +49,7 @@ Expects your custom hostname to have a CNAME record to your Supabase project's s
 		Use:   "reverify",
 		Short: "Re-verify the custom hostname config for your project",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
-			return reverify.Run(ctx, projectRef, rawOutput, afero.NewOsFs())
+			return reverify.Run(cmd.Context(), flags.ProjectRef, rawOutput, afero.NewOsFs())
 		},
 	}
 
@@ -74,8 +61,7 @@ Expects your custom hostname to have a CNAME record to your Supabase project's s
 This reconfigures your Supabase project to respond to requests on your custom hostname.
 After the custom hostname is activated, your project's auth services will no longer function on the Supabase-provisioned subdomain.`,
 		RunE: func(cmd *cobra.Command, args []string) error {
-			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
-			return activate.Run(ctx, projectRef, rawOutput, afero.NewOsFs())
+			return activate.Run(cmd.Context(), flags.ProjectRef, rawOutput, afero.NewOsFs())
 		},
 	}
 
@@ -83,15 +69,15 @@ After the custom hostname is activated, your project's auth services will no lon
 		Use:   "delete",
 		Short: "Deletes the custom hostname config for your project",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
-			return delete.Run(ctx, projectRef, afero.NewOsFs())
+			return delete.Run(cmd.Context(), flags.ProjectRef, afero.NewOsFs())
 		},
 	}
 )
 
 func init() {
-	customHostnamesCmd.PersistentFlags().StringVar(&projectRef, "project-ref", "", "Project ref of the Supabase project.")
-	customHostnamesCmd.PersistentFlags().BoolVar(&rawOutput, "include-raw-output", false, "Include raw output (useful for debugging).")
+	persistentFlags := customHostnamesCmd.PersistentFlags()
+	persistentFlags.StringVar(&flags.ProjectRef, "project-ref", "", "Project ref of the Supabase project.")
+	persistentFlags.BoolVar(&rawOutput, "include-raw-output", false, "Include raw output (useful for debugging).")
 	customHostnamesCreateCmd.Flags().StringVar(&customHostname, "custom-hostname", "", "The custom hostname to use for your Supabase project.")
 	customHostnamesCmd.AddCommand(customHostnamesGetCmd)
 	customHostnamesCmd.AddCommand(customHostnamesCreateCmd)
diff --git a/cmd/functions.go b/cmd/functions.go
index 57232f52..4c6b72e6 100644
--- a/cmd/functions.go
+++ b/cmd/functions.go
@@ -1,12 +1,8 @@
 package cmd
 
 import (
-	"bufio"
-	"errors"
-	"fmt"
 	"os"
 	"os/signal"
-	"strings"
 
 	"github.com/spf13/afero"
 	"github.com/spf13/cobra"
@@ -17,6 +13,7 @@ import (
 	"github.com/supabase/cli/internal/functions/serve"
 	"github.com/supabase/cli/internal/login"
 	"github.com/supabase/cli/internal/utils"
+	"github.com/supabase/cli/internal/utils/flags"
 )
 
 var (
@@ -26,20 +23,13 @@ var (
 		Short:   "Manage Supabase Edge functions",
 	}
 
-	projectRef string
-
 	functionsDeleteCmd = &cobra.Command{
 		Use:   "delete <Function name>",
 		Short: "Delete a Function from Supabase",
 		Long:  "Delete a Function from the linked Supabase project. This does NOT remove the Function locally.",
 		Args:  cobra.ExactArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			fsys := afero.NewOsFs()
-			if err := PromptLogin(fsys); err != nil {
-				return err
-			}
-			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
-			return delete.Run(ctx, args[0], projectRef, fsys)
+			return delete.Run(cmd.Context(), args[0], flags.ProjectRef, afero.NewOsFs())
 		},
 	}
 
@@ -49,12 +39,7 @@ var (
 		Long:  "Download the source code for a Function from the linked Supabase project.",
 		Args:  cobra.ExactArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			fsys := afero.NewOsFs()
-			if err := PromptLogin(fsys); err != nil {
-				return err
-			}
-			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
-			return download.Run(ctx, args[0], projectRef, fsys)
+			return download.Run(cmd.Context(), args[0], flags.ProjectRef, afero.NewOsFs())
 		},
 	}
 
@@ -68,19 +53,11 @@ var (
 		Long:  "Deploy a Function to the linked Supabase project.",
 		Args:  cobra.ExactArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			fsys := afero.NewOsFs()
-			if err := PromptLogin(fsys); err != nil {
-				return err
-			}
-			if err := PromptProjectRef(fsys, cmd); err != nil {
-				return err
-			}
-			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
 			// Fallback to config if user did not set the flag.
 			if !cmd.Flags().Changed("no-verify-jwt") {
 				noVerifyJWT = nil
 			}
-			return deploy.Run(ctx, args[0], projectRef, noVerifyJWT, useLegacyBundle, importMapPath, fsys)
+			return deploy.Run(cmd.Context(), args[0], flags.ProjectRef, noVerifyJWT, useLegacyBundle, importMapPath, afero.NewOsFs())
 		},
 	}
 
@@ -88,6 +65,10 @@ var (
 		Use:   "new <Function name>",
 		Short: "Create a new Function locally",
 		Args:  cobra.ExactArgs(1),
+		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
+			cmd.SetHelpCommandGroupID(groupLocalDev)
+			return cmd.Root().PersistentPostRunE(cmd, args)
+		},
 		RunE: func(cmd *cobra.Command, args []string) error {
 			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
 			return new_.Run(ctx, args[0], afero.NewOsFs())
@@ -100,6 +81,10 @@ var (
 		Use:   "serve <Function name>",
 		Short: "Serve all Functions locally",
 		Args:  cobra.RangeArgs(0, 1),
+		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
+			cmd.SetHelpCommandGroupID(groupLocalDev)
+			return cmd.Root().PersistentPostRunE(cmd, args)
+		},
 		RunE: func(cmd *cobra.Command, args []string) error {
 			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
 			// Fallback to config if user did not set the flag.
@@ -116,9 +101,9 @@ var (
 )
 
 func init() {
-	functionsDeleteCmd.Flags().StringVar(&projectRef, "project-ref", "", "Project ref of the Supabase project.")
+	functionsDeleteCmd.Flags().StringVar(&flags.ProjectRef, "project-ref", "", "Project ref of the Supabase project.")
 	functionsDeployCmd.Flags().BoolVar(noVerifyJWT, "no-verify-jwt", false, "Disable JWT verification for the Function.")
-	functionsDeployCmd.Flags().StringVar(&projectRef, "project-ref", "", "Project ref of the Supabase project.")
+	functionsDeployCmd.Flags().StringVar(&flags.ProjectRef, "project-ref", "", "Project ref of the Supabase project.")
 	functionsDeployCmd.Flags().BoolVar(&useLegacyBundle, "legacy-bundle", false, "Use legacy bundling mechanism.")
 	functionsDeployCmd.Flags().StringVar(&importMapPath, "import-map", "", "Path to import map file.")
 	functionsServeCmd.Flags().BoolVar(noVerifyJWT, "no-verify-jwt", false, "Disable JWT verification for the Function.")
@@ -126,7 +111,7 @@ func init() {
 	functionsServeCmd.Flags().StringVar(&importMapPath, "import-map", "", "Path to import map file.")
 	functionsServeCmd.Flags().Bool("all", true, "Serve all Functions (caution: experimental feature)")
 	cobra.CheckErr(functionsServeCmd.Flags().MarkHidden("all"))
-	functionsDownloadCmd.Flags().StringVar(&projectRef, "project-ref", "", "Project ref of the Supabase project.")
+	functionsDownloadCmd.Flags().StringVar(&flags.ProjectRef, "project-ref", "", "Project ref of the Supabase project.")
 	functionsCmd.AddCommand(functionsDeleteCmd)
 	functionsCmd.AddCommand(functionsDeployCmd)
 	functionsCmd.AddCommand(functionsNewCmd)
@@ -142,26 +127,3 @@ func PromptLogin(fsys afero.Fs) error {
 		return err
 	}
 }
-
-// PromptProjectRef prompts for the project ref if there is no project ref provided
-// or if there is no linked project
-func PromptProjectRef(fsys afero.Fs, cmd *cobra.Command) error {
-	if len(projectRef) > 0 {
-		return nil
-	} else if err := utils.AssertIsLinkedFS(fsys); err == nil {
-		return nil
-	} else if strings.HasPrefix(err.Error(), "Cannot find project ref. Have you run") {
-		fmt.Fprintf(os.Stderr, `You can find your project ref from the project's dashboard home page, e.g. %s/project/<project-ref>.
-Enter your project ref: `, utils.GetSupabaseDashboardURL())
-
-		scanner := bufio.NewScanner(os.Stdin)
-		if !scanner.Scan() {
-			return errors.New("Cancelled " + utils.Aqua(cmd.UseLine()) + ".")
-		}
-
-		projectRef = strings.TrimSpace(scanner.Text())
-		return nil
-	} else {
-		return err
-	}
-}
diff --git a/cmd/link.go b/cmd/link.go
index c9386b56..b0e87923 100644
--- a/cmd/link.go
+++ b/cmd/link.go
@@ -11,6 +11,8 @@ import (
 )
 
 var (
+	projectRef string
+
 	linkCmd = &cobra.Command{
 		GroupID: groupLocalDev,
 		Use:     "link",
diff --git a/cmd/migration.go b/cmd/migration.go
index 510a07bb..4d68a019 100644
--- a/cmd/migration.go
+++ b/cmd/migration.go
@@ -107,15 +107,6 @@ func init() {
 	rootCmd.AddCommand(migrationCmd)
 }
 
-func loadLinkedProject(fsys afero.Fs) (err error) {
-	projectRef, err = utils.LoadProjectRef(fsys)
-	if err != nil {
-		return err
-	}
-	dbPassword = getPassword(projectRef)
-	return nil
-}
-
 func getPassword(projectRef string) string {
 	if password := viper.GetString("DB_PASSWORD"); len(password) > 0 {
 		return password
@@ -134,9 +125,12 @@ func parseDatabaseConfig(fsys afero.Fs) error {
 		}
 		return err
 	}
-	if err := loadLinkedProject(fsys); err != nil {
+	// Load linked project
+	projectRef, err := utils.LoadProjectRef(fsys)
+	if err != nil {
 		return err
 	}
+	dbPassword = getPassword(projectRef)
 	// Initialise connection details for hosted project
 	dbConfig.Host = utils.GetSupabaseDbHost(projectRef)
 	dbConfig.Port = 6543
diff --git a/cmd/orgs.go b/cmd/orgs.go
index 6b8ec627..a68933c2 100644
--- a/cmd/orgs.go
+++ b/cmd/orgs.go
@@ -1,9 +1,6 @@
 package cmd
 
 import (
-	"os"
-	"os/signal"
-
 	"github.com/spf13/afero"
 	"github.com/spf13/cobra"
 	"github.com/supabase/cli/internal/orgs/list"
@@ -21,8 +18,7 @@ var (
 		Short: "List all organizations",
 		Long:  "List all organizations the logged-in user belongs.",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
-			return list.Run(ctx, afero.NewOsFs())
+			return list.Run(cmd.Context(), afero.NewOsFs())
 		},
 	}
 )
diff --git a/cmd/projects.go b/cmd/projects.go
index a6a490d8..a887d328 100644
--- a/cmd/projects.go
+++ b/cmd/projects.go
@@ -4,7 +4,6 @@ import (
 	"errors"
 	"fmt"
 	"os"
-	"os/signal"
 	"sort"
 	"strings"
 
@@ -55,8 +54,7 @@ var (
 				fmt.Fprintln(os.Stderr, printKeyValue("Creating project", name))
 				cobra.CheckErr(PromptCreateFlags(cmd))
 			}
-			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
-			return create.Run(ctx, api.CreateProjectBody{
+			return create.Run(cmd.Context(), api.CreateProjectBody{
 				Name:           name,
 				OrganizationId: orgId,
 				DbPass:         dbPassword,
@@ -71,8 +69,7 @@ var (
 		Short: "List all Supabase projects",
 		Long:  "List all Supabase projects the logged-in user can access.",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
-			return list.Run(ctx, afero.NewOsFs())
+			return list.Run(cmd.Context(), afero.NewOsFs())
 		},
 	}
 )
diff --git a/cmd/restrictions.go b/cmd/restrictions.go
index 394638c9..419c7029 100644
--- a/cmd/restrictions.go
+++ b/cmd/restrictions.go
@@ -1,13 +1,11 @@
 package cmd
 
 import (
-	"os"
-	"os/signal"
-
 	"github.com/spf13/afero"
 	"github.com/spf13/cobra"
 	"github.com/supabase/cli/internal/restrictions/get"
 	"github.com/supabase/cli/internal/restrictions/update"
+	"github.com/supabase/cli/internal/utils/flags"
 )
 
 var (
@@ -24,12 +22,7 @@ var (
 		Use:   "update",
 		Short: "Update network restrictions",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			fsys := afero.NewOsFs()
-			if err := PromptLogin(fsys); err != nil {
-				return err
-			}
-			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
-			return update.Run(ctx, projectRef, dbCidrsToAllow, bypassCidrChecks, fsys)
+			return update.Run(cmd.Context(), flags.ProjectRef, dbCidrsToAllow, bypassCidrChecks, afero.NewOsFs())
 		},
 	}
 
@@ -37,18 +30,13 @@ var (
 		Use:   "get",
 		Short: "Get the current network restrictions",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			fsys := afero.NewOsFs()
-			if err := PromptLogin(fsys); err != nil {
-				return err
-			}
-			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
-			return get.Run(ctx, projectRef, fsys)
+			return get.Run(cmd.Context(), flags.ProjectRef, afero.NewOsFs())
 		},
 	}
 )
 
 func init() {
-	restrictionsCmd.PersistentFlags().StringVar(&projectRef, "project-ref", "", "Project ref of the Supabase project.")
+	restrictionsCmd.PersistentFlags().StringVar(&flags.ProjectRef, "project-ref", "", "Project ref of the Supabase project.")
 	restrictionsUpdateCmd.Flags().StringSliceVar(&dbCidrsToAllow, "db-allow-cidr", []string{}, "CIDR to allow DB connections from.")
 	restrictionsUpdateCmd.Flags().BoolVar(&bypassCidrChecks, "bypass-cidr-checks", false, "Bypass some of the CIDR validation checks.")
 	restrictionsCmd.AddCommand(restrictionsGetCmd)
diff --git a/cmd/root.go b/cmd/root.go
index 4a158c56..896f1bc0 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -4,6 +4,7 @@ import (
 	"errors"
 	"fmt"
 	"os"
+	"os/signal"
 	"strings"
 
 	"github.com/mitchellh/mapstructure"
@@ -11,6 +12,7 @@ import (
 	"github.com/spf13/cobra"
 	"github.com/spf13/viper"
 	"github.com/supabase/cli/internal/utils"
+	"github.com/supabase/cli/internal/utils/flags"
 )
 
 const (
@@ -18,6 +20,16 @@ const (
 	groupManagementAPI = "management-api"
 )
 
+func IsManagementAPI(cmd *cobra.Command) bool {
+	for cmd != cmd.Root() {
+		if cmd.GroupID == groupManagementAPI {
+			return true
+		}
+		cmd = cmd.Parent()
+	}
+	return false
+}
+
 var experimental = []*cobra.Command{
 	bansCmd,
 	restrictionsCmd,
@@ -44,19 +56,31 @@ var (
 				return errors.New("must set the --experimental flag to run this command")
 			}
 			cmd.SilenceUsage = true
-			if viper.GetBool("DEBUG") {
-				cmd.SetContext(utils.WithTraceContext(cmd.Context()))
-			} else {
-				utils.CmdSuggestion = "Try rerunning the command with --debug to troubleshoot the error."
+			// Change workdir
+			fsys := afero.NewOsFs()
+			if err := changeWorkDir(fsys); err != nil {
+				return err
 			}
-			workdir := viper.GetString("WORKDIR")
-			if workdir == "" {
-				var err error
-				if workdir, err = utils.GetProjectRoot(afero.NewOsFs()); err != nil {
+			// Add common flags
+			ctx := cmd.Context()
+			if IsManagementAPI(cmd) {
+				if err := PromptLogin(fsys); err != nil {
 					return err
 				}
+				if cmd.Flags().Lookup("project-ref") != nil {
+					if err := flags.ParseProjectRef(fsys); err != nil {
+						return err
+					}
+				}
+				ctx, _ = signal.NotifyContext(ctx, os.Interrupt)
+			}
+			// Prepare context
+			if viper.GetBool("DEBUG") {
+				cmd.SetContext(utils.WithTraceContext(ctx))
+			} else {
+				utils.CmdSuggestion = "Try rerunning the command with --debug to troubleshoot the error."
 			}
-			return os.Chdir(workdir)
+			return nil
 		},
 	}
 )
@@ -104,3 +128,14 @@ func init() {
 func GetRootCmd() *cobra.Command {
 	return rootCmd
 }
+
+func changeWorkDir(fsys afero.Fs) error {
+	workdir := viper.GetString("WORKDIR")
+	if workdir == "" {
+		var err error
+		if workdir, err = utils.GetProjectRoot(fsys); err != nil {
+			return err
+		}
+	}
+	return os.Chdir(workdir)
+}
diff --git a/cmd/secrets.go b/cmd/secrets.go
index dece7e30..a6f123b6 100644
--- a/cmd/secrets.go
+++ b/cmd/secrets.go
@@ -1,14 +1,12 @@
 package cmd
 
 import (
-	"os"
-	"os/signal"
-
 	"github.com/spf13/afero"
 	"github.com/spf13/cobra"
 	"github.com/supabase/cli/internal/secrets/list"
 	"github.com/supabase/cli/internal/secrets/set"
 	"github.com/supabase/cli/internal/secrets/unset"
+	"github.com/supabase/cli/internal/utils/flags"
 )
 
 var (
@@ -23,8 +21,7 @@ var (
 		Short: "List all secrets on Supabase",
 		Long:  "List all secrets in the linked project.",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
-			return list.Run(ctx, afero.NewOsFs())
+			return list.Run(cmd.Context(), flags.ProjectRef, afero.NewOsFs())
 		},
 	}
 
@@ -37,8 +34,7 @@ var (
 			if err != nil {
 				return err
 			}
-			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
-			return set.Run(ctx, envFilePath, args, afero.NewOsFs())
+			return set.Run(cmd.Context(), flags.ProjectRef, envFilePath, args, afero.NewOsFs())
 		},
 	}
 
@@ -47,13 +43,13 @@ var (
 		Short: "Unset a secret(s) on Supabase",
 		Long:  "Unset a secret(s) from the linked Supabase project.",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
-			return unset.Run(ctx, args, afero.NewOsFs())
+			return unset.Run(cmd.Context(), flags.ProjectRef, args, afero.NewOsFs())
 		},
 	}
 )
 
 func init() {
+	secretsCmd.PersistentFlags().StringVar(&flags.ProjectRef, "project-ref", "", "Project ref of the Supabase project.")
 	secretsSetCmd.Flags().String("env-file", "", "Read secrets from a .env file.")
 	secretsCmd.AddCommand(secretsListCmd)
 	secretsCmd.AddCommand(secretsSetCmd)
diff --git a/cmd/sslEnforcement.go b/cmd/sslEnforcement.go
index 7230d2b4..ec8469d1 100644
--- a/cmd/sslEnforcement.go
+++ b/cmd/sslEnforcement.go
@@ -2,13 +2,12 @@ package cmd
 
 import (
 	"fmt"
-	"os"
-	"os/signal"
 
 	"github.com/spf13/afero"
 	"github.com/spf13/cobra"
 	"github.com/supabase/cli/internal/ssl_enforcement/get"
 	"github.com/supabase/cli/internal/ssl_enforcement/update"
+	"github.com/supabase/cli/internal/utils/flags"
 )
 
 var (
@@ -25,15 +24,10 @@ var (
 		Use:   "update",
 		Short: "Update SSL enforcement configuration",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			fsys := afero.NewOsFs()
-			if err := PromptLogin(fsys); err != nil {
-				return err
-			}
-			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
 			if !dbEnforceSsl && !dbDisableSsl {
 				return fmt.Errorf("enable/disable not specified")
 			}
-			return update.Run(ctx, projectRef, dbEnforceSsl, fsys)
+			return update.Run(cmd.Context(), flags.ProjectRef, dbEnforceSsl, afero.NewOsFs())
 		},
 	}
 
@@ -41,18 +35,13 @@ var (
 		Use:   "get",
 		Short: "Get the current SSL enforcement configuration",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			fsys := afero.NewOsFs()
-			if err := PromptLogin(fsys); err != nil {
-				return err
-			}
-			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
-			return get.Run(ctx, projectRef, fsys)
+			return get.Run(cmd.Context(), flags.ProjectRef, afero.NewOsFs())
 		},
 	}
 )
 
 func init() {
-	sslEnforcementCmd.PersistentFlags().StringVar(&projectRef, "project-ref", "", "Project ref of the Supabase project.")
+	sslEnforcementCmd.PersistentFlags().StringVar(&flags.ProjectRef, "project-ref", "", "Project ref of the Supabase project.")
 	sslEnforcementUpdateCmd.Flags().BoolVar(&dbEnforceSsl, "enable-db-ssl-enforcement", false, "Whether the DB should enable SSL enforcement for all external connections.")
 	sslEnforcementUpdateCmd.Flags().BoolVar(&dbDisableSsl, "disable-db-ssl-enforcement", false, "Whether the DB should disable SSL enforcement for all external connections.")
 	sslEnforcementUpdateCmd.MarkFlagsMutuallyExclusive("enable-db-ssl-enforcement", "disable-db-ssl-enforcement")
diff --git a/cmd/sso.go b/cmd/sso.go
index d9ece8a4..64fadd41 100644
--- a/cmd/sso.go
+++ b/cmd/sso.go
@@ -1,12 +1,8 @@
 package cmd
 
 import (
-	"errors"
 	"fmt"
-	"os"
-	"os/signal"
 
-	"github.com/spf13/afero"
 	"github.com/spf13/cobra"
 
 	"github.com/supabase/cli/internal/sso/create"
@@ -16,6 +12,7 @@ import (
 	"github.com/supabase/cli/internal/sso/remove"
 	"github.com/supabase/cli/internal/sso/update"
 	"github.com/supabase/cli/internal/utils"
+	"github.com/supabase/cli/internal/utils/flags"
 )
 
 var (
@@ -23,24 +20,6 @@ var (
 		GroupID: groupManagementAPI,
 		Use:     "sso",
 		Short:   "Manage Single Sign-On (SSO) authentication for projects",
-		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
-			if err := cmd.Root().PersistentPreRunE(cmd, args); err != nil {
-				return err
-			}
-			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
-			cmd.SetContext(ctx)
-
-			fsys := afero.NewOsFs()
-			if err := PromptProjectRef(fsys, cmd); err != nil {
-				return err
-			}
-
-			// Validate project ref
-			if !utils.ProjectRefPattern.MatchString(projectRef) {
-				return errors.New("invalid project ref format. Must be like `abcdefghijklmnopqrst`")
-			}
-			return nil
-		},
 	}
 
 	ssoProviderType = utils.EnumFlag{
@@ -66,7 +45,7 @@ var (
 		Example: `  supabase sso add --type saml --project-ref mwjylndxudmiehsxhmmz --metadata-url 'https://...' --domains example.com`,
 		RunE: func(cmd *cobra.Command, args []string) error {
 			return create.Run(cmd.Context(), create.RunParams{
-				ProjectRef:        projectRef,
+				ProjectRef:        flags.ProjectRef,
 				Type:              ssoProviderType.String(),
 				Format:            ssoOutput.Value,
 				MetadataFile:      ssoMetadataFile,
@@ -88,7 +67,7 @@ var (
 				return fmt.Errorf("identity provider ID %q is not a UUID", args[0])
 			}
 
-			return remove.Run(cmd.Context(), projectRef, args[0], ssoOutput.Value)
+			return remove.Run(cmd.Context(), flags.ProjectRef, args[0], ssoOutput.Value)
 		},
 	}
 
@@ -103,7 +82,7 @@ var (
 			}
 
 			return update.Run(cmd.Context(), update.RunParams{
-				ProjectRef: projectRef,
+				ProjectRef: flags.ProjectRef,
 				ProviderID: args[0],
 				Format:     ssoOutput.Value,
 
@@ -133,7 +112,7 @@ var (
 				format = utils.OutputMetadata
 			}
 
-			return get.Run(cmd.Context(), projectRef, args[0], format)
+			return get.Run(cmd.Context(), flags.ProjectRef, args[0], format)
 		},
 	}
 
@@ -142,7 +121,7 @@ var (
 		Short:   "List all SSO identity providers for a project",
 		Example: `  supabase sso list --project-ref mwjylndxudmiehsxhmmz`,
 		RunE: func(cmd *cobra.Command, args []string) error {
-			return list.Run(cmd.Context(), projectRef, ssoOutput.Value)
+			return list.Run(cmd.Context(), flags.ProjectRef, ssoOutput.Value)
 		},
 	}
 
@@ -151,16 +130,17 @@ var (
 		Short:   "Returns the SAML SSO settings required for the identity provider",
 		Example: `  supabase sso info --project-ref mwjylndxudmiehsxhmmz`,
 		RunE: func(cmd *cobra.Command, args []string) error {
-			return info.Run(cmd.Context(), projectRef, ssoOutput.Value)
+			return info.Run(cmd.Context(), flags.ProjectRef, ssoOutput.Value)
 		},
 	}
 )
 
 func init() {
+	persistentFlags := ssoCmd.PersistentFlags()
+	persistentFlags.StringVar(&flags.ProjectRef, "project-ref", "", "Project ref of the Supabase project.")
+	persistentFlags.VarP(&ssoOutput, "output", "o", "Output format")
 	ssoAddFlags := ssoAddCmd.Flags()
 	ssoAddFlags.VarP(&ssoProviderType, "type", "t", "Type of identity provider (according to supported protocol).")
-	ssoAddFlags.VarP(&ssoOutput, "output", "o", "Output format")
-	ssoAddFlags.StringVar(&projectRef, "project-ref", "", "Project on which to add this identity provider.")
 	ssoAddFlags.StringSliceVar(&ssoDomains, "domains", nil, "Comma separated list of email domains to associate with the added identity provider.")
 	ssoAddFlags.StringVar(&ssoMetadataFile, "metadata-file", "", "File containing a SAML 2.0 Metadata XML document describing the identity provider.")
 	ssoAddFlags.StringVar(&ssoMetadataURL, "metadata-url", "", "URL pointing to a SAML 2.0 Metadata XML document describing the identity provider.")
@@ -171,13 +151,7 @@ func init() {
 	cobra.CheckErr(ssoAddCmd.MarkFlagFilename("metadata-file", "xml"))
 	cobra.CheckErr(ssoAddCmd.MarkFlagFilename("attribute-mapping-file", "json"))
 
-	ssoRemoveFlags := ssoRemoveCmd.Flags()
-	ssoRemoveFlags.VarP(&ssoOutput, "output", "o", "Output format")
-	ssoRemoveFlags.StringVar(&projectRef, "project-ref", "", "Project on which to remove this identity provider.")
-
 	ssoUpdateFlags := ssoUpdateCmd.Flags()
-	ssoUpdateFlags.VarP(&ssoOutput, "output", "o", "Output format")
-	ssoUpdateFlags.StringVar(&projectRef, "project-ref", "", "Project on which to update this identity provider.")
 	ssoUpdateFlags.StringSliceVar(&ssoDomains, "domains", []string{}, "Replace domains with this comma separated list of email domains.")
 	ssoUpdateFlags.StringSliceVar(&ssoAddDomains, "add-domains", []string{}, "Add this comma separated list of email domains to the identity provider.")
 	ssoUpdateFlags.StringSliceVar(&ssoRemoveDomains, "remove-domains", []string{}, "Remove this comma separated list of email domains from the identity provider.")
@@ -192,18 +166,8 @@ func init() {
 	cobra.CheckErr(ssoUpdateCmd.MarkFlagFilename("attribute-mapping-file", "json"))
 
 	ssoShowFlags := ssoShowCmd.Flags()
-	ssoShowFlags.VarP(&ssoOutput, "output", "o", "Output format")
-	ssoShowFlags.StringVar(&projectRef, "project-ref", "", "Project on which to get this identity provider.")
 	ssoShowFlags.BoolVar(&ssoMetadata, "metadata", false, "Show SAML 2.0 XML Metadata only")
 
-	ssoListFlags := ssoListCmd.Flags()
-	ssoListFlags.VarP(&ssoOutput, "output", "o", "Output format")
-	ssoListFlags.StringVar(&projectRef, "project-ref", "", "Project on which to list identity providers.")
-
-	ssoInfoFlags := ssoInfoCmd.Flags()
-	ssoInfoFlags.VarP(&ssoOutput, "output", "o", "Output format")
-	ssoInfoFlags.StringVar(&projectRef, "project-ref", "", "Project on which to show project SAML SSO settings.")
-
 	ssoCmd.AddCommand(ssoAddCmd)
 	ssoCmd.AddCommand(ssoRemoveCmd)
 	ssoCmd.AddCommand(ssoUpdateCmd)
diff --git a/cmd/vanitySubdomains.go b/cmd/vanitySubdomains.go
index d165298d..dd3608c5 100644
--- a/cmd/vanitySubdomains.go
+++ b/cmd/vanitySubdomains.go
@@ -1,11 +1,9 @@
 package cmd
 
 import (
-	"os"
-	"os/signal"
-
 	"github.com/spf13/afero"
 	"github.com/spf13/cobra"
+	"github.com/supabase/cli/internal/utils/flags"
 	"github.com/supabase/cli/internal/vanity_subdomains/activate"
 	"github.com/supabase/cli/internal/vanity_subdomains/check"
 	"github.com/supabase/cli/internal/vanity_subdomains/delete"
@@ -33,12 +31,7 @@ This reconfigures your Supabase project to respond to requests on your vanity su
 After the vanity subdomain is activated, your project's auth services will no longer function on the {project-ref}.{supabase-domain} hostname.
 `,
 		RunE: func(cmd *cobra.Command, args []string) error {
-			fsys := afero.NewOsFs()
-			if err := PromptLogin(fsys); err != nil {
-				return err
-			}
-			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
-			return activate.Run(ctx, projectRef, desiredSubdomain, fsys)
+			return activate.Run(cmd.Context(), flags.ProjectRef, desiredSubdomain, afero.NewOsFs())
 		},
 	}
 
@@ -46,12 +39,7 @@ After the vanity subdomain is activated, your project's auth services will no lo
 		Use:   "get",
 		Short: "Get the current vanity subdomain",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			fsys := afero.NewOsFs()
-			if err := PromptLogin(fsys); err != nil {
-				return err
-			}
-			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
-			return get.Run(ctx, projectRef, fsys)
+			return get.Run(cmd.Context(), flags.ProjectRef, afero.NewOsFs())
 		},
 	}
 
@@ -59,8 +47,7 @@ After the vanity subdomain is activated, your project's auth services will no lo
 		Use:   "check-availability",
 		Short: "Checks if a desired subdomain is available for use",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
-			return check.Run(ctx, projectRef, desiredSubdomain, afero.NewOsFs())
+			return check.Run(cmd.Context(), flags.ProjectRef, desiredSubdomain, afero.NewOsFs())
 		},
 	}
 
@@ -69,14 +56,13 @@ After the vanity subdomain is activated, your project's auth services will no lo
 		Short: "Deletes a project's vanity subdomain",
 		Long:  `Deletes the vanity subdomain for a project, and reverts to using the project ref for routing.`,
 		RunE: func(cmd *cobra.Command, args []string) error {
-			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
-			return delete.Run(ctx, projectRef, afero.NewOsFs())
+			return delete.Run(cmd.Context(), flags.ProjectRef, afero.NewOsFs())
 		},
 	}
 )
 
 func init() {
-	vanityCmd.PersistentFlags().StringVar(&projectRef, "project-ref", "", "Project ref of the Supabase project.")
+	vanityCmd.PersistentFlags().StringVar(&flags.ProjectRef, "project-ref", "", "Project ref of the Supabase project.")
 	vanityActivateCmd.Flags().StringVar(&desiredSubdomain, "desired-subdomain", "", "The desired vanity subdomain to use for your Supabase project.")
 	vanityCheckCmd.Flags().StringVar(&desiredSubdomain, "desired-subdomain", "", "The desired vanity subdomain to use for your Supabase project.")
 	vanityCmd.AddCommand(vanityGetCmd)
diff --git a/internal/bans/get/get.go b/internal/bans/get/get.go
index c481ceeb..75527dc7 100644
--- a/internal/bans/get/get.go
+++ b/internal/bans/get/get.go
@@ -9,21 +9,8 @@ import (
 	"github.com/supabase/cli/internal/utils"
 )
 
-func Run(ctx context.Context, projectRefArg string, fsys afero.Fs) error {
+func Run(ctx context.Context, projectRef string, fsys afero.Fs) error {
 	// 1. Sanity checks.
-	projectRef := projectRefArg
-	{
-		if len(projectRefArg) == 0 {
-			ref, err := utils.LoadProjectRef(fsys)
-			if err != nil {
-				return err
-			}
-			projectRef = ref
-		} else if !utils.ProjectRefPattern.MatchString(projectRef) {
-			return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
-		}
-	}
-
 	// 2. get network bans
 	{
 		resp, err := utils.GetSupabase().GetNetworkBansWithResponse(ctx, projectRef)
diff --git a/internal/bans/update/update.go b/internal/bans/update/update.go
index fb60e4bf..694e12e7 100644
--- a/internal/bans/update/update.go
+++ b/internal/bans/update/update.go
@@ -21,21 +21,9 @@ func validateIps(ips []string) error {
 	return nil
 }
 
-func Run(ctx context.Context, projectRefArg string, dbIpsToUnban []string, fsys afero.Fs) error {
-	// 1. Sanity checks.
-	projectRef := projectRefArg
-
+func Run(ctx context.Context, projectRef string, dbIpsToUnban []string, fsys afero.Fs) error {
 	// 1. sanity checks
 	{
-		if len(projectRefArg) == 0 {
-			ref, err := utils.LoadProjectRef(fsys)
-			if err != nil {
-				return err
-			}
-			projectRef = ref
-		} else if !utils.ProjectRefPattern.MatchString(projectRef) {
-			return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
-		}
 		err := validateIps(dbIpsToUnban)
 		if err != nil {
 			return err
diff --git a/internal/functions/delete/delete.go b/internal/functions/delete/delete.go
index 079ffc76..3f9c966c 100644
--- a/internal/functions/delete/delete.go
+++ b/internal/functions/delete/delete.go
@@ -10,19 +10,9 @@ import (
 	"github.com/supabase/cli/internal/utils"
 )
 
-func Run(ctx context.Context, slug string, projectRefArg string, fsys afero.Fs) error {
+func Run(ctx context.Context, slug string, projectRef string, fsys afero.Fs) error {
 	// 1. Sanity checks.
-	projectRef := projectRefArg
 	{
-		if len(projectRefArg) == 0 {
-			ref, err := utils.LoadProjectRef(fsys)
-			if err != nil {
-				return err
-			}
-			projectRef = ref
-		} else if !utils.ProjectRefPattern.MatchString(projectRefArg) {
-			return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
-		}
 		if err := utils.ValidateFunctionSlug(slug); err != nil {
 			return err
 		}
diff --git a/internal/functions/deploy/deploy.go b/internal/functions/deploy/deploy.go
index 85eed07c..c6b192a4 100644
--- a/internal/functions/deploy/deploy.go
+++ b/internal/functions/deploy/deploy.go
@@ -20,20 +20,10 @@ import (
 
 const eszipContentType = "application/vnd.denoland.eszip"
 
-func Run(ctx context.Context, slug string, projectRefArg string, noVerifyJWT *bool, useLegacyBundle bool, importMapPath string, fsys afero.Fs) error {
+func Run(ctx context.Context, slug string, projectRef string, noVerifyJWT *bool, useLegacyBundle bool, importMapPath string, fsys afero.Fs) error {
 	// 1. Sanity checks.
-	projectRef := projectRefArg
 	var scriptDir *utils.DenoScriptDir
 	{
-		if len(projectRefArg) == 0 {
-			ref, err := utils.LoadProjectRef(fsys)
-			if err != nil {
-				return err
-			}
-			projectRef = ref
-		} else if !utils.ProjectRefPattern.MatchString(projectRef) {
-			return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
-		}
 		// Load function config if any for fallbacks for some flags, but continue on error.
 		_ = utils.LoadConfigFS(fsys)
 		// Ensure noVerifyJWT is not nil.
diff --git a/internal/functions/download/download.go b/internal/functions/download/download.go
index 131e5463..7a7ab9f6 100644
--- a/internal/functions/download/download.go
+++ b/internal/functions/download/download.go
@@ -14,20 +14,9 @@ import (
 	"github.com/supabase/cli/internal/utils"
 )
 
-func Run(ctx context.Context, slug string, projectRefArg string, fsys afero.Fs) error {
+func Run(ctx context.Context, slug string, projectRef string, fsys afero.Fs) error {
 	// 1. Sanity checks.
-	projectRef := projectRefArg
-	var scriptDir *utils.DenoScriptDir
 	{
-		if len(projectRefArg) == 0 {
-			ref, err := utils.LoadProjectRef(fsys)
-			if err != nil {
-				return err
-			}
-			projectRef = ref
-		} else if !utils.ProjectRefPattern.MatchString(projectRefArg) {
-			return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
-		}
 		if err := utils.ValidateFunctionSlug(slug); err != nil {
 			return err
 		}
@@ -36,8 +25,7 @@ func Run(ctx context.Context, slug string, projectRefArg string, fsys afero.Fs)
 		return err
 	}
 
-	var err error
-	scriptDir, err = utils.CopyDenoScripts(ctx, fsys)
+	scriptDir, err := utils.CopyDenoScripts(ctx, fsys)
 	if err != nil {
 		return err
 	}
diff --git a/internal/hostnames/activate/activate.go b/internal/hostnames/activate/activate.go
index b5f30b35..40d4acc7 100644
--- a/internal/hostnames/activate/activate.go
+++ b/internal/hostnames/activate/activate.go
@@ -10,19 +10,9 @@ import (
 	"github.com/supabase/cli/internal/utils"
 )
 
-func Run(ctx context.Context, projectRefArg string, includeRawOutput bool, fsys afero.Fs) error {
+func Run(ctx context.Context, projectRef string, includeRawOutput bool, fsys afero.Fs) error {
 	// 1. Sanity checks.
-	projectRef := projectRefArg
 	{
-		if len(projectRefArg) == 0 {
-			ref, err := utils.LoadProjectRef(fsys)
-			if err != nil {
-				return err
-			}
-			projectRef = ref
-		} else if !utils.ProjectRefPattern.MatchString(projectRef) {
-			return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
-		}
 		resp, err := hostnames.GetCustomHostnameConfig(ctx, projectRef)
 		if err != nil {
 			return err
diff --git a/internal/hostnames/create/create.go b/internal/hostnames/create/create.go
index eb8667fb..08a4b1e5 100644
--- a/internal/hostnames/create/create.go
+++ b/internal/hostnames/create/create.go
@@ -12,20 +12,10 @@ import (
 	"github.com/supabase/cli/pkg/api"
 )
 
-func Run(ctx context.Context, projectRefArg string, customHostname string, includeRawOutput bool, fsys afero.Fs) error {
+func Run(ctx context.Context, projectRef string, customHostname string, includeRawOutput bool, fsys afero.Fs) error {
 	// 1. Sanity checks.
-	projectRef := projectRefArg
 	hostname := strings.TrimSpace(customHostname)
 	{
-		if len(projectRefArg) == 0 {
-			ref, err := utils.LoadProjectRef(fsys)
-			if err != nil {
-				return err
-			}
-			projectRef = ref
-		} else if !utils.ProjectRefPattern.MatchString(projectRef) {
-			return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
-		}
 		if len(hostname) == 0 {
 			return errors.New("non-empty custom hostname expected")
 		}
diff --git a/internal/hostnames/delete/delete.go b/internal/hostnames/delete/delete.go
index 3572c911..bf2dd7c0 100644
--- a/internal/hostnames/delete/delete.go
+++ b/internal/hostnames/delete/delete.go
@@ -9,21 +9,8 @@ import (
 	"github.com/supabase/cli/internal/utils"
 )
 
-func Run(ctx context.Context, projectRefArg string, fsys afero.Fs) error {
+func Run(ctx context.Context, projectRef string, fsys afero.Fs) error {
 	// 1. Sanity checks.
-	projectRef := projectRefArg
-	{
-		if len(projectRefArg) == 0 {
-			ref, err := utils.LoadProjectRef(fsys)
-			if err != nil {
-				return err
-			}
-			projectRef = ref
-		} else if !utils.ProjectRefPattern.MatchString(projectRef) {
-			return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
-		}
-	}
-
 	// 2. delete config
 	{
 		resp, err := utils.GetSupabase().RemoveCustomHostnameConfigWithResponse(ctx, projectRef)
diff --git a/internal/hostnames/get/get.go b/internal/hostnames/get/get.go
index 89fa7800..9e17c19f 100644
--- a/internal/hostnames/get/get.go
+++ b/internal/hostnames/get/get.go
@@ -2,29 +2,14 @@ package get
 
 import (
 	"context"
-	"errors"
 	"fmt"
 
 	"github.com/spf13/afero"
 	"github.com/supabase/cli/internal/hostnames"
-	"github.com/supabase/cli/internal/utils"
 )
 
-func Run(ctx context.Context, projectRefArg string, includeRawOutput bool, fsys afero.Fs) error {
+func Run(ctx context.Context, projectRef string, includeRawOutput bool, fsys afero.Fs) error {
 	// 1. Sanity checks.
-	projectRef := projectRefArg
-	{
-		if len(projectRefArg) == 0 {
-			ref, err := utils.LoadProjectRef(fsys)
-			if err != nil {
-				return err
-			}
-			projectRef = ref
-		} else if !utils.ProjectRefPattern.MatchString(projectRef) {
-			return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
-		}
-	}
-
 	// 2. activate custom hostname config
 	{
 		resp, err := hostnames.GetCustomHostnameConfig(ctx, projectRef)
diff --git a/internal/hostnames/reverify/reverify.go b/internal/hostnames/reverify/reverify.go
index f60aa432..63e96e28 100644
--- a/internal/hostnames/reverify/reverify.go
+++ b/internal/hostnames/reverify/reverify.go
@@ -10,21 +10,8 @@ import (
 	"github.com/supabase/cli/internal/utils"
 )
 
-func Run(ctx context.Context, projectRefArg string, includeRawOutput bool, fsys afero.Fs) error {
+func Run(ctx context.Context, projectRef string, includeRawOutput bool, fsys afero.Fs) error {
 	// 1. Sanity checks.
-	projectRef := projectRefArg
-	{
-		if len(projectRefArg) == 0 {
-			ref, err := utils.LoadProjectRef(fsys)
-			if err != nil {
-				return err
-			}
-			projectRef = ref
-		} else if !utils.ProjectRefPattern.MatchString(projectRef) {
-			return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
-		}
-	}
-
 	// 2. attempt to re-verify custom hostname config
 	{
 		resp, err := utils.GetSupabase().ReverifyWithResponse(ctx, projectRef)
diff --git a/internal/link/link.go b/internal/link/link.go
index e95e46a7..21f227bb 100644
--- a/internal/link/link.go
+++ b/internal/link/link.go
@@ -24,8 +24,8 @@ var updatedConfig map[string]interface{} = make(map[string]interface{})
 
 func PreRun(projectRef string, fsys afero.Fs) error {
 	// Sanity checks
-	if !utils.ProjectRefPattern.MatchString(projectRef) {
-		return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
+	if err := utils.AssertProjectRefIsValid(projectRef); err != nil {
+		return err
 	}
 	return utils.LoadConfigFS(fsys)
 }
diff --git a/internal/restrictions/get/get.go b/internal/restrictions/get/get.go
index 279070aa..15462b04 100644
--- a/internal/restrictions/get/get.go
+++ b/internal/restrictions/get/get.go
@@ -9,21 +9,8 @@ import (
 	"github.com/supabase/cli/internal/utils"
 )
 
-func Run(ctx context.Context, projectRefArg string, fsys afero.Fs) error {
+func Run(ctx context.Context, projectRef string, fsys afero.Fs) error {
 	// 1. Sanity checks.
-	projectRef := projectRefArg
-	{
-		if len(projectRefArg) == 0 {
-			ref, err := utils.LoadProjectRef(fsys)
-			if err != nil {
-				return err
-			}
-			projectRef = ref
-		} else if !utils.ProjectRefPattern.MatchString(projectRef) {
-			return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
-		}
-	}
-
 	// 2. get network restrictions
 	{
 		resp, err := utils.GetSupabase().GetNetworkRestrictionsWithResponse(ctx, projectRef)
diff --git a/internal/restrictions/update/update.go b/internal/restrictions/update/update.go
index 2811b074..7eace20d 100644
--- a/internal/restrictions/update/update.go
+++ b/internal/restrictions/update/update.go
@@ -27,21 +27,9 @@ func validateCidrs(cidrs []string, bypassChecks bool) error {
 	return nil
 }
 
-func Run(ctx context.Context, projectRefArg string, dbCidrsToAllow []string, bypassCidrChecks bool, fsys afero.Fs) error {
-	// 1. Sanity checks.
-	projectRef := projectRefArg
-
+func Run(ctx context.Context, projectRef string, dbCidrsToAllow []string, bypassCidrChecks bool, fsys afero.Fs) error {
 	// 1. sanity checks
 	{
-		if len(projectRefArg) == 0 {
-			ref, err := utils.LoadProjectRef(fsys)
-			if err != nil {
-				return err
-			}
-			projectRef = ref
-		} else if !utils.ProjectRefPattern.MatchString(projectRef) {
-			return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
-		}
 		err := validateCidrs(dbCidrsToAllow, bypassCidrChecks)
 		if err != nil {
 			return err
diff --git a/internal/secrets/list/list.go b/internal/secrets/list/list.go
index 1d39ee38..c4a04e79 100644
--- a/internal/secrets/list/list.go
+++ b/internal/secrets/list/list.go
@@ -12,21 +12,10 @@ import (
 	"github.com/supabase/cli/internal/utils"
 )
 
-func Run(ctx context.Context, fsys afero.Fs) error {
+func Run(ctx context.Context, projectRef string, fsys afero.Fs) error {
 	// 1. Sanity checks.
-	{
-		if err := utils.AssertSupabaseCliIsSetUpFS(fsys); err != nil {
-			return err
-		}
-	}
-
 	// 2. Print secrets.
 	{
-		projectRef, err := utils.LoadProjectRef(fsys)
-		if err != nil {
-			return err
-		}
-
 		resp, err := utils.GetSupabase().GetSecretsWithResponse(ctx, projectRef)
 		if err != nil {
 			return err
diff --git a/internal/secrets/set/set.go b/internal/secrets/set/set.go
index ff8fb737..d3d066f1 100644
--- a/internal/secrets/set/set.go
+++ b/internal/secrets/set/set.go
@@ -13,21 +13,10 @@ import (
 	"github.com/supabase/cli/pkg/api"
 )
 
-func Run(ctx context.Context, envFilePath string, args []string, fsys afero.Fs) error {
+func Run(ctx context.Context, projectRef, envFilePath string, args []string, fsys afero.Fs) error {
 	// 1. Sanity checks.
-	{
-		if err := utils.AssertSupabaseCliIsSetUpFS(fsys); err != nil {
-			return err
-		}
-	}
-
 	// 2. Set secret(s).
 	{
-		projectRef, err := utils.LoadProjectRef(fsys)
-		if err != nil {
-			return err
-		}
-
 		var secrets api.CreateSecretsJSONBody
 		if envFilePath != "" {
 			envMap, err := godotenv.Read(envFilePath)
diff --git a/internal/secrets/unset/unset.go b/internal/secrets/unset/unset.go
index 1a32a105..60ea7bc9 100644
--- a/internal/secrets/unset/unset.go
+++ b/internal/secrets/unset/unset.go
@@ -10,21 +10,10 @@ import (
 	"github.com/supabase/cli/internal/utils"
 )
 
-func Run(ctx context.Context, args []string, fsys afero.Fs) error {
+func Run(ctx context.Context, projectRef string, args []string, fsys afero.Fs) error {
 	// 1. Sanity checks.
-	{
-		if err := utils.AssertSupabaseCliIsSetUpFS(fsys); err != nil {
-			return err
-		}
-	}
-
 	// 2. Unset secret(s).
 	{
-		projectRef, err := utils.LoadProjectRef(fsys)
-		if err != nil {
-			return err
-		}
-
 		resp, err := utils.GetSupabase().DeleteSecretsWithResponse(ctx, projectRef, args)
 		if err != nil {
 			return err
diff --git a/internal/ssl_enforcement/get/get.go b/internal/ssl_enforcement/get/get.go
index ae317640..ff7d71b0 100644
--- a/internal/ssl_enforcement/get/get.go
+++ b/internal/ssl_enforcement/get/get.go
@@ -9,21 +9,8 @@ import (
 	"github.com/supabase/cli/internal/utils"
 )
 
-func Run(ctx context.Context, projectRefArg string, fsys afero.Fs) error {
+func Run(ctx context.Context, projectRef string, fsys afero.Fs) error {
 	// 1. Sanity checks.
-	projectRef := projectRefArg
-	{
-		if len(projectRefArg) == 0 {
-			ref, err := utils.LoadProjectRef(fsys)
-			if err != nil {
-				return err
-			}
-			projectRef = ref
-		} else if !utils.ProjectRefPattern.MatchString(projectRef) {
-			return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
-		}
-	}
-
 	// 2. get ssl enforcement config
 	{
 		resp, err := utils.GetSupabase().GetSslEnforcementConfigWithResponse(ctx, projectRef)
diff --git a/internal/ssl_enforcement/update/update.go b/internal/ssl_enforcement/update/update.go
index 7c7609d8..36511175 100644
--- a/internal/ssl_enforcement/update/update.go
+++ b/internal/ssl_enforcement/update/update.go
@@ -10,23 +10,8 @@ import (
 	"github.com/supabase/cli/pkg/api"
 )
 
-func Run(ctx context.Context, projectRefArg string, enforceDbSsl bool, fsys afero.Fs) error {
-	// 1. Sanity checks.
-	projectRef := projectRefArg
-
+func Run(ctx context.Context, projectRef string, enforceDbSsl bool, fsys afero.Fs) error {
 	// 1. sanity checks
-	{
-		if len(projectRefArg) == 0 {
-			ref, err := utils.LoadProjectRef(fsys)
-			if err != nil {
-				return err
-			}
-			projectRef = ref
-		} else if !utils.ProjectRefPattern.MatchString(projectRef) {
-			return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
-		}
-	}
-
 	// 2. update restrictions
 	{
 		resp, err := utils.GetSupabase().UpdateSslEnforcementConfigWithResponse(ctx, projectRef, api.UpdateSslEnforcementConfigJSONRequestBody{
diff --git a/internal/utils/flags/project_ref.go b/internal/utils/flags/project_ref.go
new file mode 100644
index 00000000..30759155
--- /dev/null
+++ b/internal/utils/flags/project_ref.go
@@ -0,0 +1,49 @@
+package flags
+
+import (
+	"bufio"
+	"bytes"
+	"errors"
+	"fmt"
+	"io"
+	"os"
+	"strings"
+
+	"github.com/spf13/afero"
+	"github.com/supabase/cli/internal/utils"
+	"golang.org/x/term"
+)
+
+var ProjectRef string
+
+func ParseProjectRef(fsys afero.Fs) error {
+	// Flag takes highest precedence
+	if len(ProjectRef) > 0 {
+		return utils.AssertProjectRefIsValid(ProjectRef)
+	}
+	// Followed by linked ref file
+	if projectRefBytes, err := afero.ReadFile(fsys, utils.ProjectRefPath); err == nil {
+		ProjectRef = string(bytes.TrimSpace(projectRefBytes))
+		return utils.AssertProjectRefIsValid(ProjectRef)
+	} else if !errors.Is(err, os.ErrNotExist) {
+		return err
+	}
+	// Prompt as the last resort
+	if term.IsTerminal(int(os.Stdin.Fd())) {
+		return promptProjectRef(os.Stdin)
+	}
+	return utils.ErrNotLinked
+}
+
+func promptProjectRef(stdin io.Reader) error {
+	fmt.Fprintf(os.Stderr, `You can find your project ref from the project's dashboard home page, e.g. %s/project/<project-ref>.
+Enter your project ref: `, utils.GetSupabaseDashboardURL())
+	// Scan a single line for input
+	scanner := bufio.NewScanner(stdin)
+	scanner.Scan()
+	if err := scanner.Err(); err != nil {
+		return err
+	}
+	ProjectRef = strings.TrimSpace(scanner.Text())
+	return utils.AssertProjectRefIsValid(ProjectRef)
+}
diff --git a/internal/utils/misc.go b/internal/utils/misc.go
index 2d4b4e3b..4b805e17 100644
--- a/internal/utils/misc.go
+++ b/internal/utils/misc.go
@@ -173,6 +173,10 @@ var (
 	DbTestsDir            = filepath.Join(SupabaseDirPath, "tests")
 	SeedDataPath          = filepath.Join(SupabaseDirPath, "seed.sql")
 	CustomRolesPath       = filepath.Join(SupabaseDirPath, "roles.sql")
+
+	ErrNotLinked  = errors.New("Cannot find project ref. Have you run " + Aqua("supabase link") + "?")
+	ErrInvalidRef = errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
+	ErrNotRunning = errors.New(Aqua("supabase start") + " is not running.")
 )
 
 func GetCurrentTimestamp() string {
@@ -221,7 +225,7 @@ func NewError(s string) error {
 
 func AssertSupabaseDbIsRunning() error {
 	if _, err := Docker.ContainerInspect(context.Background(), DbId); err != nil {
-		return errors.New(Aqua("supabase start") + " is not running.")
+		return ErrNotRunning
 	}
 
 	return nil
@@ -319,7 +323,7 @@ func AssertSupabaseCliIsSetUpFS(fsys afero.Fs) error {
 
 func AssertIsLinkedFS(fsys afero.Fs) error {
 	if _, err := fsys.Stat(ProjectRefPath); errors.Is(err, os.ErrNotExist) {
-		return errors.New("Cannot find project ref. Have you run " + Aqua("supabase link") + "?")
+		return ErrNotLinked
 	} else if err != nil {
 		return err
 	}
@@ -327,14 +331,21 @@ func AssertIsLinkedFS(fsys afero.Fs) error {
 	return nil
 }
 
+func AssertProjectRefIsValid(projectRef string) error {
+	if !ProjectRefPattern.MatchString(projectRef) {
+		return ErrInvalidRef
+	}
+	return nil
+}
+
 func LoadProjectRef(fsys afero.Fs) (string, error) {
 	projectRefBytes, err := afero.ReadFile(fsys, ProjectRefPath)
 	if err != nil {
-		return "", errors.New("Cannot find project ref. Have you run " + Aqua("supabase link") + "?")
+		return "", ErrNotLinked
 	}
 	projectRef := string(bytes.TrimSpace(projectRefBytes))
 	if !ProjectRefPattern.MatchString(projectRef) {
-		return "", errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
+		return "", ErrInvalidRef
 	}
 	return projectRef, nil
 }
diff --git a/internal/vanity_subdomains/activate/activate.go b/internal/vanity_subdomains/activate/activate.go
index c0efa4f4..f814fba2 100644
--- a/internal/vanity_subdomains/activate/activate.go
+++ b/internal/vanity_subdomains/activate/activate.go
@@ -11,20 +11,10 @@ import (
 	"github.com/supabase/cli/pkg/api"
 )
 
-func Run(ctx context.Context, projectRefArg string, desiredSubdomain string, fsys afero.Fs) error {
+func Run(ctx context.Context, projectRef string, desiredSubdomain string, fsys afero.Fs) error {
 	// 1. Sanity checks.
-	projectRef := projectRefArg
 	subdomain := strings.TrimSpace(desiredSubdomain)
 	{
-		if len(projectRefArg) == 0 {
-			ref, err := utils.LoadProjectRef(fsys)
-			if err != nil {
-				return err
-			}
-			projectRef = ref
-		} else if !utils.ProjectRefPattern.MatchString(projectRef) {
-			return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
-		}
 		if len(subdomain) == 0 {
 			return errors.New("non-empty vanity subdomain expected")
 		}
diff --git a/internal/vanity_subdomains/check/check.go b/internal/vanity_subdomains/check/check.go
index 4cf5723f..a58a8f70 100644
--- a/internal/vanity_subdomains/check/check.go
+++ b/internal/vanity_subdomains/check/check.go
@@ -11,19 +11,12 @@ import (
 	"github.com/supabase/cli/pkg/api"
 )
 
-func Run(ctx context.Context, projectRefArg string, desiredSubdomain string, fsys afero.Fs) error {
+func Run(ctx context.Context, projectRef string, desiredSubdomain string, fsys afero.Fs) error {
 	// 1. Sanity checks.
-	projectRef := projectRefArg
 	subdomain := strings.TrimSpace(desiredSubdomain)
 	{
-		if len(projectRefArg) == 0 {
-			ref, err := utils.LoadProjectRef(fsys)
-			if err != nil {
-				return err
-			}
-			projectRef = ref
-		} else if !utils.ProjectRefPattern.MatchString(projectRef) {
-			return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
+		if len(subdomain) == 0 {
+			return errors.New("non-empty vanity subdomain expected")
 		}
 	}
 
diff --git a/internal/vanity_subdomains/delete/delete.go b/internal/vanity_subdomains/delete/delete.go
index 8b00843e..6c4da5b0 100644
--- a/internal/vanity_subdomains/delete/delete.go
+++ b/internal/vanity_subdomains/delete/delete.go
@@ -9,21 +9,8 @@ import (
 	"github.com/supabase/cli/internal/utils"
 )
 
-func Run(ctx context.Context, projectRefArg string, fsys afero.Fs) error {
+func Run(ctx context.Context, projectRef string, fsys afero.Fs) error {
 	// 1. Sanity checks.
-	projectRef := projectRefArg
-	{
-		if len(projectRefArg) == 0 {
-			ref, err := utils.LoadProjectRef(fsys)
-			if err != nil {
-				return err
-			}
-			projectRef = ref
-		} else if !utils.ProjectRefPattern.MatchString(projectRef) {
-			return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
-		}
-	}
-
 	// 2. delete config
 	{
 		resp, err := utils.GetSupabase().RemoveVanitySubdomainConfigWithResponse(ctx, projectRef)
diff --git a/internal/vanity_subdomains/get/get.go b/internal/vanity_subdomains/get/get.go
index 1e31ae9b..b0560dc9 100644
--- a/internal/vanity_subdomains/get/get.go
+++ b/internal/vanity_subdomains/get/get.go
@@ -2,28 +2,14 @@ package get
 
 import (
 	"context"
-	"errors"
 	"fmt"
 
 	"github.com/spf13/afero"
 	"github.com/supabase/cli/internal/utils"
 )
 
-func Run(ctx context.Context, projectRefArg string, fsys afero.Fs) error {
+func Run(ctx context.Context, projectRef string, fsys afero.Fs) error {
 	// 1. Sanity checks.
-	projectRef := projectRefArg
-	{
-		if len(projectRefArg) == 0 {
-			ref, err := utils.LoadProjectRef(fsys)
-			if err != nil {
-				return err
-			}
-			projectRef = ref
-		} else if !utils.ProjectRefPattern.MatchString(projectRef) {
-			return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
-		}
-	}
-
 	// 2. get vanity subdomain config
 	{
 		response, err := utils.GetSupabase().GetVanitySubdomainConfigWithResponse(ctx, projectRef)


Test patch:
diff --git a/internal/db/branch/switch_/switch__test.go b/internal/db/branch/switch_/switch__test.go
index ae8e1228..2004df5d 100644
--- a/internal/db/branch/switch_/switch__test.go
+++ b/internal/db/branch/switch_/switch__test.go
@@ -104,7 +104,7 @@ func TestSwitchCommand(t *testing.T) {
 		// Run test
 		err := Run(context.Background(), "target", fsys)
 		// Check error
-		assert.ErrorContains(t, err, "supabase start is not running.")
+		assert.ErrorIs(t, err, utils.ErrNotRunning)
 		assert.Empty(t, apitest.ListUnmatchedRequests())
 	})
 
diff --git a/internal/db/diff/migra_test.go b/internal/db/diff/migra_test.go
index d4288b2f..a0d785f2 100644
--- a/internal/db/diff/migra_test.go
+++ b/internal/db/diff/migra_test.go
@@ -92,7 +92,7 @@ func TestRunMigra(t *testing.T) {
 		// Run test
 		err := RunMigra(context.Background(), []string{"public"}, "", pgconn.Config{}, fsys)
 		// Check error
-		assert.ErrorContains(t, err, "supabase start is not running.")
+		assert.ErrorIs(t, err, utils.ErrNotRunning)
 		assert.Empty(t, apitest.ListUnmatchedRequests())
 	})
 
diff --git a/internal/db/reset/reset_test.go b/internal/db/reset/reset_test.go
index 46ae1d66..c83c1044 100644
--- a/internal/db/reset/reset_test.go
+++ b/internal/db/reset/reset_test.go
@@ -42,7 +42,7 @@ func TestResetCommand(t *testing.T) {
 		// Run test
 		err := Run(context.Background(), fsys)
 		// Check error
-		assert.ErrorContains(t, err, "supabase start is not running.")
+		assert.ErrorIs(t, err, utils.ErrNotRunning)
 		assert.Empty(t, apitest.ListUnmatchedRequests())
 	})
 
diff --git a/internal/db/test/test_test.go b/internal/db/test/test_test.go
index bcb3a162..5d0145b7 100644
--- a/internal/db/test/test_test.go
+++ b/internal/db/test/test_test.go
@@ -117,7 +117,7 @@ func TestRunCommand(t *testing.T) {
 		// Run test
 		err := Run(context.Background(), fsys)
 		// Check error
-		assert.ErrorContains(t, err, "supabase start is not running.")
+		assert.ErrorIs(t, err, utils.ErrNotRunning)
 		assert.Empty(t, apitest.ListUnmatchedRequests())
 	})
 
diff --git a/internal/functions/delete/delete_test.go b/internal/functions/delete/delete_test.go
index d06be0d6..0585ac19 100644
--- a/internal/functions/delete/delete_test.go
+++ b/internal/functions/delete/delete_test.go
@@ -8,7 +8,6 @@ import (
 
 	"github.com/spf13/afero"
 	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
 	"github.com/supabase/cli/internal/testing/apitest"
 	"github.com/supabase/cli/internal/utils"
 	"github.com/supabase/cli/pkg/api"
@@ -21,7 +20,6 @@ func TestDeleteCommand(t *testing.T) {
 		fsys := afero.NewMemMapFs()
 		// Setup valid project ref
 		project := apitest.RandomProjectRef()
-		require.NoError(t, afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644))
 		// Setup valid access token
 		token := apitest.RandomAccessToken(t)
 		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
@@ -35,31 +33,11 @@ func TestDeleteCommand(t *testing.T) {
 			Delete("/v1/projects/" + project + "/functions/test-func").
 			Reply(http.StatusOK)
 		// Run test
-		assert.NoError(t, Run(context.Background(), "test-func", "", fsys))
+		assert.NoError(t, Run(context.Background(), "test-func", project, fsys))
 		// Validate api
 		assert.Empty(t, apitest.ListUnmatchedRequests())
 	})
 
-	t.Run("throws error on malformed ref", func(t *testing.T) {
-		// Setup in-memory fs
-		fsys := afero.NewMemMapFs()
-		// Setup invalid project ref
-		require.NoError(t, afero.WriteFile(fsys, utils.ProjectRefPath, []byte("test-project"), 0644))
-		// Run test
-		err := Run(context.Background(), "test-func", "", fsys)
-		// Check error
-		assert.ErrorContains(t, err, "Invalid project ref format.")
-	})
-
-	t.Run("throws error on malformed ref arg", func(t *testing.T) {
-		// Setup in-memory fs
-		fsys := afero.NewMemMapFs()
-		// Run test
-		err := Run(context.Background(), "test-func", "test-project", fsys)
-		// Check error
-		assert.ErrorContains(t, err, "Invalid project ref format.")
-	})
-
 	t.Run("throws error on malformed slug", func(t *testing.T) {
 		// Setup in-memory fs
 		fsys := afero.NewMemMapFs()
diff --git a/internal/functions/deploy/deploy_test.go b/internal/functions/deploy/deploy_test.go
index 5c51588a..88fefb42 100644
--- a/internal/functions/deploy/deploy_test.go
+++ b/internal/functions/deploy/deploy_test.go
@@ -105,7 +105,6 @@ func TestDeployCommand(t *testing.T) {
 		fsys := afero.NewMemMapFs()
 		// Setup valid project ref
 		project := apitest.RandomProjectRef()
-		require.NoError(t, afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644))
 		// Setup valid access token
 		token := apitest.RandomAccessToken(t)
 		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
@@ -123,7 +122,7 @@ func TestDeployCommand(t *testing.T) {
 			Reply(http.StatusOK).
 			JSON(api.FunctionResponse{Id: "1"})
 		// Run test
-		assert.NoError(t, Run(context.Background(), slug, "", nil, true, "", fsys))
+		assert.NoError(t, Run(context.Background(), slug, project, nil, true, "", fsys))
 		// Validate api
 		assert.Empty(t, apitest.ListUnmatchedRequests())
 	})
@@ -134,7 +133,6 @@ func TestDeployCommand(t *testing.T) {
 		fsys := afero.NewMemMapFs()
 		// Setup valid project ref
 		project := apitest.RandomProjectRef()
-		require.NoError(t, afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644))
 		// Setup valid access token
 		token := apitest.RandomAccessToken(t)
 		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
@@ -152,33 +150,11 @@ func TestDeployCommand(t *testing.T) {
 			Reply(http.StatusOK).
 			JSON(api.FunctionResponse{Id: "1"})
 		// Run test
-		assert.NoError(t, Run(context.Background(), slug, "", nil, false, "", fsys))
+		assert.NoError(t, Run(context.Background(), slug, project, nil, false, "", fsys))
 		// Validate api
 		assert.Empty(t, apitest.ListUnmatchedRequests())
 	})
 
-	t.Run("throws error on malformed ref", func(t *testing.T) {
-		// Setup in-memory fs
-		fsys := afero.NewMemMapFs()
-		// Setup invalid project ref
-		require.NoError(t, afero.WriteFile(fsys, utils.ProjectRefPath, []byte("test-project"), 0644))
-		// Run test
-		noVerifyJWT := true
-		err := Run(context.Background(), "test-func", "", &noVerifyJWT, true, "", fsys)
-		// Check error
-		assert.ErrorContains(t, err, "Invalid project ref format.")
-	})
-
-	t.Run("throws error on malformed ref arg", func(t *testing.T) {
-		// Setup in-memory fs
-		fsys := afero.NewMemMapFs()
-		// Run test
-		noVerifyJWT := true
-		err := Run(context.Background(), "test-func", "test-project", &noVerifyJWT, true, "", fsys)
-		// Check error
-		assert.ErrorContains(t, err, "Invalid project ref format.")
-	})
-
 	t.Run("throws error on malformed slug", func(t *testing.T) {
 		// Setup in-memory fs
 		fsys := afero.NewMemMapFs()
diff --git a/internal/functions/serve/serve_test.go b/internal/functions/serve/serve_test.go
index 89fcd6e0..95a3a1b4 100644
--- a/internal/functions/serve/serve_test.go
+++ b/internal/functions/serve/serve_test.go
@@ -91,7 +91,7 @@ func TestServeCommand(t *testing.T) {
 		// Run test
 		err := Run(context.Background(), "", "", nil, "", fsys)
 		// Check error
-		assert.ErrorContains(t, err, "supabase start is not running.")
+		assert.ErrorIs(t, err, utils.ErrNotRunning)
 	})
 
 	t.Run("throws error on missing env file", func(t *testing.T) {
diff --git a/internal/link/link_test.go b/internal/link/link_test.go
index ab5ba3c8..d6ea0b56 100644
--- a/internal/link/link_test.go
+++ b/internal/link/link_test.go
@@ -56,7 +56,7 @@ func TestPreRun(t *testing.T) {
 		// Run test
 		err := PreRun("malformed", fsys)
 		// Check error
-		assert.ErrorContains(t, err, "Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
+		assert.ErrorIs(t, err, utils.ErrInvalidRef)
 	})
 
 	t.Run("throws error on missing config", func(t *testing.T) {
diff --git a/internal/secrets/list/list_test.go b/internal/secrets/list/list_test.go
index 99bcb4e4..129b3d30 100644
--- a/internal/secrets/list/list_test.go
+++ b/internal/secrets/list/list_test.go
@@ -7,7 +7,6 @@ import (
 
 	"github.com/spf13/afero"
 	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
 	"github.com/supabase/cli/internal/testing/apitest"
 	"github.com/supabase/cli/internal/utils"
 	"github.com/supabase/cli/pkg/api"
@@ -18,12 +17,8 @@ func TestSecretListCommand(t *testing.T) {
 	t.Run("lists all secrets", func(t *testing.T) {
 		// Setup in-memory fs
 		fsys := afero.NewMemMapFs()
-		_, err := fsys.Create(utils.ConfigPath)
-		require.NoError(t, err)
 		// Setup valid project ref
 		project := apitest.RandomProjectRef()
-		err = afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644)
-		require.NoError(t, err)
 		// Setup valid access token
 		token := apitest.RandomAccessToken(t)
 		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
@@ -39,44 +34,27 @@ func TestSecretListCommand(t *testing.T) {
 				},
 			})
 		// Run test
-		assert.NoError(t, Run(context.Background(), fsys))
-		// Validate api
+		err := Run(context.Background(), project, fsys)
+		// Check error
+		assert.NoError(t, err)
 		assert.Empty(t, apitest.ListUnmatchedRequests())
 	})
 
-	t.Run("throws error on missing config file", func(t *testing.T) {
-		assert.Error(t, Run(context.Background(), afero.NewMemMapFs()))
-	})
-
-	t.Run("throws error on missing project ref", func(t *testing.T) {
-		// Setup in-memory fs
-		fsys := afero.NewMemMapFs()
-		_, err := fsys.Create(utils.ConfigPath)
-		require.NoError(t, err)
-		// Run test
-		assert.Error(t, Run(context.Background(), fsys))
-	})
-
 	t.Run("throws error on missing access token", func(t *testing.T) {
+		t.Skip()
 		// Setup in-memory fs
 		fsys := afero.NewMemMapFs()
-		_, err := fsys.Create(utils.ConfigPath)
-		require.NoError(t, err)
-		_, err = fsys.Create(utils.ProjectRefPath)
-		require.NoError(t, err)
 		// Run test
-		assert.Error(t, Run(context.Background(), fsys))
+		err := Run(context.Background(), "", fsys)
+		// Check error
+		assert.ErrorContains(t, err, "Unexpected error retrieving project secrets")
 	})
 
 	t.Run("throws error on network error", func(t *testing.T) {
 		// Setup in-memory fs
 		fsys := afero.NewMemMapFs()
-		_, err := fsys.Create(utils.ConfigPath)
-		require.NoError(t, err)
 		// Setup valid project ref
 		project := apitest.RandomProjectRef()
-		err = afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644)
-		require.NoError(t, err)
 		// Setup valid access token
 		token := apitest.RandomAccessToken(t)
 		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
@@ -86,20 +64,17 @@ func TestSecretListCommand(t *testing.T) {
 			Get("/v1/projects/" + project + "/secrets").
 			ReplyError(errors.New("network error"))
 		// Run test
-		assert.Error(t, Run(context.Background(), fsys))
-		// Validate api
+		err := Run(context.Background(), project, fsys)
+		// Check error
+		assert.ErrorContains(t, err, "network error")
 		assert.Empty(t, apitest.ListUnmatchedRequests())
 	})
 
 	t.Run("throws error on server unavailable", func(t *testing.T) {
 		// Setup in-memory fs
 		fsys := afero.NewMemMapFs()
-		_, err := fsys.Create(utils.ConfigPath)
-		require.NoError(t, err)
 		// Setup valid project ref
 		project := apitest.RandomProjectRef()
-		err = afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644)
-		require.NoError(t, err)
 		// Setup valid access token
 		token := apitest.RandomAccessToken(t)
 		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
@@ -110,20 +85,17 @@ func TestSecretListCommand(t *testing.T) {
 			Reply(500).
 			JSON(map[string]string{"message": "unavailable"})
 		// Run test
-		assert.Error(t, Run(context.Background(), fsys))
-		// Validate api
+		err := Run(context.Background(), project, fsys)
+		// Check error
+		assert.ErrorContains(t, err, `Unexpected error retrieving project secrets: {"message":"unavailable"}`)
 		assert.Empty(t, apitest.ListUnmatchedRequests())
 	})
 
 	t.Run("throws error on malformed json", func(t *testing.T) {
 		// Setup in-memory fs
 		fsys := afero.NewMemMapFs()
-		_, err := fsys.Create(utils.ConfigPath)
-		require.NoError(t, err)
 		// Setup valid project ref
 		project := apitest.RandomProjectRef()
-		err = afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644)
-		require.NoError(t, err)
 		// Setup valid access token
 		token := apitest.RandomAccessToken(t)
 		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
@@ -134,8 +106,9 @@ func TestSecretListCommand(t *testing.T) {
 			Reply(200).
 			JSON(map[string]string{})
 		// Run test
-		assert.Error(t, Run(context.Background(), fsys))
-		// Validate api
+		err := Run(context.Background(), project, fsys)
+		// Check error
+		assert.ErrorContains(t, err, "json: cannot unmarshal object into Go value of type []api.SecretResponse")
 		assert.Empty(t, apitest.ListUnmatchedRequests())
 	})
 }
diff --git a/internal/secrets/set/set_test.go b/internal/secrets/set/set_test.go
index d9d04069..2f513b45 100644
--- a/internal/secrets/set/set_test.go
+++ b/internal/secrets/set/set_test.go
@@ -22,12 +22,8 @@ func TestSecretSetCommand(t *testing.T) {
 	t.Run("Sets secret via cli args", func(t *testing.T) {
 		// Setup in-memory fs
 		fsys := afero.NewMemMapFs()
-		_, err := fsys.Create(utils.ConfigPath)
-		require.NoError(t, err)
 		// Setup valid project ref
 		project := apitest.RandomProjectRef()
-		err = afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644)
-		require.NoError(t, err)
 		// Setup valid access token
 		token := apitest.RandomAccessToken(t)
 		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
@@ -39,20 +35,17 @@ func TestSecretSetCommand(t *testing.T) {
 			JSON(api.CreateSecretsJSONBody{dummy}).
 			Reply(200)
 		// Run test
-		assert.NoError(t, Run(context.Background(), "", []string{dummyEnv}, fsys))
-		// Validate api
+		err := Run(context.Background(), project, "", []string{dummyEnv}, fsys)
+		// Check error
+		assert.NoError(t, err)
 		assert.Empty(t, apitest.ListUnmatchedRequests())
 	})
 
 	t.Run("Sets secret value via env file", func(t *testing.T) {
 		// Setup in-memory fs
 		fsys := afero.NewMemMapFs()
-		_, err := fsys.Create(utils.ConfigPath)
-		require.NoError(t, err)
 		// Setup valid project ref
 		project := apitest.RandomProjectRef()
-		err = afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644)
-		require.NoError(t, err)
 		// Setup valid access token
 		token := apitest.RandomAccessToken(t)
 		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
@@ -70,76 +63,45 @@ func TestSecretSetCommand(t *testing.T) {
 			JSON(api.CreateSecretsJSONBody{dummy}).
 			Reply(200)
 		// Run test
-		assert.NoError(t, Run(context.Background(), tmpfile.Name(), []string{}, fsys))
-		// Validate api
+		err = Run(context.Background(), project, tmpfile.Name(), []string{}, fsys)
+		// Check error
+		assert.NoError(t, err)
 		assert.Empty(t, apitest.ListUnmatchedRequests())
 	})
 
-	t.Run("throws error on missing config file", func(t *testing.T) {
-		assert.Error(t, Run(context.Background(), "", []string{}, afero.NewMemMapFs()))
-	})
-
-	t.Run("throws error on missing project ref", func(t *testing.T) {
-		// Setup in-memory fs
-		fsys := afero.NewMemMapFs()
-		_, err := fsys.Create(utils.ConfigPath)
-		require.NoError(t, err)
-		// Run test
-		assert.Error(t, Run(context.Background(), "", []string{}, fsys))
-	})
-
-	t.Run("throws error on missing access token", func(t *testing.T) {
-		// Setup in-memory fs
-		fsys := afero.NewMemMapFs()
-		_, err := fsys.Create(utils.ConfigPath)
-		require.NoError(t, err)
-		_, err = fsys.Create(utils.ProjectRefPath)
-		require.NoError(t, err)
-		// Run test
-		assert.Error(t, Run(context.Background(), "", []string{}, fsys))
-	})
-
 	t.Run("throws error on empty secret", func(t *testing.T) {
 		// Setup in-memory fs
 		fsys := afero.NewMemMapFs()
-		_, err := fsys.Create(utils.ConfigPath)
-		require.NoError(t, err)
 		// Setup valid project ref
 		project := apitest.RandomProjectRef()
-		err = afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644)
-		require.NoError(t, err)
 		// Setup valid access token
 		token := apitest.RandomAccessToken(t)
 		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
 		// Run test
-		assert.Error(t, Run(context.Background(), "", []string{}, fsys))
+		err := Run(context.Background(), project, "", []string{}, fsys)
+		// Check error
+		assert.ErrorContains(t, err, "No arguments found. Use --env-file to read from a .env file.")
 	})
 
 	t.Run("throws error on malformed secret", func(t *testing.T) {
 		// Setup in-memory fs
 		fsys := afero.NewMemMapFs()
-		_, err := fsys.Create(utils.ConfigPath)
-		require.NoError(t, err)
 		// Setup valid project ref
 		project := apitest.RandomProjectRef()
-		err = afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644)
-		require.NoError(t, err)
 		// Setup valid access token
 		token := apitest.RandomAccessToken(t)
 		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
 		// Run test
-		assert.Error(t, Run(context.Background(), "", []string{"malformed"}, fsys))
+		err := Run(context.Background(), project, "", []string{"malformed"}, fsys)
+		// Check error
+		assert.ErrorContains(t, err, "Invalid secret pair: malformed. Must be NAME=VALUE.")
 	})
 
 	t.Run("throws error on network error", func(t *testing.T) {
 		// Setup in-memory fs
 		fsys := afero.NewMemMapFs()
-		_, err := fsys.Create(utils.ConfigPath)
-		require.NoError(t, err)
 		// Setup valid project ref
 		project := apitest.RandomProjectRef()
-		err = afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644)
-		require.NoError(t, err)
 		// Setup valid access token
 		token := apitest.RandomAccessToken(t)
 		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
@@ -151,20 +113,17 @@ func TestSecretSetCommand(t *testing.T) {
 			JSON(api.CreateSecretsJSONBody{dummy}).
 			ReplyError(errors.New("network error"))
 		// Run test
-		assert.Error(t, Run(context.Background(), "", []string{dummyEnv}, fsys))
-		// Validate api
+		err := Run(context.Background(), project, "", []string{dummyEnv}, fsys)
+		// Check error
+		assert.ErrorContains(t, err, "network error")
 		assert.Empty(t, apitest.ListUnmatchedRequests())
 	})
 
 	t.Run("throws error on server unavailable", func(t *testing.T) {
 		// Setup in-memory fs
 		fsys := afero.NewMemMapFs()
-		_, err := fsys.Create(utils.ConfigPath)
-		require.NoError(t, err)
 		// Setup valid project ref
 		project := apitest.RandomProjectRef()
-		err = afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644)
-		require.NoError(t, err)
 		// Setup valid access token
 		token := apitest.RandomAccessToken(t)
 		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
@@ -177,8 +136,9 @@ func TestSecretSetCommand(t *testing.T) {
 			Reply(500).
 			JSON(map[string]string{"message": "unavailable"})
 		// Run test
-		assert.Error(t, Run(context.Background(), "", []string{dummyEnv}, fsys))
-		// Validate api
+		err := Run(context.Background(), project, "", []string{dummyEnv}, fsys)
+		// Check error
+		assert.ErrorContains(t, err, `Unexpected error setting project secrets: {"message":"unavailable"}`)
 		assert.Empty(t, apitest.ListUnmatchedRequests())
 	})
 }
diff --git a/internal/secrets/unset/unset_test.go b/internal/secrets/unset/unset_test.go
index 9452a0bd..313a8e68 100644
--- a/internal/secrets/unset/unset_test.go
+++ b/internal/secrets/unset/unset_test.go
@@ -7,7 +7,6 @@ import (
 
 	"github.com/spf13/afero"
 	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
 	"github.com/supabase/cli/internal/testing/apitest"
 	"github.com/supabase/cli/internal/utils"
 	"github.com/supabase/cli/pkg/api"
@@ -18,12 +17,8 @@ func TestSecretUnsetCommand(t *testing.T) {
 	t.Run("Unsets secret via cli args", func(t *testing.T) {
 		// Setup in-memory fs
 		fsys := afero.NewMemMapFs()
-		_, err := fsys.Create(utils.ConfigPath)
-		require.NoError(t, err)
 		// Setup valid project ref
 		project := apitest.RandomProjectRef()
-		err = afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644)
-		require.NoError(t, err)
 		// Setup valid access token
 		token := apitest.RandomAccessToken(t)
 		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
@@ -35,60 +30,17 @@ func TestSecretUnsetCommand(t *testing.T) {
 			JSON(api.DeleteSecretsJSONBody{"my-secret"}).
 			Reply(200)
 		// Run test
-		assert.NoError(t, Run(context.Background(), []string{"my-secret"}, fsys))
-		// Validate api
+		err := Run(context.Background(), project, []string{"my-secret"}, fsys)
+		// Check error
+		assert.NoError(t, err)
 		assert.Empty(t, apitest.ListUnmatchedRequests())
 	})
 
-	t.Run("throws error on missing config file", func(t *testing.T) {
-		assert.Error(t, Run(context.Background(), []string{}, afero.NewMemMapFs()))
-	})
-
-	t.Run("throws error on missing project ref", func(t *testing.T) {
-		// Setup in-memory fs
-		fsys := afero.NewMemMapFs()
-		_, err := fsys.Create(utils.ConfigPath)
-		require.NoError(t, err)
-		// Run test
-		assert.Error(t, Run(context.Background(), []string{}, fsys))
-	})
-
-	t.Run("throws error on missing access token", func(t *testing.T) {
-		// Setup in-memory fs
-		fsys := afero.NewMemMapFs()
-		_, err := fsys.Create(utils.ConfigPath)
-		require.NoError(t, err)
-		_, err = fsys.Create(utils.ProjectRefPath)
-		require.NoError(t, err)
-		// Run test
-		assert.Error(t, Run(context.Background(), []string{}, fsys))
-	})
-
-	t.Run("throws error on empty secret", func(t *testing.T) {
-		// Setup in-memory fs
-		fsys := afero.NewMemMapFs()
-		_, err := fsys.Create(utils.ConfigPath)
-		require.NoError(t, err)
-		// Setup valid project ref
-		project := apitest.RandomProjectRef()
-		err = afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644)
-		require.NoError(t, err)
-		// Setup valid access token
-		token := apitest.RandomAccessToken(t)
-		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
-		// Run test
-		assert.Error(t, Run(context.Background(), []string{}, fsys))
-	})
-
 	t.Run("throws error on network error", func(t *testing.T) {
 		// Setup in-memory fs
 		fsys := afero.NewMemMapFs()
-		_, err := fsys.Create(utils.ConfigPath)
-		require.NoError(t, err)
 		// Setup valid project ref
 		project := apitest.RandomProjectRef()
-		err = afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644)
-		require.NoError(t, err)
 		// Setup valid access token
 		token := apitest.RandomAccessToken(t)
 		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
@@ -100,20 +52,17 @@ func TestSecretUnsetCommand(t *testing.T) {
 			JSON(api.DeleteSecretsJSONBody{"my-secret"}).
 			ReplyError(errors.New("network error"))
 		// Run test
-		assert.Error(t, Run(context.Background(), []string{"my-secret"}, fsys))
-		// Validate api
+		err := Run(context.Background(), project, []string{"my-secret"}, fsys)
+		// Check error
+		assert.ErrorContains(t, err, "network error")
 		assert.Empty(t, apitest.ListUnmatchedRequests())
 	})
 
 	t.Run("throws error on server unavailable", func(t *testing.T) {
 		// Setup in-memory fs
 		fsys := afero.NewMemMapFs()
-		_, err := fsys.Create(utils.ConfigPath)
-		require.NoError(t, err)
 		// Setup valid project ref
 		project := apitest.RandomProjectRef()
-		err = afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644)
-		require.NoError(t, err)
 		// Setup valid access token
 		token := apitest.RandomAccessToken(t)
 		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
@@ -126,8 +75,9 @@ func TestSecretUnsetCommand(t *testing.T) {
 			Reply(500).
 			JSON(map[string]string{"message": "unavailable"})
 		// Run test
-		assert.Error(t, Run(context.Background(), []string{"my-secret"}, fsys))
-		// Validate api
+		err := Run(context.Background(), project, []string{"my-secret"}, fsys)
+		// Check error
+		assert.ErrorContains(t, err, `Unexpected error unsetting project secrets: {"message":"unavailable"}`)
 		assert.Empty(t, apitest.ListUnmatchedRequests())
 	})
 }
diff --git a/internal/utils/flags/project_ref_test.go b/internal/utils/flags/project_ref_test.go
new file mode 100644
index 00000000..ebb8096f
--- /dev/null
+++ b/internal/utils/flags/project_ref_test.go
@@ -0,0 +1,85 @@
+package flags
+
+import (
+	"bufio"
+	"bytes"
+	"os"
+	"strings"
+	"testing"
+
+	"github.com/spf13/afero"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"github.com/supabase/cli/internal/testing/apitest"
+	"github.com/supabase/cli/internal/testing/fstest"
+	"github.com/supabase/cli/internal/utils"
+)
+
+func TestProjectRef(t *testing.T) {
+	t.Run("validates cmd flag", func(t *testing.T) {
+		ProjectRef = "invalid"
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		// Run test
+		err := ParseProjectRef(fsys)
+		// Check error
+		assert.Error(t, err, utils.ErrInvalidRef)
+	})
+
+	t.Run("loads from linked", func(t *testing.T) {
+		ProjectRef = ""
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		// Setup valid project ref
+		project := apitest.RandomProjectRef()
+		err := afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644)
+		require.NoError(t, err)
+		// Run test
+		err = ParseProjectRef(fsys)
+		// Check error
+		assert.NoError(t, err)
+	})
+
+	t.Run("throws error on read failure", func(t *testing.T) {
+		ProjectRef = ""
+		// Setup in-memory fs
+		fsys := &fstest.OpenErrorFs{DenyPath: utils.ProjectRefPath}
+		// Run test
+		err := ParseProjectRef(fsys)
+		// Check error
+		assert.ErrorIs(t, err, os.ErrPermission)
+	})
+
+	t.Run("throws error if all fail", func(t *testing.T) {
+		ProjectRef = ""
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		// Run test
+		err := ParseProjectRef(fsys)
+		// Check error
+		assert.ErrorIs(t, err, utils.ErrNotLinked)
+	})
+}
+
+func TestProjectPrompt(t *testing.T) {
+	t.Run("validates prompt input", func(t *testing.T) {
+		var stdin bytes.Buffer
+		_, err := stdin.WriteString(apitest.RandomProjectRef())
+		require.NoError(t, err)
+		// Run test
+		err = promptProjectRef(&stdin)
+		// Check error
+		assert.NoError(t, err)
+	})
+
+	t.Run("throws error on read failure", func(t *testing.T) {
+		// Setup long token
+		var stdin bytes.Buffer
+		_, err := stdin.WriteString(strings.Repeat("a", bufio.MaxScanTokenSize))
+		require.NoError(t, err)
+		// Run test
+		err = promptProjectRef(&stdin)
+		// Check error
+		assert.ErrorIs(t, err, bufio.ErrTooLong)
+	})
+}
diff --git a/test/secrets_test.go b/test/secrets_test.go
index 8b8c5f95..7a98d8e7 100644
--- a/test/secrets_test.go
+++ b/test/secrets_test.go
@@ -15,7 +15,7 @@ import (
 	"github.com/stretchr/testify/require"
 	"github.com/stretchr/testify/suite"
 	clicmd "github.com/supabase/cli/cmd"
-	"github.com/supabase/cli/internal/utils"
+	"github.com/supabase/cli/internal/utils/flags"
 	"github.com/supabase/cli/test/mocks/supabase"
 )
 
@@ -37,9 +37,6 @@ func (suite *SecretsTestSuite) TestList() {
 	list.SetContext(context.Background())
 	require.NoError(suite.T(), err)
 
-	ref := gonanoid.MustGenerate(supabase.IDAlphabet, supabase.IDLength)
-	require.NoError(suite.T(), os.WriteFile(utils.ProjectRefPath, []byte(ref), os.FileMode(0755)))
-
 	// set stdout to write into file so we can capture cmd output
 	tmpfile, err := os.CreateTemp(suite.tempDir, "output")
 	require.NoError(suite.T(), err)
@@ -48,12 +45,13 @@ func (suite *SecretsTestSuite) TestList() {
 	defer func() { os.Stdout = oldStdout }()
 	os.Stdout = tmpfile
 
+	flags.ProjectRef = gonanoid.MustGenerate(supabase.IDAlphabet, supabase.IDLength)
 	require.NoError(suite.T(), list.RunE(list, []string{}))
 
 	// check request details
 	suite.mtx.RLock()
 	defer suite.mtx.RUnlock()
-	require.Contains(suite.T(), suite.ids, ref)
+	require.Contains(suite.T(), suite.ids, flags.ProjectRef)
 	require.Contains(suite.T(), suite.headers, http.Header{
 		"Authorization":   []string{fmt.Sprintf("Bearer %s", supabase.AccessToken)},
 		"Accept-Encoding": []string{"gzip"},