package cmd import ( "fmt" "os" "path/filepath" "github.com/spf13/cobra" "github.com/spf13/viper" ) var cfgFile string // rootCmd is the base command when called without any subcommands. var rootCmd = &cobra.Command{ Use: "keyhunter", Short: "KeyHunter -- detect leaked LLM API keys across 108+ providers", Long: `KeyHunter scans files, git history, and internet sources for leaked LLM API keys. Supports 108+ providers with Aho-Corasick pre-filtering and regex + entropy detection.`, SilenceUsage: true, } // Execute is the entry point called by main.go. // // OUT-06 exit-code contract: // - 0: clean scan (no findings) // - 1: findings present (emitted directly by scanCmd via os.Exit(1)) // - 2: scan/tool error (any RunE returning a non-nil error) // // Cobra prints the error message itself; we only translate the non-nil return // into exit code 2 so CI consumers can distinguish "found leaks" from "scan // failed". func Execute() { if err := rootCmd.Execute(); err != nil { os.Exit(2) } } func init() { cobra.OnInitialize(initConfig) rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default: ~/.keyhunter.yaml)") rootCmd.AddCommand(scanCmd) rootCmd.AddCommand(providersCmd) rootCmd.AddCommand(configCmd) rootCmd.AddCommand(legalCmd) // Stub commands for future phases (per CLI-01 requirement of 11 commands) rootCmd.AddCommand(verifyCmd) rootCmd.AddCommand(importCmd) rootCmd.AddCommand(reconCmd) rootCmd.AddCommand(keysCmd) rootCmd.AddCommand(serveCmd) rootCmd.AddCommand(dorksCmd) rootCmd.AddCommand(hookCmd) rootCmd.AddCommand(scheduleCmd) } func initConfig() { if cfgFile != "" { viper.SetConfigFile(cfgFile) } else { home, err := os.UserHomeDir() if err != nil { fmt.Fprintln(os.Stderr, "warning: cannot determine home directory:", err) return } viper.SetConfigName(".keyhunter") viper.SetConfigType("yaml") viper.AddConfigPath(home) viper.AddConfigPath(".") } viper.SetEnvPrefix("KEYHUNTER") viper.AutomaticEnv() // Defaults viper.SetDefault("scan.workers", 0) // 0 = auto (CPU*8) viper.SetDefault("database.path", filepath.Join(mustHomeDir(), ".keyhunter", "keyhunter.db")) // Config file is optional -- ignore if not found _ = viper.ReadInConfig() } func mustHomeDir() string { h, _ := os.UserHomeDir() return h }