package cli import ( "io" "os" "strings" "time" "sneak.berlin/go/vaultik/internal/globals" "sneak.berlin/go/vaultik/internal/ui" ) // shortCommitLen is the number of git commit hash characters shown in // the startup banner. const shortCommitLen = 12 // Entry is the main entry point for the CLI application. // It prints the startup banner to stdout (unless a banner-suppressing // flag is present in os.Args — see bannerSuppressedInArgs), executes the // root cobra command, and routes any returned error through the // ui.Writer so the user sees a properly formatted "🛑 ERROR:" line. func Entry() { emitStartupBanner(os.Args[1:], os.Stdout) rootCmd := NewRootCommand() rootCmd.SilenceErrors = true err := rootCmd.Execute() if err != nil { ReportErrorf("%s", err.Error()) os.Exit(1) } } // emitStartupBanner writes the startup banner to w unless args (the // argument vector with the program name already stripped) contains a // flag that suppresses it. Split out of Entry so that the decision — the // only thing standing between a --json invocation and a parseable // stdout — is reachable from a test without running the whole CLI. func emitStartupBanner(args []string, w io.Writer) { if bannerSuppressedInArgs(args) { return } short := globals.Commit if len(short) > shortCommitLen { short = short[:shortCommitLen] } writeStartupBanner(ui.New(w), time.Now().UTC(), short) } // ReportErrorf emits a user-facing error to stderr in the standard // 🛑 ERROR: format. Use it from goroutine error paths (where returning // an error to cobra isn't an option) and anywhere else a CLI command // must surface a failure outside the normal RunE return path. func ReportErrorf(format string, args ...any) { ui.New(os.Stderr).Errorf(format, args...) } // bannerSuppressedInArgs reports whether any of args is a flag that // should suppress the startup banner (--quiet/-q/--cron/--json). Stops // at the "--" argument terminator. Recognizes both long forms and short // -q, including combined short flags like "-qv". // // This scans the raw argument vector because the banner is printed // before cobra parses anything — deliberately, so that it still appears // when cobra rejects the arguments and on --help. The consequence is // that a flag is matched wherever it occurs in the vector, including // positions where the command it belongs to would not accept it. // --json is a subcommand flag rather than a persistent one, but so is // --cron (it exists only on `snapshot create`), so this adds no new // class of imprecision. The only cost of a false positive is a missing // decorative banner; the cost of a false negative is a corrupt document // on stdout, so the scan errs deliberately in that direction. func bannerSuppressedInArgs(args []string) bool { for _, a := range args { if a == "--" { return false } switch a { case "--quiet", "-q", "--cron", "--json": return true } if strings.HasPrefix(a, "--quiet=") || strings.HasPrefix(a, "--cron=") || strings.HasPrefix(a, "--json=") { return true } // Combined short flags like -qv or -vq. if len(a) > 1 && a[0] == '-' && a[1] != '-' { for _, c := range a[1:] { if c == 'q' { return true } } } } return false }