refactor(cli): clean DX with direct function calls

- cli.Success(), cli.Error(), etc. now print directly
- String-returning versions renamed to cli.FmtSuccess(), etc.
- Removes App() from common usage path
- Usage: cli.Success("done") instead of fmt.Println(cli.Success("done"))

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Snider 2026-01-30 10:41:35 +00:00
parent 23d399407c
commit 22aa1df30a
3 changed files with 75 additions and 101 deletions

View file

@ -95,12 +95,12 @@ func runDoctor(verbose bool) error {
// Summary
fmt.Println()
if failed > 0 {
fmt.Println(cli.Error(i18n.T("cmd.doctor.issues", map[string]interface{}{"Count": failed})))
cli.Error(i18n.T("cmd.doctor.issues", map[string]interface{}{"Count": failed}))
fmt.Printf("\n%s\n", i18n.T("cmd.doctor.install_missing"))
printInstallInstructions()
return fmt.Errorf("%s", i18n.T("cmd.doctor.issues_error", map[string]interface{}{"Count": failed}))
}
fmt.Println(cli.Success(i18n.T("cmd.doctor.ready")))
cli.Success(i18n.T("cmd.doctor.ready"))
return nil
}

View file

@ -1,10 +1,16 @@
// Package cli provides the CLI runtime and utilities.
//
// The CLI uses the Core framework for its own runtime, providing:
// - Global singleton access via cli.App()
// - Output service for styled terminal printing
// - Signal handling for graceful shutdown
// - Worker bundle spawning for commands
// The CLI uses the Core framework for its own runtime. Usage is simple:
//
// cli.Init(cli.Options{AppName: "core"})
// defer cli.Shutdown()
//
// cli.Success("Done!")
// cli.Error("Failed")
// if cli.Confirm("Proceed?") { ... }
//
// // When you need the Core instance
// c := cli.Core()
package cli
import (
@ -19,39 +25,32 @@ import (
)
var (
instance *Runtime
instance *runtime
once sync.Once
)
// Runtime is the CLI's Core runtime.
type Runtime struct {
Core *framework.Core
// runtime is the CLI's internal Core runtime.
type runtime struct {
core *framework.Core
ctx context.Context
cancel context.CancelFunc
}
// RuntimeOptions configures the CLI runtime.
type RuntimeOptions struct {
// AppName is the CLI application name (used in output)
// Options configures the CLI runtime.
type Options struct {
AppName string
// Version is the CLI version string
Version string
}
// Init initialises the global CLI runtime.
// Call this once at startup (typically in main.go).
func Init(opts RuntimeOptions) error {
func Init(opts Options) error {
var initErr error
once.Do(func() {
ctx, cancel := context.WithCancel(context.Background())
core, err := framework.New(
framework.WithService(NewOutputService(OutputServiceOptions{
AppName: opts.AppName,
})),
framework.WithService(NewSignalService(SignalServiceOptions{
Cancel: cancel,
})),
c, err := framework.New(
framework.WithName("signal", newSignalService(cancel)),
framework.WithServiceLock(),
)
if err != nil {
@ -60,14 +59,13 @@ func Init(opts RuntimeOptions) error {
return
}
instance = &Runtime{
Core: core,
instance = &runtime{
core: c,
ctx: ctx,
cancel: cancel,
}
// Start services
if err := core.ServiceStartup(ctx, nil); err != nil {
if err := c.ServiceStartup(ctx, nil); err != nil {
initErr = err
return
}
@ -75,114 +73,91 @@ func Init(opts RuntimeOptions) error {
return initErr
}
// App returns the global CLI runtime.
// Panics if Init() hasn't been called.
func App() *Runtime {
func mustInit() {
if instance == nil {
panic("cli.App() called before cli.Init()")
panic("cli not initialised - call cli.Init() first")
}
return instance
}
// --- Core Access ---
// Core returns the CLI's framework Core instance.
func Core() *framework.Core {
mustInit()
return instance.core
}
// Context returns the CLI's root context.
// This context is cancelled on shutdown signals.
func (r *Runtime) Context() context.Context {
return r.ctx
// Cancelled on SIGINT/SIGTERM.
func Context() context.Context {
mustInit()
return instance.ctx
}
// Shutdown gracefully shuts down the CLI runtime.
func (r *Runtime) Shutdown() {
r.cancel()
r.Core.ServiceShutdown(r.ctx)
// Shutdown gracefully shuts down the CLI.
func Shutdown() {
if instance == nil {
return
}
instance.cancel()
instance.core.ServiceShutdown(instance.ctx)
}
// Output returns the output service for styled printing.
func (r *Runtime) Output() *OutputService {
return framework.MustServiceFor[*OutputService](r.Core, "output")
}
// --- Output Service ---
// OutputServiceOptions configures the output service.
type OutputServiceOptions struct {
AppName string
}
// OutputService provides styled terminal output.
type OutputService struct {
*framework.ServiceRuntime[OutputServiceOptions]
}
// NewOutputService creates an output service factory.
func NewOutputService(opts OutputServiceOptions) func(*framework.Core) (any, error) {
return func(c *framework.Core) (any, error) {
return &OutputService{
ServiceRuntime: framework.NewServiceRuntime(c, opts),
}, nil
}
}
// --- Output Functions ---
// Success prints a success message with checkmark.
func (s *OutputService) Success(msg string) {
func Success(msg string) {
fmt.Println(SuccessStyle.Render(SymbolCheck + " " + msg))
}
// Error prints an error message with cross.
func (s *OutputService) Error(msg string) {
func Error(msg string) {
fmt.Println(ErrorStyle.Render(SymbolCross + " " + msg))
}
// Warning prints a warning message.
func (s *OutputService) Warning(msg string) {
func Warning(msg string) {
fmt.Println(WarningStyle.Render(SymbolWarning + " " + msg))
}
// Info prints an info message.
func (s *OutputService) Info(msg string) {
func Info(msg string) {
fmt.Println(InfoStyle.Render(SymbolInfo + " " + msg))
}
// Title prints a title/header.
func (s *OutputService) Title(msg string) {
func Title(msg string) {
fmt.Println(TitleStyle.Render(msg))
}
// Dim prints dimmed/subtle text.
func (s *OutputService) Dim(msg string) {
func Dim(msg string) {
fmt.Println(DimStyle.Render(msg))
}
// --- Signal Service ---
// --- Signal Service (internal) ---
// SignalServiceOptions configures the signal service.
type SignalServiceOptions struct {
Cancel context.CancelFunc
}
// SignalService handles OS signals for graceful shutdown.
type SignalService struct {
*framework.ServiceRuntime[SignalServiceOptions]
type signalService struct {
cancel context.CancelFunc
sigChan chan os.Signal
}
// NewSignalService creates a signal service factory.
func NewSignalService(opts SignalServiceOptions) func(*framework.Core) (any, error) {
func newSignalService(cancel context.CancelFunc) func(*framework.Core) (any, error) {
return func(c *framework.Core) (any, error) {
return &SignalService{
ServiceRuntime: framework.NewServiceRuntime(c, opts),
return &signalService{
cancel: cancel,
sigChan: make(chan os.Signal, 1),
}, nil
}
}
// OnStartup starts listening for signals.
func (s *SignalService) OnStartup(ctx context.Context) error {
func (s *signalService) OnStartup(ctx context.Context) error {
signal.Notify(s.sigChan, syscall.SIGINT, syscall.SIGTERM)
go func() {
select {
case <-s.sigChan:
s.Opts().Cancel()
s.cancel()
case <-ctx.Done():
}
}()
@ -190,8 +165,7 @@ func (s *SignalService) OnStartup(ctx context.Context) error {
return nil
}
// OnShutdown stops listening for signals.
func (s *SignalService) OnShutdown(ctx context.Context) error {
func (s *signalService) OnShutdown(ctx context.Context) error {
signal.Stop(s.sigChan)
close(s.sigChan)
return nil

View file

@ -327,23 +327,23 @@ var (
// Helper Functions
// ─────────────────────────────────────────────────────────────────────────────
// Success returns a styled success message with checkmark.
func Success(msg string) string {
// FmtSuccess returns a styled success message with checkmark.
func FmtSuccess(msg string) string {
return fmt.Sprintf("%s %s", SuccessStyle.Render(SymbolCheck), msg)
}
// Error returns a styled error message with cross.
func Error(msg string) string {
// FmtError returns a styled error message with cross.
func FmtError(msg string) string {
return fmt.Sprintf("%s %s", ErrorStyle.Render(SymbolCross), msg)
}
// Warning returns a styled warning message with warning symbol.
func Warning(msg string) string {
// FmtWarning returns a styled warning message with warning symbol.
func FmtWarning(msg string) string {
return fmt.Sprintf("%s %s", WarningStyle.Render(SymbolWarning), msg)
}
// Info returns a styled info message with info symbol.
func Info(msg string) string {
// FmtInfo returns a styled info message with info symbol.
func FmtInfo(msg string) string {
return fmt.Sprintf("%s %s", InfoStyle.Render(SymbolInfo), msg)
}
@ -464,13 +464,13 @@ func Header(title string, withSeparator bool) string {
return fmt.Sprintf("\n%s", HeaderStyle.Render(title))
}
// Title returns a styled command/section title.
func Title(text string) string {
// FmtTitle returns a styled command/section title.
func FmtTitle(text string) string {
return TitleStyle.Render(text)
}
// Dim returns dimmed text.
func Dim(text string) string {
// FmtDim returns dimmed text.
func FmtDim(text string) string {
return DimStyle.Render(text)
}