package report import ( "fmt" "io" "os" "strings" "sync" "github.com/pinchtab/pinchtab/internal/config" "github.com/pinchtab/pinchtab/internal/termstyle" ) var menuBannerOnce sync.Once type StartupBannerOptions struct { Mode string ListenAddr string ListenStatus string PublicURL string Strategy string Allocation string ProfileDir string } func PrintStartupBanner(cfg *config.RuntimeConfig, opts StartupBannerOptions) { if opts.Mode == "menu" { printed := false menuBannerOnce.Do(func() { printed = true }) if !printed { return } } writeBannerLine(renderStartupLogo(blankIfEmpty(opts.Mode, "server"))) writeBannerf(" %s %s\n", styleLabel("listen"), formatListenValue(blankIfEmpty(opts.ListenAddr, cfg.ListenAddr()), defaultListenStatus(opts.Mode, opts.ListenStatus))) if opts.PublicURL != "" { writeBannerf(" %s %s\n", styleLabel("url"), styleValue(opts.PublicURL)) } strat := blankIfEmpty(opts.Strategy, "manual") alloc := blankIfEmpty(opts.Allocation, "none") writeBannerf(" %s %s\n", styleLabel("str,plc"), styleValue(fmt.Sprintf("%s,%s", strat, alloc))) daemonStatus := styleStdout(warningStyle, "not installed") if IsDaemonInstalled() { daemonStatus = styleStdout(successStyle, "ok") } writeBannerf(" %s %s\n", styleLabel("daemon"), daemonStatus) posture := AssessSecurityPosture(cfg) writeBannerf(" %s %s %s\n", styleLabel("security"), styleSecurityBar(posture.Level, posture.Bar), styleSecurityLevel(posture.Level)) if opts.ProfileDir != "" { writeBannerf(" %s %s\n", styleLabel("profile"), styleValue(opts.ProfileDir)) } writeBannerLine("") } func PrintSecuritySummary(w io.Writer, cfg *config.RuntimeConfig, prefix string, detailed bool) { posture := AssessSecurityPosture(cfg) if detailed { writeSummaryf(w, "%s%s %s %s\n", prefix, styleHeading("Security"), styleSecurityBar(posture.Level, posture.Bar), styleSecurityLevel(posture.Level)) for _, check := range posture.Checks { writeSummaryf(w, "%s %s %s %s\n", prefix, styleMarker(check.Passed), styleCheckLabel(check.Label), styleCheckDetail(check.Passed, check.Detail)) } } else { writeSummaryf(w, "%s%s %s %s\n", prefix, styleLabel("security"), styleSecurityBar(posture.Level, posture.Bar), styleSecurityLevel(posture.Level)) } } func blankIfEmpty(value, fallback string) string { if strings.TrimSpace(value) == "" { return fallback } return value } func renderStartupLogo(mode string) string { if mode == "menu" || mode == "" { return styleLogo(startupLogo) } return styleLogo(startupLogo) + " " + styleMode(mode) } func writeBannerLine(line string) { _, _ = fmt.Fprintln(os.Stdout, line) } func writeBannerf(format string, args ...any) { _, _ = fmt.Fprintf(os.Stdout, format, args...) } func writeSummaryf(w io.Writer, format string, args ...any) { _, _ = fmt.Fprintf(w, format, args...) } func styleHeading(text string) string { return applyStyle(text, headingStyle) } func styleLogo(text string) string { return applyStyle(text, headingStyle) } func styleMode(text string) string { return applyStyle(text, commandStyle) } func styleLabel(text string) string { return applyStyle(fmt.Sprintf("%-8s", text), mutedStyle) } func styleValue(text string) string { return applyStyle(text, valueStyle) } func styleListenStatus(status string) string { switch status { case "running": return applyStyle(status, successStyle) case "starting": return applyStyle(status, warningStyle) case "stopped": return applyStyle(status, mutedStyle) default: return applyStyle(status, valueStyle) } } func styleCheckLabel(text string) string { return applyStyle(fmt.Sprintf("%-20s", text), mutedStyle) } func styleCheckDetail(passed bool, text string) string { if passed { return applyStyle(text, successStyle) } return applyStyle(text, warningStyle) } func styleMarker(passed bool) string { if passed { return applyStyle("ok", successStyle) } return applyStyle("!!", errorStyle) } func styleSecurityLevel(level string) string { return applyStyle(level, termstyle.NewStyle().Foreground(termstyle.Color(SecurityLevelColor(level))).Bold(true)) } func styleSecurityBar(level, bar string) string { return applyStyle(bar, termstyle.NewStyle().Foreground(termstyle.Color(SecurityLevelColor(level))).Bold(true)) } func SecurityLevelColor(level string) string { switch level { case "LOCKED": return string(colorSuccess) case "GUARDED": return string(colorWarning) case "ELEVATED": return string(colorWarning) default: return string(colorDanger) } } func applyStyle(text string, style termstyle.Style) string { return styleStdout(style, text) } func formatListenValue(addr, status string) string { status = strings.TrimSpace(status) if status == "" { return styleValue(addr) } return fmt.Sprintf("%-8s %s", styleListenStatus(status), styleValue(addr)) } func defaultListenStatus(mode, explicit string) string { if strings.TrimSpace(explicit) != "" { return explicit } switch mode { case "menu": return "stopped" case "server", "bridge", "mcp": return "starting" default: return "" } } const startupLogo = ` ____ _ _ _____ _ | _ \(_)_ __ ___| |__ |_ _|_ _| |__ | |_) | | '_ \ / __| '_ \ | |/ _ | '_ \ | __/| | | | | (__| | | | | | (_| | |_) | |_| |_|_| |_|\___|_| |_| |_|\__,_|_.__/`