diff --git a/cmd/macaron/main.go b/cmd/macaron/main.go index 69d82ed..d490fc8 100644 --- a/cmd/macaron/main.go +++ b/cmd/macaron/main.go @@ -28,6 +28,7 @@ func main() { func run() int { normalizeLegacyArgs() normalizeCommandArgs() + normalizeCompactFlags() var ( scanTargets []string @@ -63,38 +64,38 @@ func run() int { guide bool ) - pflag.StringArrayVarP(&scanTargets, "scan", "s", nil, "Scan target(s)") - pflag.BoolVarP(&status, "status", "S", false, "Show scan status") - pflag.BoolVarP(&results, "results", "R", false, "Show results") - pflag.BoolVarP(&listTools, "list-tools", "L", false, "List external tool availability") - pflag.BoolVarP(&export, "export", "E", false, "Export results to JSON") - pflag.BoolVarP(&configCmd, "config", "C", false, "Show config paths") - pflag.BoolVarP(&pipeline, "pipeline", "P", false, "Show pipeline path (v2 native pipeline is built-in)") - pflag.BoolVar(&serve, "serve", false, "Start web dashboard server") + pflag.StringArrayVar(&scanTargets, "scn", nil, "Scan target(s)") + pflag.BoolVar(&status, "sts", false, "Show scan status") + pflag.BoolVar(&results, "res", false, "Show results") + pflag.BoolVar(&listTools, "lst", false, "List external tool availability") + pflag.BoolVar(&export, "exp", false, "Export results to JSON") + pflag.BoolVar(&configCmd, "cfg", false, "Show config paths") + pflag.BoolVar(&pipeline, "pip", false, "Show pipeline path (v2 native pipeline is built-in)") + pflag.BoolVar(&serve, "srv", false, "Start web dashboard server") - pflag.StringVarP(&filePath, "file", "F", "", "Read targets from file") - pflag.BoolVar(&useStdin, "stdin", false, "Read targets from stdin") - pflag.StringVarP(&domain, "domain", "d", "", "Filter by domain") - pflag.StringVar(&scanID, "id", "", "Fetch specific scan ID") - pflag.StringVarP(&what, "what", "w", "all", "Result view: all|subdomains|live|ports|urls|js|vulns") - pflag.StringVarP(&mode, "mode", "m", "wide", "Mode: wide|narrow|fast|deep|osint") - pflag.BoolVarP(&fast, "fast", "f", false, "Shortcut for mode fast") - pflag.BoolVarP(&narrow, "narrow", "n", false, "Shortcut for mode narrow") - pflag.IntVar(&rate, "rate", 150, "Request rate hint") - pflag.IntVar(&threads, "threads", 30, "Worker threads") - pflag.IntVar(&limit, "limit", 50, "Output limit") - pflag.StringVarP(&output, "output", "o", "", "Output file") - pflag.BoolVarP(&quiet, "quiet", "q", false, "Quiet output") - pflag.BoolVar(&showVersion, "version", false, "Show version") - pflag.StringVar(&serveAddr, "addr", "127.0.0.1:8088", "Dashboard bind address") - pflag.StringVar(&storagePath, "storage", "", "Storage root directory (default: ./storage)") - pflag.StringVar(&stages, "stages", "all", "Comma-separated stages: subdomains,http,ports,urls,vulns") - pflag.StringArrayVar(&setAPI, "set-api", nil, "Set API key as name=value (repeatable). Use empty value to unset.") - pflag.BoolVar(&showAPI, "show-api", false, "Show configured API keys (masked)") - pflag.BoolVar(&setup, "setup", false, "Show setup screen with tool installation status") - pflag.BoolVar(&installTools, "install-tools", false, "Install missing supported tools (Linux)") - pflag.StringVar(&profile, "profile", "balanced", "Workflow profile: passive|balanced|aggressive") - pflag.BoolVar(&guide, "guide", false, "Show first-principles workflow guide") + pflag.StringVar(&filePath, "fil", "", "Read targets from file") + pflag.BoolVar(&useStdin, "inp", false, "Read targets from stdin") + pflag.StringVar(&domain, "dom", "", "Filter by domain") + pflag.StringVar(&scanID, "sid", "", "Fetch specific scan ID") + pflag.StringVar(&what, "wht", "all", "Result view: all|subdomains|live|ports|urls|js|vulns") + pflag.StringVar(&mode, "mod", "wide", "Mode: wide|narrow|fast|deep|osint") + pflag.BoolVar(&fast, "fst", false, "Shortcut for mode fast") + pflag.BoolVar(&narrow, "nrw", false, "Shortcut for mode narrow") + pflag.IntVar(&rate, "rte", 150, "Request rate hint") + pflag.IntVar(&threads, "thr", 30, "Worker threads") + pflag.IntVar(&limit, "lim", 50, "Output limit") + pflag.StringVar(&output, "out", "", "Output file") + pflag.BoolVar(&quiet, "qut", false, "Quiet output") + pflag.BoolVar(&showVersion, "ver", false, "Show version") + pflag.StringVar(&serveAddr, "adr", "127.0.0.1:8088", "Dashboard bind address") + pflag.StringVar(&storagePath, "str", "", "Storage root directory (default: ./storage)") + pflag.StringVar(&stages, "stg", "all", "Comma-separated stages: subdomains,http,ports,urls,vulns") + pflag.StringArrayVar(&setAPI, "sak", nil, "Set API key as name=value (repeatable). Use empty value to unset.") + pflag.BoolVar(&showAPI, "shk", false, "Show configured API keys (masked)") + pflag.BoolVar(&setup, "stp", false, "Show setup screen with tool installation status") + pflag.BoolVar(&installTools, "ins", false, "Install missing supported tools (Linux)") + pflag.StringVar(&profile, "prf", "balanced", "Workflow profile: passive|balanced|aggressive") + pflag.BoolVar(&guide, "gud", false, "Show first-principles workflow guide") pflag.Parse() if showVersion { @@ -277,38 +278,38 @@ func printHelp() { Usage: macaron scan example.com macaron status - macaron results -d example.com -w live - macaron serve --addr 127.0.0.1:8088 + macaron results -dom example.com -wht live + macaron serve -adr 127.0.0.1:8088 macaron setup Core flags: - -s, --scan TARGET Scan one or more targets - -F, --file FILE Read targets from file - --stdin Read targets from stdin - -m, --mode MODE wide|narrow|fast|deep|osint - -S, --status Show scan summaries - -R, --results Show scan details - -E, --export Export JSON - -L, --list-tools Show tool availability - --storage DIR Use custom storage root (default ./storage) - --stages LIST Choose stages: subdomains,http,ports,urls,vulns - --set-api k=v Save API keys to storage config.yaml - --show-api Show masked API keys - --setup Show setup screen with tool status - --install-tools Install missing supported tools (Linux) - --profile NAME passive|balanced|aggressive - --guide Show first-principles workflow guide - --serve Start browser dashboard - --version Show version`) + -scn TARGET Scan one or more targets + -fil FILE Read targets from file + -inp Read targets from stdin + -mod MODE wide|narrow|fast|deep|osint + -sts Show scan summaries + -res Show scan details + -exp Export JSON + -lst Show tool availability + -str DIR Use custom storage root (default ./storage) + -stg LIST Choose stages: subdomains,http,ports,urls,vulns + -sak k=v Save API keys to storage config.yaml + -shk Show masked API keys + -stp Show setup screen with tool status + -ins Install missing supported tools (Linux) + -prf NAME passive|balanced|aggressive + -gud Show first-principles workflow guide + -srv Start browser dashboard + -ver Show version`) } func normalizeLegacyArgs() { for i, arg := range os.Args { if arg == "-setup" { - os.Args[i] = "--setup" + os.Args[i] = "-stp" } if arg == "-install-tools" { - os.Args[i] = "--install-tools" + os.Args[i] = "-ins" } } } @@ -327,29 +328,95 @@ func normalizeCommandArgs() { args = append(args, tok) continue } - args = append(args, "--scan", tok) + args = append(args, "--scn", tok) } if len(args) == 1 { - args = append(args, "--scan") + args = append(args, "--scn") } os.Args = args case "status": - os.Args = append([]string{os.Args[0], "--status"}, rest...) + os.Args = append([]string{os.Args[0], "--sts"}, rest...) case "results": - os.Args = append([]string{os.Args[0], "--results"}, rest...) + os.Args = append([]string{os.Args[0], "--res"}, rest...) case "serve": - os.Args = append([]string{os.Args[0], "--serve"}, rest...) + os.Args = append([]string{os.Args[0], "--srv"}, rest...) case "setup": - os.Args = append([]string{os.Args[0], "--setup"}, rest...) + os.Args = append([]string{os.Args[0], "--stp"}, rest...) case "export": - os.Args = append([]string{os.Args[0], "--export"}, rest...) + os.Args = append([]string{os.Args[0], "--exp"}, rest...) case "config": - os.Args = append([]string{os.Args[0], "--config"}, rest...) + os.Args = append([]string{os.Args[0], "--cfg"}, rest...) case "guide": - os.Args = append([]string{os.Args[0], "--guide"}, rest...) + os.Args = append([]string{os.Args[0], "--gud"}, rest...) } } +func normalizeCompactFlags() { + flagMap := map[string]string{ + "scan": "scn", "s": "scn", "scn": "scn", + "status": "sts", "S": "sts", "sts": "sts", + "results": "res", "R": "res", "res": "res", + "list-tools": "lst", "L": "lst", "lst": "lst", + "export": "exp", "E": "exp", "exp": "exp", + "config": "cfg", "C": "cfg", "cfg": "cfg", + "pipeline": "pip", "P": "pip", "pip": "pip", + "serve": "srv", "srv": "srv", + "file": "fil", "F": "fil", "fil": "fil", + "stdin": "inp", "inp": "inp", + "domain": "dom", "d": "dom", "dom": "dom", + "id": "sid", "sid": "sid", + "what": "wht", "w": "wht", "wht": "wht", + "mode": "mod", "m": "mod", "mod": "mod", + "fast": "fst", "f": "fst", "fst": "fst", + "narrow": "nrw", "n": "nrw", "nrw": "nrw", + "rate": "rte", "rte": "rte", + "threads": "thr", "thr": "thr", + "limit": "lim", "lim": "lim", + "output": "out", "o": "out", "out": "out", + "quiet": "qut", "q": "qut", "qut": "qut", + "version": "ver", "ver": "ver", + "addr": "adr", "adr": "adr", + "storage": "str", "str": "str", + "stages": "stg", "stg": "stg", + "set-api": "sak", "sak": "sak", + "show-api": "shk", "shk": "shk", + "setup": "stp", "stp": "stp", + "install-tools": "ins", "ins": "ins", + "profile": "prf", "prf": "prf", + "guide": "gud", "gud": "gud", + } + args := make([]string, 0, len(os.Args)) + args = append(args, os.Args[0]) + for _, arg := range os.Args[1:] { + if strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, "--") && len(arg) > 2 { + key := strings.TrimPrefix(arg, "-") + val := "" + if i := strings.IndexRune(key, '='); i >= 0 { + val = key[i:] + key = key[:i] + } + if mapped, ok := flagMap[key]; ok { + args = append(args, "--"+mapped+val) + continue + } + } + if strings.HasPrefix(arg, "--") { + key := strings.TrimPrefix(arg, "--") + val := "" + if i := strings.IndexRune(key, '='); i >= 0 { + val = key[i:] + key = key[:i] + } + if mapped, ok := flagMap[key]; ok { + args = append(args, "--"+mapped+val) + continue + } + } + args = append(args, arg) + } + os.Args = args +} + func applyProfile(profile string, mode *string, rate *int, threads *int, stages *string) { switch strings.ToLower(strings.TrimSpace(profile)) { case "passive": @@ -382,21 +449,21 @@ func printGuide() { 1) Setup once: macaron setup - macaron --install-tools - macaron --set-api securitytrails=YOUR_KEY + macaron -ins + macaron -sak securitytrails=YOUR_KEY 2) Run intentional scans: - macaron scan target.com --profile passive - macaron scan target.com --profile balanced - macaron scan target.com --profile aggressive --stages subdomains,http,ports,urls,vulns + macaron scan target.com -prf passive + macaron scan target.com -prf balanced + macaron scan target.com -prf aggressive -stg subdomains,http,ports,urls,vulns 3) Inspect and decide: macaron status - macaron results -d target.com -w live + macaron results -dom target.com -wht live macaron serve 4) Export/share: - macaron export -o target.json + macaron export -out target.json Profiles: passive low-noise, low-rate, mostly passive collection diff --git a/cmd/macaron/main_test.go b/cmd/macaron/main_test.go index e419c53..fb4d47d 100644 --- a/cmd/macaron/main_test.go +++ b/cmd/macaron/main_test.go @@ -15,17 +15,50 @@ func withArgs(args []string, fn func()) { func TestNormalizeLegacySetup(t *testing.T) { withArgs([]string{"macaron", "-setup"}, func() { normalizeLegacyArgs() - if osArgs()[1] != "--setup" { - t.Fatalf("expected --setup, got %s", osArgs()[1]) + if osArgs()[1] != "-stp" { + t.Fatalf("expected -stp, got %s", osArgs()[1]) } }) } func TestNormalizeCommandScan(t *testing.T) { withArgs([]string{"macaron", "scan", "example.com", "--fast"}, func() { + normalizeCompactFlags() normalizeCommandArgs() args := osArgs() - want := []string{"macaron", "--scan", "example.com", "--fast"} + want := []string{"macaron", "--scn", "example.com", "--fst"} + if len(args) != len(want) { + t.Fatalf("unexpected len: %#v", args) + } + for i := range want { + if args[i] != want[i] { + t.Fatalf("idx %d: got %q want %q", i, args[i], want[i]) + } + } + }) +} + +func TestNormalizeLongToCompact(t *testing.T) { + withArgs([]string{"macaron", "--scan", "example.com", "--threads", "20"}, func() { + normalizeCompactFlags() + args := osArgs() + want := []string{"macaron", "--scn", "example.com", "--thr", "20"} + if len(args) != len(want) { + t.Fatalf("unexpected len: %#v", args) + } + for i := range want { + if args[i] != want[i] { + t.Fatalf("idx %d: got %q want %q", i, args[i], want[i]) + } + } + }) +} + +func TestNormalizeSingleDashCompact(t *testing.T) { + withArgs([]string{"macaron", "-stp", "-ver"}, func() { + normalizeCompactFlags() + args := osArgs() + want := []string{"macaron", "--stp", "--ver"} if len(args) != len(want) { t.Fatalf("unexpected len: %#v", args) }