root

package
v0.39.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 18, 2026 License: MIT Imports: 40 Imported by: 0

README

Root Command

The entry point and orchestration layer for GTB CLI applications.

Key Responsibilities:

  • Persistent service initialization (Logging, Configuration)
  • Global flag management (--config, --debug, --ci)
  • Lifecycle hooks (PersistentPreRunE)
  • Automatic feature command registration

For detailed documentation on the root command and the application lifecycle, see the Built-in Commands Documentation.

Documentation

Overview

Package root provides the reusable root Cobra command constructor that wires configuration loading, logging setup, update checks, and feature-flagged subcommand registration (version, update, init, doctor, config, telemetry, changelog, man, MCP, docs).

The NewCmdRoot and NewCmdRootWithConfig functions build a root command whose PersistentPreRunE handles config merging (local files + embedded assets), log level/format configuration, and optional self-update prompting before any subcommand executes.

Index

Constants

This section is empty.

Variables

View Source
var ErrNoConfigFile = errors.NewSentinel("gtb.root.no_config_file", "no config file found")

ErrNoConfigFile reports that none of the candidate config files exist.

config v0.2.0 supplied this as ErrNoFilesFound. The Store does not: a missing file is an empty layer, not an error, because in a layered model there is nothing unusual about a layer being absent. GTB still needs the distinction — it is what gates auto-initialise — so the sentinel is owned here.

View Source
var ErrUpdateComplete = errorhandling.WithOutcome(
	errors.NewSentinel("gtb.update_complete", "update complete — restart required"),
	errorhandling.Outcome{
		Code:    0,
		Level:   slog.LevelWarn,
		Message: "update complete — please run the command again",
	},
)

ErrUpdateComplete is returned by PersistentPreRunE when a self-update has completed successfully.

It is terminal AND successful, which is why it carries an Outcome rather than a special case in Execute. Everything about how it should be reported — exit zero, say so at warn level, and say this instead of the error's own text — travels with the sentinel that means it, so Execute needs no branch and a downstream tool can declare its own terminal errors the same way. See spec 0002 D10 in the errorhandling wiki.

NewSentinel rather than New: a package-level New captures its stack at package initialisation, which points at runtime.doInit rather than anywhere the error was returned from.

Functions

func ConventionKey added in v0.17.0

func ConventionKey(flagName string) string

ConventionKey maps a flag name to a configuration key using the hyphen-to-dot convention: "server-port" → "server.port". It is exported so downstream tools can derive the same keys when constructing explicit WithBoundFlags maps.

func Execute

func Execute(rootCmd *setup.Command, props *p.Props, opts ...ExecuteOption)

Execute runs the root command with centralized error handling and a signal-aware execution context. SIGINT/SIGTERM cancel cmd.Context() so commands can unwind gracefully; a second signal force-exits immediately (kubectl/docker UX); a signal-terminated run exits 128+signum (130 for SIGINT, 143 for SIGTERM).

It silences Cobra's default error output and reports any error returned by the command tree through ErrorHandler.Fatal. Since errorhandling v0.2.0 the handler reports and returns an exit code rather than exiting itself, so THIS function owns process termination — which keeps the decision at the outermost frame, where a deferred cleanup can still run before it. The buffered telemetry flush runs on every path — success, error, and cancellation — before any exit fires.

func NewCmdRoot

func NewCmdRoot(props *p.Props, subcommands ...*setup.Command) *setup.Command

NewCmdRoot creates the root command with Props wiring and optional subcommands.

func NewCmdRootWithConfig

func NewCmdRootWithConfig(props *p.Props, configPaths []string, subcommands ...*setup.Command) *setup.Command

NewCmdRootWithConfig creates the root command for the CLI application. It accepts additional configuration file paths to be considered during initialization.

func NewCmdRootWithOptions added in v0.17.0

func NewCmdRootWithOptions(props *p.Props, opts ...RootOption) *setup.Command

Types

type ConfigLoadOptions

type ConfigLoadOptions struct {
	CfgPaths    []string
	ConfigPaths []string
	Props       *p.Props
	AllowEmpty  bool

	// ProjectConfigPath is the discovered project-local ".<tool>.yaml" (a
	// repo-root config layer), or "" when none applies (no file, or an
	// explicit --config suppressed it). When set it is layered as the
	// highest-precedence file — but a hostile clone must not be able to
	// downgrade security posture through it, so unless the directory is
	// trusted (setup.IsProjectConfigTrusted) its security-sensitive keys are
	// stripped and it is read-only. See projectLayerBackend.
	ProjectConfigPath string

	// Flags is the dispatched command's full flag set (local + inherited).
	// Changed flags become the store's highest-precedence layer; nil skips
	// the layer (reload paths that outlive the invocation's flag values).
	Flags *pflag.FlagSet
	// BoundFlags maps config keys to author-declared flags whose names do
	// not follow the hyphen-to-dot convention (WithBoundFlags).
	BoundFlags map[string]*pflag.Flag
}

ConfigLoadOptions holds the options needed for loading configuration.

type ConsentOption added in v0.34.0

type ConsentOption func(*consentConfig)

ConsentOption configures promptTelemetryConsent behaviour.

func WithConsentInteractive added in v0.34.0

func WithConsentInteractive(isInteractive func() bool) ConsentOption

WithConsentInteractive overrides the TTY gate (default: utils.IsInteractive) so tests can exercise the interactive consent path without a real terminal.

type ExecuteOption added in v0.35.0

type ExecuteOption func(*executeOptions)

ExecuteOption customises how Execute runs the command tree.

func WithoutSignals added in v0.35.0

func WithoutSignals() ExecuteOption

WithoutSignals stops the framework installing its SIGINT/SIGTERM handler, so the tool owns signal disposition itself.

Signal disposition is process-global: whichever layer registers a handler becomes an owner of it, and signal.Notify is additive, so two owners means two shutdown drivers racing on one Ctrl-C. The framework claims that ownership by default because it has framework-wide work to do on interruption — flushing buffered telemetry, cleaning up a half-written self-update — and most commands have nothing of their own to run.

Reach for this only when the tool genuinely needs to own signals; having done so, it is responsible for the whole contract the framework otherwise provides: cancelling the command context, flushing telemetry, and choosing an exit code.

Note that a service supervisor such as gitlab.com/phpboyscout/go/controls is NOT a reason to opt out. It observes the context the framework cancels, which is exactly the intended arrangement.

type FlagValues

type FlagValues struct {
	Debug bool
}

FlagValues holds the command-line flag values extracted from cobra command.

type OutdatedVersionOption

type OutdatedVersionOption func(*outdatedVersionConfig)

OutdatedVersionOption configures handleOutdatedVersion behavior.

func WithForm

func WithForm(formCreator func(*bool) *huh.Form) OutdatedVersionOption

WithForm allows providing a custom form creator for testing.

func WithInteractive added in v0.34.0

func WithInteractive(isInteractive func() bool) OutdatedVersionOption

WithInteractive overrides the TTY gate (default: utils.IsInteractive) so tests can exercise the interactive prompt path without a real terminal.

type RootOption added in v0.17.0

type RootOption func(*rootOptions)

RootOption configures the root command constructed by NewCmdRootWithOptions. Options are the extensible way to register bound flags and subcommands without breaking the existing constructor signatures.

func WithBoundFlags added in v0.17.0

func WithBoundFlags(flags map[string]*pflag.Flag) RootOption

WithBoundFlags binds the given persistent/root pflags to configuration keys.

The map key is the dotted configuration key (e.g. "server.port") and the value is the pflag to bind. Bound flags participate in the documented configuration precedence (flags > env > file > embedded > defaults): a flag the user explicitly set on the command line overrides the corresponding config value. A flag left at its default does not override config — binding is filtered by flag.Changed during config load.

Example:

root.NewCmdRootWithOptions(props, root.WithBoundFlags(map[string]*pflag.Flag{
    "server.port": rootCmd.PersistentFlags().Lookup("server-port"),
}))

func WithConfigPaths added in v0.17.0

func WithConfigPaths(paths ...string) RootOption

WithConfigPaths registers additional configuration file paths to consider during initialisation. Equivalent to the configPaths argument of NewCmdRootWithConfig.

func WithConventionBoundFlags added in v0.17.0

func WithConventionBoundFlags(flags *pflag.FlagSet) RootOption

WithConventionBoundFlags binds every flag in the given flag set to a config key derived from its name by the hyphen-to-dot convention: "--server-port" becomes the config key "server.port". This is the zero-boilerplate alternative to WithBoundFlags when flag names already mirror config keys.

As with WithBoundFlags, only flags the user explicitly changed override config (filtered by flag.Changed during load). Flags already containing a dot are mapped verbatim; authors should avoid dots in flag names to keep the mapping unambiguous.

func WithSubcommands added in v0.17.0

func WithSubcommands(subcommands ...*setup.Command) RootOption

WithSubcommands registers subcommands on the root command.

type UpdateCheckResult

type UpdateCheckResult struct {
	HasUpdated bool
	ShouldExit bool
	Error      error
}

UpdateCheckResult holds the result of checking for updates.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL