| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137 |
- package cli
- import (
- "fmt"
- "io"
- "os"
- "github.com/spf13/cobra"
- "github.com/kotoyuuko/cc-switch-cli/internal/config"
- )
- // appState is threaded through every subcommand via cobra's persistent
- // pre-run. Subcommands read from app.cfg, mutate it in place, and call
- // app.save() to persist.
- type appState struct {
- // Streams. Tests substitute these; production uses os.Std*.
- stdin io.Reader
- stdout io.Writer
- stderr io.Writer
- // Flags.
- verbose bool
- configFlag string // --config; beats env var precedence
- // Loaded state.
- configPath string
- cfg config.Config
- // requestedExit lets subcommands like `use` hand back a specific exit
- // code (claude's own) that bypasses cobra's 0/1 mapping.
- requestedExit *int
- }
- func (a *appState) tracef(format string, args ...any) {
- if !a.verbose {
- return
- }
- fmt.Fprintf(a.stderr, "[cc-switch] "+format+"\n", args...)
- }
- // exitCode collapses (requestedExit, cobra err) into a single process exit
- // code. requestedExit takes precedence — subcommands use it to propagate
- // claude's own exit code even when we also returned an error for logging.
- func (a *appState) exitCode(err error) int {
- if a.requestedExit != nil {
- return *a.requestedExit
- }
- if err != nil {
- return 1
- }
- return 0
- }
- func (a *appState) save() error {
- return config.Save(a.configPath, a.cfg)
- }
- func newRootCmd(app *appState) *cobra.Command {
- root := &cobra.Command{
- Use: "cc-switch",
- Short: "Switch between Claude Code provider subscriptions",
- Long: "cc-switch manages multiple Claude Code provider env profiles and launches the `claude` CLI with the right environment.",
- SilenceUsage: true,
- SilenceErrors: false,
- // Bare run -> `use` (interactive when tty).
- RunE: func(cmd *cobra.Command, args []string) error {
- return runUse(cmd, app, args)
- },
- }
- root.PersistentFlags().BoolVarP(&app.verbose, "verbose", "v", false,
- "print trace output to stderr")
- root.PersistentFlags().StringVar(&app.configFlag, "config", "",
- "path to config file (overrides $CC_SWITCH_CONFIG)")
- root.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
- // Compute effective config path.
- if app.configFlag != "" {
- expanded, err := config.ExpandUser(app.configFlag)
- if err != nil {
- return err
- }
- app.configPath = expanded
- } else {
- p, err := config.ResolvePath()
- if err != nil {
- return err
- }
- app.configPath = p
- }
- app.tracef("config path: %s", app.configPath)
- res, err := config.Load(app.configPath)
- if err != nil {
- return err
- }
- if res.Warning != "" {
- fmt.Fprintln(app.stderr, res.Warning)
- }
- app.cfg = res.Config
- return nil
- }
- root.AddCommand(
- newAddCmd(app),
- newListCmd(app),
- newEditCmd(app),
- newRemoveCmd(app),
- newUseCmd(app),
- newConfigCmd(app),
- newTemplatesCmd(app),
- newVersionCmd(app),
- )
- return root
- }
- // Build-time injected values (see Makefile LDFLAGS).
- var (
- version = "dev"
- commit = "none"
- date = "unknown"
- )
- func newVersionCmd(app *appState) *cobra.Command {
- return &cobra.Command{
- Use: "version",
- Short: "Print version information",
- RunE: func(cmd *cobra.Command, args []string) error {
- _, err := fmt.Fprintf(app.stdout, "cc-switch %s (commit %s, built %s)\n", version, commit, date)
- return err
- },
- }
- }
- // ensure os.Stdin satisfies io.Reader — helps static tools.
- var _ io.Reader = os.Stdin
|