Skip to content

CLI Integration

MeGaNeKo edited this page Mar 20, 2026 · 1 revision

CLI Integration

The neomacli package provides an optional CLI for Neoma services, built on Cobra. It bridges configuration from CLI flags, environment variables, and defaults into a type-safe Go struct.

Options Struct

Define your configuration as a struct with tags:

type Options struct {
    Host  string `doc:"Hostname to listen on" default:"0.0.0.0"`
    Port  int    `doc:"Port to listen on" short:"p" default:"8080"`
    Debug bool   `doc:"Enable debug mode"`
}
Tag Description
doc Help text for the flag
default Default value
short Short flag name (single character)
name Override the flag name (default is kebab-case of the field name)

Supported field types: string, int, int64, bool, time.Duration.

Creating and Running the CLI

cli := neomacli.New(func(hooks neomacli.Hooks, opts *Options) {
    // Set up router, API, server using opts
    // ...
    hooks.OnStart(func() {
        srv.ListenAndServe()
    })
    hooks.OnStop(func() {
        srv.Shutdown(context.Background())
    })
})

cli.Run()

neomacli.New accepts a generic callback that receives Hooks and a pointer to your options struct. The Hooks interface provides OnStart and OnStop callbacks. OnStart runs in a goroutine; the main goroutine waits for SIGINT/SIGTERM and then calls OnStop.

Environment Variable Mapping

Flags map to environment variables with a SERVICE_ prefix. The field name is converted to uppercase snake_case:

Field Flag Env Var
Port --port SERVICE_PORT
Debug --debug SERVICE_DEBUG

Nested structs use dot-separated flag names and underscore-separated env vars:

Field Flag Env Var
Database.Host --database.host SERVICE_DATABASE_HOST

Precedence (highest first): CLI flag, environment variable, default tag value.

Custom Commands

Access the root Cobra command via Root() to add subcommands. Use neomacli.WithOptions for type-safe access to the parsed options:

cli.Root().AddCommand(&cobra.Command{
    Use:   "migrate",
    Short: "Run database migrations",
    Run: neomacli.WithOptions(func(cmd *cobra.Command, args []string, opts *Options) {
        fmt.Printf("Running migrations with debug=%v\n", opts.Debug)
    }),
})

Custom Logger

By default, diagnostic messages go to stderr. Override with WithLogger:

cli := neomacli.New(onParsed, neomacli.WithLogger(myLogger))

The logger must implement neomacli.Logger:

type Logger interface {
    Println(args ...any)
}

Complete Example

package main

import (
    "context"
    "fmt"
    "log"
    "net/http"

    "github.com/MeGaNeKoS/neoma/adapters/neomachi/v5"
    "github.com/MeGaNeKoS/neoma/core"
    "github.com/MeGaNeKoS/neoma/neoma"
    "github.com/MeGaNeKoS/neoma/neomacli"
    "github.com/go-chi/chi/v5"
)

type Options struct {
    Host  string `doc:"Host to listen on" default:"0.0.0.0"`
    Port  int    `doc:"Port to listen on" short:"p" default:"8080"`
    Debug bool   `doc:"Enable debug logging"`
}

func main() {
    cli := neomacli.New(func(hooks neomacli.Hooks, opts *Options) {
        router := chi.NewMux()
        adapter := neomachi.NewAdapter(router)
        config := neoma.DefaultConfig("My API", "1.0.0")
        api := neoma.NewAPI(config, adapter)

        // Register operations...
        _ = api

        addr := fmt.Sprintf("%s:%d", opts.Host, opts.Port)
        srv := &http.Server{Addr: addr, Handler: router}

        hooks.OnStart(func() {
            fmt.Printf("Server starting on %s\n", addr)
            if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
                log.Fatal(err)
            }
        })

        hooks.OnStop(func() {
            srv.Shutdown(context.Background())
        })
    })

    cli.Run()
}
# Use defaults
./myapp

# Override with flags
./myapp --port 3000 --debug

# Override with environment variables
SERVICE_PORT=3000 SERVICE_DEBUG=true ./myapp

Clone this wiki locally