Documentation
¶
Overview ¶
Package protocli provides framework features for generated gRPC CLI commands.
This package is automatically imported by generated CLI code and provides lifecycle hooks, output formatting, configuration options, and utilities for CLI applications.
Options ¶
The Option type provides a functional options pattern for configuring CLI behavior:
cmd := proto.BuildUserServiceCLI(ctx, impl,
protocli.BeforeCommand(func(ctx context.Context, cmd *cli.Command) error {
log.Printf("Running command: %s", cmd.Name)
return nil
}),
protocli.AfterCommand(func(ctx context.Context, cmd *cli.Command) error {
log.Printf("Completed command: %s", cmd.Name)
return nil
}),
protocli.WithOutputFormats(
protocli.JSON(),
protocli.YAML(),
),
)
Lifecycle Hooks ¶
- BeforeCommand: Runs before each command execution
- AfterCommand: Runs after each command execution
Hooks receive the context and command, allowing for logging, validation, metrics collection, or other cross-cutting concerns.
Output Formats ¶
The CLI automatically supports --format and --output flags for all commands:
- --format: Specifies output format (go, json, yaml, or custom)
- --output: Specifies output file (- or empty for stdout)
Built-in formats (use factory functions to create them):
- protocli.Go(): Default Go %+v formatting (automatically used if no formats registered)
- protocli.JSON(): JSON output with optional --pretty flag
- protocli.YAML(): YAML-style output
If no formats are explicitly registered via WithOutputFormats, the Go format is used as the default. Custom formats can be registered and will define additional flags (e.g., --pretty for JSON) that are automatically added to all commands.
Example custom format without additional flags:
type CSVFormat struct{}
func (f *CSVFormat) Name() string { return "csv" }
func (f *CSVFormat) Format(ctx context.Context, cmd *cli.Command, w io.Writer, msg proto.Message) error {
// Format as CSV...
return nil
}
Example custom format with additional flags (implements FlagConfiguredOutputFormat):
type CSVFormat struct{}
func (f *CSVFormat) Name() string { return "csv" }
func (f *CSVFormat) Flags() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{Name: "delimiter", Value: ",", Usage: "CSV delimiter"},
}
}
func (f *CSVFormat) Format(ctx context.Context, cmd *cli.Command, w io.Writer, msg proto.Message) error {
delimiter := cmd.String("delimiter")
// Format as CSV...
return nil
}
The Flags() method is optional - implement it only if your format needs custom flags. The generated CLI code checks for the FlagConfiguredOutputFormat interface at runtime.
Template-Based Formats ¶
For simpler cases, use TemplateFormat to create formats using Go text templates:
templates := map[string]string{
"example.UserResponse": `User: {{.user.name}} ({{.user.email}})`,
"example.CreateUserRequest": `Creating: {{.name}}`,
}
format, err := protocli.TemplateFormat("user-compact", templates)
Templates support:
- Field access: {{.fieldName}}
- Conditionals: {{if .field}}...{{end}}
- Loops: {{range .list}}...{{end}}
- Custom functions via template.FuncMap
- Nested fields: {{.user.address.city}}
Message types are identified by fully qualified name (e.g., "example.UserResponse").
Example (TemplateFormat) ¶
Example_templateFormat demonstrates template format usage
package main
import (
protocli "github.com/drewfead/proto-cli"
)
func main() {
// Define templates for message types
templates := map[string]string{
"example.UserResponse": `{{$f := protoFields .}}User: {{$f.user.name}} (ID: {{$f.user.id}})
Email: {{$f.user.email}}
Status: {{if $f.user.verified}}Verified{{else}}Unverified{{end}}`,
}
// Create the format
format, err := protocli.TemplateFormat("user-detail", templates)
if err != nil {
panic(err)
}
// Use in CLI
_ = format // protocli.WithOutputFormats(format)
}
Output:
Index ¶
- Variables
- func CallFactory(factory any, config proto.Message) (any, error)
- func DefaultConfigPaths(rootCommandName string) []string
- func DefaultTemplateFunctions() template.FuncMap
- func InvokeTUI(ctx context.Context, cmd *cli.Command, opts ...TUIRunOption) error
- func NewConfigMessage(configType proto.Message) proto.Message
- func NewDaemonizeCommand(_ context.Context, services []*ServiceCLI, _ ServiceConfig) *cli.Command
- func ReadInputFile(filePath, formatName string, formats []InputFormat, msg proto.Message) error
- func RootCommand(appName string, opts ...RootOption) (*cli.Command, error)
- type CLIService
- type ConfigDebugInfo
- type ConfigLoader
- type ConfigLoaderOption
- type ConfigMode
- type DaemonReadyHook
- type DaemonShutdownHook
- type DaemonStartupHook
- type FlagConfiguredOutputFormat
- type FlagContainer
- type FlagDeserializer
- type HelpCustomization
- type InputFormat
- type LoggingConfigCallback
- type OutputFormat
- type RootConfig
- type RootOnlyOption
- func ConfigureLogging(configFunc LoggingConfigCallback) RootOnlyOption
- func IgnoreLocalOnly() RootOnlyOption
- func OnDaemonReady(hook DaemonReadyHook) RootOnlyOption
- func OnDaemonShutdown(hook DaemonShutdownHook) RootOnlyOption
- func OnDaemonStartup(hook DaemonStartupHook) RootOnlyOption
- func Service(service *ServiceCLI, opts ...ServiceRegistrationOption) RootOnlyOption
- func WithAuth(provider cliauth.LoginProvider, opts ...cliauth.Option) RootOnlyOption
- func WithConfigFactory(serviceName string, factory any) RootOnlyOption
- func WithConfigFile(path string) RootOnlyOption
- func WithConfigManagementCommands(configMsg proto.Message, appName string, serviceName string) RootOnlyOption
- func WithDefaultVerbosity(level slog.Level) RootOnlyOption
- func WithEnvPrefix(prefix string) RootOnlyOption
- func WithGRPCServerOptions(opts ...grpc.ServerOption) RootOnlyOption
- func WithGlobalConfigPath(path string) RootOnlyOption
- func WithGracefulShutdownTimeout(timeout time.Duration) RootOnlyOption
- func WithHelpCustomization(custom *HelpCustomization) RootOnlyOption
- func WithInteractive(provider TUIProvider) RootOnlyOption
- func WithLocalConfigPath(path string) RootOnlyOption
- func WithRootCommandHelpTemplate(template string) RootOnlyOption
- func WithStreamInterceptor(interceptor grpc.StreamServerInterceptor) RootOnlyOption
- func WithTranscoding(httpPort int) RootOnlyOption
- func WithUnaryInterceptor(interceptor grpc.UnaryServerInterceptor) RootOnlyOption
- type RootOption
- type ServiceCLI
- type ServiceConfig
- type ServiceOnlyOption
- type ServiceOption
- type ServiceRegistrationOption
- type SharedOption
- type SlogConfigurationContext
- type TUIEnumValue
- type TUIFieldDescriptor
- type TUIFieldKind
- type TUILaunchFn
- type TUIMethod
- type TUIMethodDescriptor
- type TUIProvider
- type TUIResponseDescriptor
- type TUIRunConfig
- type TUIRunOption
- type TUIService
- type TUIServiceDescriptor
- type TemplateFunctionRegistry
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrUnexpectedFieldValueType = errors.New("unexpected field value type") ErrOverflow = errors.New("overflow") )
var ( ErrWrongConfigType = errors.New("wrong config type") ErrAmbiguousCommandInvocation = errors.New("more than one action registered for the same command") )
var ErrNoTemplate = errors.New("no template registered for message type")
ErrNoTemplate is returned when no template is registered for a message type.
var ErrUnknownField = errors.New("unknown field")
Functions ¶
func CallFactory ¶
CallFactory calls a factory function with a config message using reflection. Returns the service implementation.
func DefaultConfigPaths ¶
DefaultConfigPaths returns default paths for config files.
func DefaultTemplateFunctions ¶
DefaultTemplateFunctions returns the default set of template functions. These functions are available in all templates unless overridden.
func InvokeTUI ¶
InvokeTUI triggers the interactive TUI from a generated service or method command's Before hook. It retrieves the launch function registered by RootCommand and calls it with the given options. Returns nil without error if no TUI provider is registered.
func NewConfigMessage ¶
NewConfigMessage creates a new config message instance using the proto registry. The configType should be a pointer to the config message type (e.g., &UserServiceConfig{}).
func NewDaemonizeCommand ¶
func NewDaemonizeCommand(_ context.Context, services []*ServiceCLI, _ ServiceConfig) *cli.Command
NewDaemonizeCommand creates a daemonize command for the given services. This is useful for single-service CLIs using the flat command structure.
func ReadInputFile ¶
func ReadInputFile(filePath, formatName string, formats []InputFormat, msg proto.Message) error
ReadInputFile reads the file at filePath and unmarshals its contents into msg.
Format selection waterfall:
- If formatName is non-empty, find the format by name. Error if not found.
- Match filepath.Ext(filePath) against each format's Extensions(). Use first match.
- Try all formats in order, use the first one that succeeds. If all fail, return a combined error.
func RootCommand ¶
func RootCommand(appName string, opts ...RootOption) (*cli.Command, error)
RootCommand creates a root CLI command with the given app name and options. Returns an error if there are naming collisions between hoisted service commands.
Types ¶
type CLIService ¶
CLIService is the interface for services registered in the root CLI command.
type ConfigDebugInfo ¶
type ConfigDebugInfo struct {
PathsChecked []string // All paths that were checked
FilesLoaded []string // Paths that were successfully loaded
FilesFailed map[string]string // Paths that failed with error message
EnvVarsApplied map[string]string // Env vars that were applied (name -> value)
FlagsApplied map[string]string // CLI flags that were applied (name -> value)
FinalConfig any // Final merged config (for display)
}
ConfigDebugInfo tracks config loading for debugging.
type ConfigLoader ¶
type ConfigLoader struct {
// contains filtered or unexported fields
}
ConfigLoader loads configuration with precedence: CLI flags > env vars > files.
func NewConfigLoader ¶
func NewConfigLoader(mode ConfigMode, opts ...ConfigLoaderOption) *ConfigLoader
NewConfigLoader creates a new config loader with options.
func (*ConfigLoader) DebugInfo ¶
func (l *ConfigLoader) DebugInfo() *ConfigDebugInfo
DebugInfo returns the config debug information (only populated if debug mode is enabled).
func (*ConfigLoader) LoadServiceConfig ¶
func (l *ConfigLoader) LoadServiceConfig( cmd *cli.Command, serviceName string, target proto.Message, ) error
LoadServiceConfig loads config for a specific service.
serviceName: lowercase service name (e.g., "userservice") target: pointer to config message instance
type ConfigLoaderOption ¶
type ConfigLoaderOption func(*ConfigLoader)
ConfigLoaderOption is a functional option for configuring a ConfigLoader.
func DebugMode ¶
func DebugMode(enabled bool) ConfigLoaderOption
DebugMode enables config loading debug information.
func EnvPrefix ¶
func EnvPrefix(prefix string) ConfigLoaderOption
EnvPrefix sets the environment variable prefix for config overrides.
func FileConfig ¶
func FileConfig(paths ...string) ConfigLoaderOption
FileConfig adds config file paths to load.
func ReaderConfig ¶
func ReaderConfig(readers ...io.Reader) ConfigLoaderOption
ReaderConfig adds io.Readers to load config from (for testing).
type ConfigMode ¶
type ConfigMode int
ConfigMode determines which config sources are used.
const ( // SingleCommandMode uses files + env + flags (all sources). SingleCommandMode ConfigMode = iota // DaemonMode uses files + env only (no CLI flag overrides). DaemonMode )
type DaemonReadyHook ¶
DaemonReadyHook is called after the gRPC server is listening and ready to accept connections Errors must be handled within the hook (no error return).
type DaemonShutdownHook ¶
DaemonShutdownHook is called during graceful shutdown after stop accepting new connections The context will be cancelled when the graceful shutdown timeout expires Errors must be handled within the hook (no error return).
type DaemonStartupHook ¶
DaemonStartupHook is called before the gRPC server starts listening Receives the gRPC server instance and gateway mux (if transcoding is enabled) Returning an error prevents the daemon from starting.
type FlagConfiguredOutputFormat ¶
type FlagConfiguredOutputFormat interface {
OutputFormat
// Flags returns additional flags this format needs (e.g., --pretty for JSON).
Flags() []cli.Flag
}
FlagConfiguredOutputFormat is an optional interface for formats that need custom flags.
type FlagContainer ¶
type FlagContainer interface {
// Primary flag accessors (use the encapsulated flag name)
String() string
Int() int
Int64() int64
Uint() uint
Uint64() uint64
Bool() bool
Float() float64
StringSlice() []string
IsSet() bool
// Named flag accessors (for accessing other flags)
StringNamed(flagName string) string
IntNamed(flagName string) int
Int64Named(flagName string) int64
BoolNamed(flagName string) bool
FloatNamed(flagName string) float64
StringSliceNamed(flagName string) []string
IsSetNamed(flagName string) bool
// FlagName returns the primary flag name for this container
FlagName() string
}
FlagContainer provides type-safe access to flag values for a specific flag It encapsulates the CLI command and flag name, exposing convenient accessors This abstraction allows deserializers to be reusable across different flag names
For deserializers that need to access multiple flags (e.g., top-level request deserializers), use the *Named() methods to read other flags by name.
func NewFlagContainer ¶
func NewFlagContainer(cmd *cli.Command, flagName string) FlagContainer
NewFlagContainer creates a new FlagContainer for the given command and flag name.
func NewStringValueFlagContainer ¶
func NewStringValueFlagContainer(value string, cmd *cli.Command) FlagContainer
NewStringValueFlagContainer creates a FlagContainer whose primary String() accessor returns value directly. Named accessors delegate to cmd. Used to pass individual repeated-message elements to a FlagDeserializer.
type FlagDeserializer ¶
FlagDeserializer builds a proto message from CLI flags This allows users to implement custom logic for constructing complex messages from simple CLI flags. The FlagContainer provides type-safe access to the flag value without requiring knowledge of the flag name, making deserializers reusable.
Example of a reusable timestamp deserializer:
func(ctx context.Context, flags FlagContainer) (proto.Message, error) {
timeStr := flags.String() // No need to know the flag name!
t, err := time.Parse(time.RFC3339, timeStr)
if err != nil {
return nil, err
}
return timestamppb.New(t), nil
}
type HelpCustomization ¶
type HelpCustomization struct {
// RootCommandHelpTemplate overrides the default root command help template
RootCommandHelpTemplate string
// CommandHelpTemplate overrides the default command help template
CommandHelpTemplate string
// SubcommandHelpTemplate overrides the default subcommand help template
SubcommandHelpTemplate string
}
HelpCustomization holds options for customizing help text display. Based on urfave/cli v3 help customization capabilities.
type InputFormat ¶
type InputFormat interface {
// Name returns the format identifier (e.g., "json", "yaml")
Name() string
// Extensions returns file extensions this format handles (e.g., [".json"] or [".yaml", ".yml"])
Extensions() []string
// Unmarshal parses the given data into the proto message.
Unmarshal(data []byte, msg proto.Message) error
}
InputFormat defines how to unmarshal file contents into a proto message.
func DefaultInputFormats ¶
func DefaultInputFormats() []InputFormat
DefaultInputFormats returns the default set of input formats: protojson and YAML.
func ProtoJSONInput ¶
func ProtoJSONInput() InputFormat
ProtoJSONInput returns an InputFormat that reads protojson-encoded files.
func YAMLInput ¶
func YAMLInput() InputFormat
YAMLInput returns an InputFormat that reads YAML files by converting to JSON then using protojson.Unmarshal.
type LoggingConfigCallback ¶
type LoggingConfigCallback func(ctx context.Context, config SlogConfigurationContext) *slog.Logger
LoggingConfigCallback is a function that configures the slog logger. It receives a context with configuration details and returns a configured logger.
type OutputFormat ¶
type OutputFormat interface {
// Name returns the format identifier (e.g., "json", "text")
Name() string
// Format writes the formatted proto message to the writer
Format(ctx context.Context, cmd *cli.Command, w io.Writer, msg proto.Message) error
}
OutputFormat defines how to format proto messages for output.
func JSON ¶
func JSON() OutputFormat
JSON returns a new JSON output format with optional --pretty flag.
func MustTemplateFormat ¶
func MustTemplateFormat(name string, templates map[string]string, funcMaps ...template.FuncMap) OutputFormat
MustTemplateFormat is like TemplateFormat but panics on error. Useful for package-level initialization where template errors should be caught at startup.
Example:
var userFormat = protocli.MustTemplateFormat("user", map[string]string{
"example.UserResponse": `{{.user.name}} <{{.user.email}}>`,
})
func TemplateFormat ¶
func TemplateFormat(name string, templates map[string]string, funcMaps ...template.FuncMap) (OutputFormat, error)
TemplateFormat creates an output format that renders proto messages using Go text templates.
Templates are specified as a map from fully qualified message type name to template string. The message type name format is "package.MessageName" (e.g., "example.UserResponse").
Templates receive the actual proto message directly. Custom template functions receive actual proto types (e.g., *timestamppb.Timestamp), not JSON strings or maps.
Default template functions available:
- protoField: access message fields by JSON name, preserving proto types
- protoJSON: convert proto message to JSON string
- protoJSONIndent: convert proto message to indented JSON string
- protoFields: convert proto message to map for dot-chain field access
Field access patterns:
- Use protoFields for easy dot-chain access (proto types become JSON types): {{$fields := protoFields .}}{{$fields.user.name}}
- Use protoField helper to preserve proto types: {{protoField . "fieldName"}}
- Register custom accessor functions for your message types (recommended for complex templates)
Optional function maps can be provided to add custom template functions. Functions are merged in order: defaults, global registry, then provided funcMaps.
Example with protoFields (simplest):
templates := map[string]string{
"example.UserResponse": `{{$f := protoFields .}}User: {{$f.user.name}}
Email: {{$f.user.email}}
ID: {{$f.user.id}}`,
}
format := protocli.TemplateFormat("user-table", templates)
Example with custom accessor (best for proto types):
// Register type-specific accessor functions globally
protocli.TemplateFunctions().Register("user", func(resp *simple.UserResponse) *simple.User {
return resp.GetUser()
})
protocli.TemplateFunctions().Register("formatTime", func(ts *timestamppb.Timestamp) string {
if ts == nil || !ts.IsValid() {
return "N/A"
}
return ts.AsTime().Format("2006-01-02")
})
templates := map[string]string{
"example.UserResponse": `User: {{(user .).GetName}}
Created: {{formatTime (user .).GetCreatedAt}}`,
}
format := protocli.TemplateFormat("user-table", templates)
type RootConfig ¶
type RootConfig interface {
CLIServices() []CLIService
TUIServices() []TUIService
GRPCServerOptions() []grpc.ServerOption
EnableTranscoding() bool
TranscodingPort() int
ConfigPaths() []string
EnvPrefix() string
ServiceFactory(serviceName string) (any, bool)
GracefulShutdownTimeout() time.Duration
DaemonStartupHooks() []DaemonStartupHook
DaemonReadyHooks() []DaemonReadyHook
DaemonShutdownHooks() []DaemonShutdownHook
LoggingConfig() LoggingConfigCallback
DefaultVerbosity() string
HelpCustomization() *HelpCustomization
IgnoreLocalOnly() bool
LoginProvider() cliauth.LoginProvider
AuthOptions() []cliauth.Option
TUIProvider() TUIProvider
}
RootConfig is the configuration returned by ApplyRootOptions. Used by RootCommand.
func ApplyRootOptions ¶
func ApplyRootOptions(opts ...RootOption) RootConfig
ApplyRootOptions applies functional options and returns configured root settings.
type RootOnlyOption ¶
type RootOnlyOption func(*rootCommandOptions)
RootOnlyOption is a concrete option type that only works with root level. It implements only the RootOption interface.
func ConfigureLogging ¶
func ConfigureLogging(configFunc LoggingConfigCallback) RootOnlyOption
ConfigureLogging provides a custom slog logger configuration function. If not specified, the framework uses sensible defaults:
- Single commands: human-friendly colored logs to stderr (via clilog.HumanFriendlySlogHandler)
- Daemon mode: JSON-formatted logs to stdout
The function receives a context and a SlogConfigurationContext providing:
- IsDaemon(): true for daemon mode, false for single commands
- Level(): the configured log level from the --verbosity flag
IMPORTANT: Your custom logger factory MUST respect config.Level() to honor the --verbosity flag.
Type-safe: only works with RootOptions.
Example - Custom handler that respects verbosity:
protocli.ConfigureLogging(func(ctx context.Context, config protocli.SlogConfigurationContext) *slog.Logger {
handler := clilog.HumanFriendlySlogHandler(os.Stderr, &slog.HandlerOptions{
Level: config.Level(), // IMPORTANT: Use config.Level() to respect --verbosity flag
})
return slog.New(handler)
})
Example - Different loggers for daemon vs single-command mode:
protocli.ConfigureLogging(func(ctx context.Context, config protocli.SlogConfigurationContext) *slog.Logger {
if config.IsDaemon() {
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: config.Level()})
return slog.New(handler)
}
handler := clilog.HumanFriendlySlogHandler(os.Stderr, &slog.HandlerOptions{Level: config.Level()})
return slog.New(handler)
})
Example - Use the convenience function for always human-friendly logging:
protocli.ConfigureLogging(clilog.AlwaysHumanFriendly())
func IgnoreLocalOnly ¶
func IgnoreLocalOnly() RootOnlyOption
IgnoreLocalOnly disables automatic local-only method rejection in daemon mode. When set, the daemon will not mount interceptors that reject calls to local-only methods. Type-safe: only works with RootOptions.
func OnDaemonReady ¶
func OnDaemonReady(hook DaemonReadyHook) RootOnlyOption
OnDaemonReady registers a hook that runs after the gRPC server is listening and ready. Multiple hooks can be registered and will run in registration order. The hook cannot return errors - errors must be handled within the hook. Type-safe: only works with RootOptions.
func OnDaemonShutdown ¶
func OnDaemonShutdown(hook DaemonShutdownHook) RootOnlyOption
OnDaemonShutdown registers a hook that runs during graceful shutdown. Multiple hooks can be registered and will run in REVERSE registration order. The hook runs after stop accepting new connections but before forcing shutdown. The context will be cancelled when the graceful shutdown timeout expires. The hook cannot return errors - errors must be handled within the hook. Type-safe: only works with RootOptions.
func OnDaemonStartup ¶
func OnDaemonStartup(hook DaemonStartupHook) RootOnlyOption
OnDaemonStartup registers a hook that runs before the gRPC server starts listening. Multiple hooks can be registered and will run in registration order. The hook receives the gRPC server instance and gateway mux (may be nil if transcoding disabled). Returning an error prevents the daemon from starting. Type-safe: only works with RootOptions.
func Service ¶
func Service(service *ServiceCLI, opts ...ServiceRegistrationOption) RootOnlyOption
Service registers a service CLI (root level only). Accepts optional ServiceRegistrationOptions to customize registration (e.g., Hoisted()). Type-safe: only works with RootOptions.
func WithAuth ¶
func WithAuth(provider cliauth.LoginProvider, opts ...cliauth.Option) RootOnlyOption
WithAuth enables the auth command suite (login, logout, status). The provider implements LoginProvider and optionally InteractiveLoginProvider, LogoutProvider, and StatusProvider to control which subcommands are available. Use cliauth.WithStore and cliauth.WithDecorator options to customize behavior.
func WithConfigFactory ¶
func WithConfigFactory(serviceName string, factory any) RootOnlyOption
WithConfigFactory registers a factory function for a service. The factory function takes a config message and returns a service implementation. Example: WithConfigFactory("userservice", func(cfg *UserServiceConfig) UserServiceServer { ... }). Type-safe: only works with RootOptions.
func WithConfigFile ¶
func WithConfigFile(path string) RootOnlyOption
WithConfigFile adds a config file path to load. Can be called multiple times to specify multiple config files (deep merge). Type-safe: only works with RootOptions.
func WithConfigManagementCommands ¶
func WithConfigManagementCommands(configMsg proto.Message, appName string, serviceName string) RootOnlyOption
WithConfigManagementCommands enables the config command suite (init, set, get, list). This adds 'config' subcommands to the root CLI for managing configuration files. Config files are YAML-based and validated against the service's config proto schema.
By default:
- Global config: ~/.config/appname/config.yaml
- Local config: ./.appname/config.yaml
Use WithGlobalConfigPath and WithLocalConfigPath to customize locations.
Example:
protocli.WithConfigManagementCommands(&simple.UserServiceConfig{}, "myapp", "userservice")
func WithDefaultVerbosity ¶
func WithDefaultVerbosity(level slog.Level) RootOnlyOption
WithDefaultVerbosity sets the default verbosity level for the --verbosity flag. Accepts standard slog.Level values: slog.LevelDebug, slog.LevelInfo, slog.LevelWarn, slog.LevelError. Note: In slog, higher numeric values = less verbose logging:
- slog.LevelDebug (-4) = most verbose
- slog.LevelInfo (0) = normal
- slog.LevelWarn (4) = warnings and errors only
- slog.LevelError (8) = errors only
- slog.Level(1000) or higher = effectively disables logging
Default is slog.LevelInfo if not specified. Users can still override via the --verbosity flag or -v shorthand. Type-safe: only works with RootOptions.
Example:
protocli.WithDefaultVerbosity(slog.LevelDebug) // Most verbose (debug and above) protocli.WithDefaultVerbosity(slog.LevelWarn) // Less verbose (warn and error only) protocli.WithDefaultVerbosity(slog.Level(1000)) // Disable logging
func WithEnvPrefix ¶
func WithEnvPrefix(prefix string) RootOnlyOption
WithEnvPrefix sets the environment variable prefix for config overrides. Example: WithEnvPrefix("USERCLI") enables USERCLI_DB_URL env var. Type-safe: only works with RootOptions.
func WithGRPCServerOptions ¶
func WithGRPCServerOptions(opts ...grpc.ServerOption) RootOnlyOption
WithGRPCServerOptions adds gRPC server options (e.g., for interceptors). Type-safe: only works with RootOptions.
func WithGlobalConfigPath ¶
func WithGlobalConfigPath(path string) RootOnlyOption
WithGlobalConfigPath sets a custom global config file path. This overrides the default ~/.config/appname/config.yaml location. Type-safe: only works with RootOptions.
Example:
protocli.WithGlobalConfigPath("/etc/myapp/config.yaml")
func WithGracefulShutdownTimeout ¶
func WithGracefulShutdownTimeout(timeout time.Duration) RootOnlyOption
WithGracefulShutdownTimeout sets the timeout for graceful daemon shutdown. Default is 15 seconds if not specified. During graceful shutdown, the daemon will wait for in-flight requests to complete. before forcefully terminating after this timeout. Type-safe: only works with RootOptions.
func WithHelpCustomization ¶
func WithHelpCustomization(custom *HelpCustomization) RootOnlyOption
WithHelpCustomization sets custom help templates and printer functions. This allows full customization of help text display following urfave/cli v3 patterns.
Example:
protocli.WithHelpCustomization(&protocli.HelpCustomization{
RootCommandHelpTemplate: myCustomTemplate,
CustomizeRootCommand: func(cmd *cli.Command) {
cmd.Usage = "My custom usage text"
},
})
func WithInteractive ¶
func WithInteractive(provider TUIProvider) RootOnlyOption
WithInteractive registers an interactive TUI provider and enables the --interactive flag. When --interactive is passed at runtime, the provider's Run method is called with all services that have TUIDescriptor set (i.e., tui=true in their proto annotation). Type-safe: only works with RootOptions.
Example:
protocli.WithInteractive(tui.New())
func WithLocalConfigPath ¶
func WithLocalConfigPath(path string) RootOnlyOption
WithLocalConfigPath sets a custom local config file path. This overrides the default ./.appname/config.yaml location. Type-safe: only works with RootOptions.
Example:
protocli.WithLocalConfigPath("./config.yaml")
func WithRootCommandHelpTemplate ¶
func WithRootCommandHelpTemplate(template string) RootOnlyOption
WithRootCommandHelpTemplate sets a custom template for root command help. This is a convenience function for the most common help customization.
Example:
protocli.WithRootCommandHelpTemplate(`
NAME:
{{.Name}} - {{.Usage}}
USAGE:
{{.HelpName}} {{if .VisibleFlags}}[options]{{end}} command [command options]
VERSION:
{{.Version}}
`)
func WithStreamInterceptor ¶
func WithStreamInterceptor(interceptor grpc.StreamServerInterceptor) RootOnlyOption
WithStreamInterceptor adds a stream interceptor to the gRPC server. Type-safe: only works with RootOptions.
func WithTranscoding ¶
func WithTranscoding(httpPort int) RootOnlyOption
WithTranscoding enables gRPC-Gateway transcoding (HTTP/JSON to gRPC). This allows clients to call gRPC services via REST/JSON on the specified port. Type-safe: only works with RootOptions.
func WithUnaryInterceptor ¶
func WithUnaryInterceptor(interceptor grpc.UnaryServerInterceptor) RootOnlyOption
WithUnaryInterceptor adds a unary interceptor to the gRPC server. Type-safe: only works with RootOptions.
type RootOption ¶
type RootOption interface {
// contains filtered or unexported methods
}
RootOption interface for root-level configuration.
type ServiceCLI ¶
type ServiceCLI struct {
Command *cli.Command
ServiceName string // Service name (e.g., "userservice")
ConfigMessageType string // Config message type name (empty if no config)
ConfigPrototype proto.Message // Prototype config message instance (for cloning)
FactoryOrImpl any // Factory function or direct service implementation
RegisterFunc func(*grpc.Server, any) // Register service with gRPC server (takes impl)
GatewayRegisterFunc func(ctx context.Context, mux any) error // mux is *runtime.ServeMux from grpc-gateway
LocalOnlyMethods []string // Full gRPC method paths that are local-only (e.g., "/pkg.Svc/Method")
TUIDescriptor *TUIServiceDescriptor // nil if tui=false on service annotation
}
ServiceCLI represents a service CLI with its command and gRPC registration function.
func (*ServiceCLI) CLICommand ¶
func (s *ServiceCLI) CLICommand() *cli.Command
CLICommand returns the CLI command for this service, satisfying the CLIService interface.
func (*ServiceCLI) CLIName ¶
func (s *ServiceCLI) CLIName() string
CLIName returns the service name, satisfying the CLIService interface.
type ServiceConfig ¶
type ServiceConfig interface {
BeforeCommandHooks() []func(context.Context, *cli.Command) error
AfterCommandHooks() []func(context.Context, *cli.Command) error
OutputFormats() []OutputFormat
InputFormats() []InputFormat
FlagDeserializer(messageName string) (FlagDeserializer, bool)
}
ServiceConfig is the configuration returned by ApplyServiceOptions. Used by generated service command code.
func ApplyServiceOptions ¶
func ApplyServiceOptions(opts ...ServiceOption) ServiceConfig
ApplyServiceOptions applies functional options and returns configured service settings.
type ServiceOnlyOption ¶
type ServiceOnlyOption func(*serviceCommandOptions)
ServiceOnlyOption is a concrete option type that only works with service level. It implements only the ServiceOption interface.
func WithFlagDeserializer ¶
func WithFlagDeserializer(messageName string, deserializer FlagDeserializer) ServiceOnlyOption
WithFlagDeserializer registers a custom deserializer for a specific message type This allows users to implement custom logic for constructing complex proto messages from CLI flags, enabling advanced transformations and validation.
Example:
WithFlagDeserializer("GetUserRequest", func(ctx context.Context, cmd *cli.Command) (proto.Message, error) {
// Custom logic to build GetUserRequest from flags
userId := cmd.String("user-id")
return &pb.GetUserRequest{
UserId: userId,
IncludeDetails: cmd.Bool("details"),
}, nil
})
Type-safe: only works with ServiceOptions.
func WithInputFormat ¶
func WithInputFormat(format InputFormat) ServiceOnlyOption
WithInputFormat registers a custom input format for file-based request input. Multiple formats can be registered. Built-in formats (protojson, YAML) are always available unless overridden by a format with the same name. Type-safe: only works with ServiceOptions.
type ServiceOption ¶
type ServiceOption interface {
// contains filtered or unexported methods
}
ServiceOption interface for service-level configuration.
type ServiceRegistrationOption ¶
type ServiceRegistrationOption func(*serviceRegistration)
ServiceRegistrationOption configures how a service is registered in the root command.
func Hoisted ¶
func Hoisted() ServiceRegistrationOption
Hoisted returns an option that hoists service RPC commands to the root level. When hoisted, RPC commands appear as siblings of the daemonize command instead of nested under the service name. Multiple services can be hoisted - naming collisions will cause a runtime error. Example: protocli.WithService(serviceCLI, protocli.Hoisted())
func LocalOnly ¶
func LocalOnly(methods ...string) ServiceRegistrationOption
LocalOnly returns an option that marks additional methods as local-only in code. Methods should be specified as full gRPC method paths (e.g., "/pkg.Svc/Method"). Use generated constants from *_grpc.pb.go for type safety. Example: protocli.LocalOnly(simple.UserService_GetUser_FullMethodName)
func WithTUI ¶
func WithTUI() ServiceRegistrationOption
WithTUI explicitly includes this service in the interactive TUI, even if its proto annotation does not opt it in. Has no effect if the service has no TUIDescriptor (i.e. it was generated without tui=true and none was set in code).
func WithoutTUI ¶
func WithoutTUI() ServiceRegistrationOption
WithoutTUI explicitly excludes this service from the interactive TUI, even if its proto annotation opts it in via tui=true.
type SharedOption ¶
type SharedOption func(baseOptions)
SharedOption is a concrete option type that works with both service and root levels. It implements both ServiceOption and RootOption interfaces.
func AfterCommand ¶
AfterCommand registers a hook that runs after each command execution. Works with both ServiceCommand and RootCommand. Multiple hooks can be registered and will run in REVERSE registration order. This allows cleanup to happen in the opposite order of setup (LIFO pattern). Works with both ServiceCommand and RootCommand.
func BeforeCommand ¶
BeforeCommand registers a hook that runs before each command execution. Multiple hooks can be registered and will run in registration order. Works with both ServiceCommand and RootCommand.
func WithOutputFormats ¶
func WithOutputFormats(formats ...OutputFormat) SharedOption
WithOutputFormats registers output formatters for response rendering. Works with both ServiceCommand and RootCommand.
type SlogConfigurationContext ¶
type SlogConfigurationContext interface {
// IsDaemon returns true if the logger is being configured for daemon mode.
IsDaemon() bool
// Level returns the configured log level from the --verbosity flag.
Level() slog.Level
}
SlogConfigurationContext provides context information for slog configuration.
type TUIEnumValue ¶
TUIEnumValue is one valid value for an enum field.
type TUIFieldDescriptor ¶
type TUIFieldDescriptor struct {
Name string
Label string // from tui_label annotation or flag name
Usage string // from flag usage
Description string // from flag description
DefaultValue string // from default_value annotation; pre-populates TUI form fields
Required bool
Hidden bool // from tui_hidden annotation
Kind TUIFieldKind
MessageFullName string // proto full name for message fields (e.g. "google.protobuf.Timestamp")
EnumValues []TUIEnumValue // for enum fields
ElementKind TUIFieldKind // for repeated fields
Fields []TUIFieldDescriptor // for message fields (nested)
// Setter parses a string value and sets it on the request message.
Setter func(proto.Message, string) error
// Appender appends a parsed string element to a repeated field.
Appender func(proto.Message, string) error
}
TUIFieldDescriptor describes one input field in a TUI form.
type TUIFieldKind ¶
type TUIFieldKind int
TUIFieldKind describes the input semantics of a TUI form field.
const ( TUIFieldKindString TUIFieldKind = iota TUIFieldKindInt TUIFieldKindFloat TUIFieldKindBool TUIFieldKindEnum TUIFieldKindRepeated TUIFieldKindMessage )
type TUILaunchFn ¶
TUILaunchFn is the function stored in the root command's Metadata by RootCommand when a TUI provider is configured. Generated Before hooks call InvokeTUI which retrieves and calls this function.
type TUIMethod ¶
type TUIMethod interface {
TUIName() string
TUIDisplayName() string
TUIDescription() string
TUIHidden() bool
TUIIsStreaming() bool
TUIResponseDescriptor() TUIResponseDescriptor
TUINewRequest() proto.Message
TUIInputFields() []TUIFieldDescriptor
TUIInvoke(ctx context.Context, cmd *cli.Command, req proto.Message) (proto.Message, error)
TUIInvokeStream(ctx context.Context, cmd *cli.Command, req proto.Message, recv func(proto.Message) error) error
}
TUIMethod is the interface for RPC methods visible in the interactive TUI. Use this type in code that consumes descriptors (e.g. contrib/tui) so that the concrete *TUIMethodDescriptor can evolve independently.
type TUIMethodDescriptor ¶
type TUIMethodDescriptor struct {
Name string // CLI command name
DisplayName string
Description string
Hidden bool // from tui_hidden annotation
IsStreaming bool
// ResponseDescriptor describes the response type for this method.
// Passed to ResponseViewFactory so factories can dispatch by type.
ResponseDescriptor TUIResponseDescriptor
// NewRequest returns a fresh zero-value request message.
NewRequest func() proto.Message
// InputFields describes all form inputs for building the request.
InputFields []TUIFieldDescriptor
// Invoke calls the unary method with the given request.
// cmd is the root command; the closure uses it to load config (same as
// the generated action code: reads --config and --env-prefix flags,
// creates a ConfigLoader, calls CallFactory if the service has a config).
Invoke func(ctx context.Context, cmd *cli.Command, req proto.Message) (proto.Message, error)
// InvokeStream calls a server-streaming method.
InvokeStream func(ctx context.Context, cmd *cli.Command, req proto.Message, recv func(proto.Message) error) error
}
TUIMethodDescriptor describes one RPC method for TUI interaction.
type TUIProvider ¶
type TUIProvider interface {
Run(ctx context.Context, cmd *cli.Command, services []TUIService, opts ...TUIRunOption) error
}
TUIProvider is implemented by contrib/tui (or user code) to provide an interactive TUI. Receives descriptors for all tui-enabled services. cmd is the root command, forwarded to each Invoke call for config loading.
type TUIResponseDescriptor ¶
type TUIResponseDescriptor struct {
// MethodName is the CLI command name of the method that produced the response.
MethodName string
// MessageFullName is the proto full name of the response message
// (e.g. "mypackage.MyResponse"). Populated by the generator.
MessageFullName string
}
TUIResponseDescriptor describes the response type returned by an RPC method. It is passed to ResponseViewFactory so factories can dispatch to different presentations based on the response message type or the originating method.
type TUIRunConfig ¶
type TUIRunConfig struct {
// StartServiceName is the CLI name of the service to pre-select (e.g. "farewell").
// Empty means start at the first service.
StartServiceName string
// StartMethodName is the CLI name of the method to open the form for (e.g. "leave-note").
// Only meaningful when StartServiceName is also set. Empty means show the method list.
StartMethodName string
// FieldValues pre-populates form fields keyed by field flag-name (e.g. "name").
// Only meaningful when StartMethodName is also set. Values override the proto-defined
// default for the matching field.
FieldValues map[string]string
}
TUIRunConfig holds the parsed options for a single TUIProvider.Run call. Providers read this to determine which service/method to open on launch.
type TUIRunOption ¶
type TUIRunOption func(*TUIRunConfig)
TUIRunOption customises a single TUIProvider.Run invocation.
func StartAtMethod ¶
func StartAtMethod(serviceCLIName, methodCLIName string) TUIRunOption
StartAtMethod opens the TUI directly on the request form for the named method. serviceCLIName is the service command name; methodCLIName is the method command name.
func StartAtService ¶
func StartAtService(cliName string) TUIRunOption
StartAtService opens the TUI with the named service's method list pre-selected. cliName is the CLI command name of the service (e.g. "farewell").
func WithPrefillFields ¶
func WithPrefillFields(fields map[string]string) TUIRunOption
WithPrefillFields pre-populates form fields when opening the TUI at a specific method. fields is a map from field flag-name (e.g. "name") to its string representation. Only meaningful when StartAtMethod is also used.
type TUIService ¶
type TUIService interface {
TUIName() string
TUIDisplayName() string
TUIDescription() string
TUIMethods() []TUIMethod
}
TUIService is the interface for services visible in the interactive TUI. Use this type in code that consumes descriptors (e.g. contrib/tui) so that the concrete *TUIServiceDescriptor can evolve independently.
type TUIServiceDescriptor ¶
type TUIServiceDescriptor struct {
Name string
DisplayName string
Description string
Methods []*TUIMethodDescriptor
}
TUIServiceDescriptor describes a service's TUI-navigable structure.
type TemplateFunctionRegistry ¶
type TemplateFunctionRegistry struct {
// contains filtered or unexported fields
}
TemplateFunctionRegistry manages custom template functions for use in template-based output formats. It provides a way to register custom functions that templates can use to format proto messages.
func NewTemplateFunctionRegistry ¶
func NewTemplateFunctionRegistry() *TemplateFunctionRegistry
NewTemplateFunctionRegistry creates a new registry with default template functions. Default functions include:
- protoField: access message fields by JSON name, preserving proto types
- protoJSON: converts a proto message to JSON string using protojson
- protoJSONIndent: converts a proto message to indented JSON string
- protoFields: converts a proto message to map for dot-chain field access
func TemplateFunctions ¶
func TemplateFunctions() *TemplateFunctionRegistry
TemplateFunctions returns the global template function registry. This can be used to register custom template functions globally.
Example:
protocli.TemplateFunctions().Register("formatDate", func(ts *timestamppb.Timestamp) string {
return ts.AsTime().Format("2006-01-02")
})
func (*TemplateFunctionRegistry) Functions ¶
func (r *TemplateFunctionRegistry) Functions() template.FuncMap
Functions returns the complete set of registered template functions. This includes both default functions and any user-registered functions.
func (*TemplateFunctionRegistry) Register ¶
func (r *TemplateFunctionRegistry) Register(name string, fn any)
Register adds or replaces a template function. If a function with the same name already exists, it will be replaced.
func (*TemplateFunctionRegistry) RegisterMap ¶
func (r *TemplateFunctionRegistry) RegisterMap(funcMap template.FuncMap)
RegisterMap adds multiple template functions at once. Existing functions with the same names will be replaced.
Directories
¶
| Path | Synopsis |
|---|---|
|
cli
|
|
|
cmd
|
|
|
proto-cli-gen
command
|
|
|
contrib
|
|
|
bundle
Package bundle provides convenience functions that combine contrib deserializers and formats into ready-to-use option sets for proto-cli service commands.
|
Package bundle provides convenience functions that combine contrib deserializers and formats into ready-to-use option sets for proto-cli service commands. |
|
deserializers
Package deserializers provides reusable FlagDeserializer factories for well-known protobuf types.
|
Package deserializers provides reusable FlagDeserializer factories for well-known protobuf types. |
|
formats
Package formats provides additional output formats for proto-cli.
|
Package formats provides additional output formats for proto-cli. |
|
oauth
Package oauth provides an out-of-the-box OAuth authentication provider for proto-cli.
|
Package oauth provides an out-of-the-box OAuth authentication provider for proto-cli. |
|
tui
Package tui provides a bubbletea-based interactive TUI for proto-cli.
|
Package tui provides a bubbletea-based interactive TUI for proto-cli. |
|
examples
|
|
|
simple
Package simple demonstrates basic proto-cli usage with unary gRPC methods.
|
Package simple demonstrates basic proto-cli usage with unary gRPC methods. |
|
simple/usercli
command
|
|
|
simple/usercli_flat
command
|
|
|
streaming
Package streaming demonstrates server streaming gRPC support in proto-cli.
|
Package streaming demonstrates server streaming gRPC support in proto-cli. |
|
streaming/streamcli
command
|
|
|
streaming/streamcli_flat
command
|
|
|
tui/tuicli
command
tuicli demonstrates the --interactive TUI mode for proto-cli.
|
tuicli demonstrates the --interactive TUI mode for proto-cli. |
|
internal
|
|
|
proto
|
|
|
cli/v1
Package cli provides Protocol Buffer definitions for CLI annotations.
|
Package cli provides Protocol Buffer definitions for CLI annotations. |