pflag -> cobra: Sub commands, completions, ...

Signed-off-by: Hadi <hadi@example.com>
This commit is contained in:
Hadi
2026-05-29 11:36:46 +02:00
parent 4922f3704d
commit 098400ba77
7 changed files with 165 additions and 135 deletions
+125 -126
View File
@@ -15,7 +15,7 @@ import (
"github.com/anotherhadi/spilltea/internal/style"
appUI "github.com/anotherhadi/spilltea/internal/ui/app"
homeUI "github.com/anotherhadi/spilltea/internal/ui/home"
flag "github.com/spf13/pflag"
"github.com/spf13/cobra"
)
// Version is overwritten at build time by goreleaser/ldflag with the current version tag, or "dev" if not set.
@@ -32,136 +32,135 @@ func init() {
func main() {
var (
flagConfig = flag.StringP("config", "c", "", "path to config file")
flagPluginsDir = flag.String("plugins-dir", "", "path to plugins dir (overrides config)")
flagHost = flag.String("host", "", "proxy host (overrides config)")
flagPort = flag.IntP("port", "p", 0, "proxy port (overrides config)")
flagUpstreamProxy = flag.String("upstream-proxy", "", "upstream proxy URL, e.g. http://user:pass@host:8888 (overrides config)")
flagVersion = flag.BoolP("version", "v", false, "print version")
flagProject = flag.StringP("project", "P", "", `project name to open directly, or "tmp" for a temporary session`)
flagAddDefaultPlugins = flag.Bool("add-default-plugins", false, "copy built-in example plugins into the plugins dir and exit")
flagAddDefaultConfig = flag.Bool("add-default-config", false, "copy the default config file to the config path and exit")
flagConfig string
flagPluginsDir string
flagHost string
flagPort int
flagUpstreamProxy string
flagProject string
flagAddDefaultPlugins bool
flagAddDefaultConfig bool
)
flag.CommandLine.SetOutput(os.Stdout)
flag.Usage = func() {
fmt.Println("Usage: spilltea [flags]")
fmt.Println("")
flag.PrintDefaults()
}
flag.Parse()
if *flagVersion {
fmt.Println(version)
os.Exit(0)
}
if *flagAddDefaultPlugins {
home, _ := os.UserHomeDir()
cfgPath := filepath.Join(home, ".config", "spilltea", "config.yaml")
if *flagConfig != "" {
cfgPath = *flagConfig
}
if err := config.Load(cfgPath); err != nil {
fmt.Fprintf(os.Stderr, "config: %v\n", err)
os.Exit(1)
}
dir := config.ExpandPath(config.Global.App.PluginsDir)
if *flagPluginsDir != "" {
dir = *flagPluginsDir
}
n, err := spilltea.InstallDefaultPlugins(dir)
if err != nil {
fmt.Fprintf(os.Stderr, "add-default-plugins: %v\n", err)
os.Exit(1)
}
fmt.Printf("added %d plugin(s) to %s\n", n, dir)
os.Exit(0)
}
if *flagAddDefaultConfig {
home, _ := os.UserHomeDir()
cfgPath := filepath.Join(home, ".config", "spilltea", "config.yaml")
if *flagConfig != "" {
cfgPath = *flagConfig
}
if err := config.WriteDefaultConfig(cfgPath); err != nil {
fmt.Fprintf(os.Stderr, "add-default-config: %v\n", err)
os.Exit(1)
}
fmt.Printf("default config written to %s\n", cfgPath)
os.Exit(0)
}
if *flagProject != "" && !homeUI.IsValidProjectName(*flagProject) {
fmt.Fprintf(os.Stderr, "project: invalid name %q (only lowercase letters, digits, - and _ are allowed)\n", *flagProject)
os.Exit(1)
}
home, _ := os.UserHomeDir()
cfgPath := filepath.Join(home, ".config", "spilltea", "config.yaml")
if *flagConfig != "" {
cfgPath = *flagConfig
}
if err := config.Load(cfgPath); err != nil {
fmt.Fprintf(os.Stderr, "config: %v\n", err)
os.Exit(1)
}
config.Global.Version = version
if *flagPluginsDir != "" {
config.Global.App.PluginsDir = *flagPluginsDir
}
if *flagHost != "" {
config.Global.App.Host = *flagHost
}
if *flagPort != 0 {
config.Global.App.Port = *flagPort
}
if *flagUpstreamProxy != "" {
config.Global.App.UpstreamProxy = *flagUpstreamProxy
}
style.Init()
icons.Init(config.Global)
keys.Init(config.Global)
projectDir := config.ExpandPath(config.Global.App.ProjectDir)
// If --project flag is set, skip the home screen entirely.
if *flagProject != "" {
project, err := homeUI.OpenProject(projectDir, *flagProject)
if err != nil {
fmt.Fprintf(os.Stderr, "project: %v\n", err)
os.Exit(1)
}
broker := intercept.NewBroker()
m := appUI.New(broker, project.Name, project.Path)
final, err := tea.NewProgram(m).Run()
if err != nil {
fmt.Fprintf(os.Stderr, "tui: %v\n", err)
os.Exit(1)
}
if app, ok := final.(appUI.Model); ok {
if ferr := app.FatalErr(); ferr != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", ferr)
os.Exit(1)
rootCmd := &cobra.Command{
Use: "spilltea",
Short: "A minimal, terminal-based HTTP(S) proxy for pentesters and CTF players.",
Version: version,
SilenceUsage: true,
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
if flagAddDefaultPlugins {
home, _ := os.UserHomeDir()
cfgPath := filepath.Join(home, ".config", "spilltea", "config.yaml")
if flagConfig != "" {
cfgPath = flagConfig
}
if err := config.Load(cfgPath); err != nil {
return fmt.Errorf("config: %w", err)
}
dir := config.ExpandPath(config.Global.App.PluginsDir)
if flagPluginsDir != "" {
dir = flagPluginsDir
}
n, err := spilltea.InstallDefaultPlugins(dir)
if err != nil {
return fmt.Errorf("add-default-plugins: %w", err)
}
fmt.Printf("added %d plugin(s) to %s\n", n, dir)
return nil
}
}
return
if flagAddDefaultConfig {
home, _ := os.UserHomeDir()
cfgPath := filepath.Join(home, ".config", "spilltea", "config.yaml")
if flagConfig != "" {
cfgPath = flagConfig
}
if err := config.WriteDefaultConfig(cfgPath); err != nil {
return fmt.Errorf("add-default-config: %w", err)
}
fmt.Printf("default config written to %s\n", cfgPath)
return nil
}
if flagProject != "" && !homeUI.IsValidProjectName(flagProject) {
return fmt.Errorf("project: invalid name %q (only lowercase letters, digits, - and _ are allowed)", flagProject)
}
home, _ := os.UserHomeDir()
cfgPath := filepath.Join(home, ".config", "spilltea", "config.yaml")
if flagConfig != "" {
cfgPath = flagConfig
}
if err := config.Load(cfgPath); err != nil {
return fmt.Errorf("config: %w", err)
}
config.Global.Version = version
if flagPluginsDir != "" {
config.Global.App.PluginsDir = flagPluginsDir
}
if flagHost != "" {
config.Global.App.Host = flagHost
}
if flagPort != 0 {
config.Global.App.Port = flagPort
}
if flagUpstreamProxy != "" {
config.Global.App.UpstreamProxy = flagUpstreamProxy
}
style.Init()
icons.Init(config.Global)
keys.Init(config.Global)
projectDir := config.ExpandPath(config.Global.App.ProjectDir)
if flagProject != "" {
project, err := homeUI.OpenProject(projectDir, flagProject)
if err != nil {
return fmt.Errorf("project: %w", err)
}
broker := intercept.NewBroker()
m := appUI.New(broker, project.Name, project.Path)
final, err := tea.NewProgram(m).Run()
if err != nil {
return fmt.Errorf("tui: %w", err)
}
if app, ok := final.(appUI.Model); ok {
if ferr := app.FatalErr(); ferr != nil {
return ferr
}
}
return nil
}
root := rootModel{home: homeUI.New(projectDir)}
final, err := tea.NewProgram(root).Run()
if err != nil {
return fmt.Errorf("tui: %w", err)
}
if r, ok := final.(rootModel); ok {
if ferr := r.FatalErr(); ferr != nil {
return ferr
}
}
return nil
},
}
// Run home + app in a single program to avoid a blank flash on transition.
root := rootModel{home: homeUI.New(projectDir)}
final, err := tea.NewProgram(root).Run()
if err != nil {
fmt.Fprintf(os.Stderr, "tui: %v\n", err)
rootCmd.Flags().StringVarP(&flagConfig, "config", "c", "", "path to config file")
rootCmd.Flags().StringVar(&flagPluginsDir, "plugins-dir", "", "path to plugins dir (overrides config)")
rootCmd.Flags().StringVar(&flagHost, "host", "", "proxy host (overrides config)")
rootCmd.Flags().IntVarP(&flagPort, "port", "p", 0, "proxy port (overrides config)")
rootCmd.Flags().StringVar(&flagUpstreamProxy, "upstream-proxy", "", "upstream proxy URL, e.g. http://user:pass@host:8888 (overrides config)")
rootCmd.Flags().StringVarP(&flagProject, "project", "P", "", `project name to open directly, or "tmp" for a temporary session`)
rootCmd.Flags().BoolVar(&flagAddDefaultPlugins, "add-default-plugins", false, "copy built-in example plugins into the plugins dir and exit")
rootCmd.Flags().BoolVar(&flagAddDefaultConfig, "add-default-config", false, "copy the default config file to the config path and exit")
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if r, ok := final.(rootModel); ok {
if ferr := r.FatalErr(); ferr != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", ferr)
os.Exit(1)
}
}
}