Documentation
¶
Overview ¶
Package props defines the Props dependency container, the central type-safe dependency injection mechanism used throughout GTB.
Props is a concrete struct that aggregates tool metadata, logger, configuration, filesystem (afero.Fs), embedded assets, version info, and error handler into a single value threaded through commands and services. It replaces context.Context-based DI with compile-time type safety.
The package also provides the feature flag system via SetFeatures, Enable, and Disable functional options, controlling which built-in commands (update, init, docs, mcp) are registered at startup. Tool release source metadata for GitHub and GitLab backends is managed through ReleaseSource.
Index ¶
- Constants
- Variables
- func ConfigFileSources(snap *config.Snapshot) []string
- func IsValidConfigLayer(name ConfigLayer) bool
- func RegisterFeature(d FeatureDescriptor)
- func SealFeatures()
- func SlogLogger(p *Props) *slog.Logger
- func ViewOrNil(p *Props) config.Reader
- type AssetMap
- type AssetProvider
- type Assets
- type BootstrapPolicy
- type ConfigLayer
- type ConfigProvider
- type DeliveryMode
- type EventType
- type Feature
- type FeatureDescriptor
- type FeatureID
- type FeatureKind
- type FeatureState
- type LoggerProvider
- type NoopCollector
- func (NoopCollector) BackendInfo() string
- func (NoopCollector) Close(context.Context) error
- func (NoopCollector) Drop() error
- func (NoopCollector) Enabled() bool
- func (NoopCollector) Flush(context.Context) error
- func (NoopCollector) Track(EventType, string, map[string]string)
- func (NoopCollector) TrackCommand(string, int64, int, map[string]string)
- func (NoopCollector) TrackCommandExtended(string, []string, int64, int, string, map[string]string)
- type Option
- type Props
- func (p *Props) ApplyDefaults()
- func (p *Props) GetAssets() Assets
- func (p *Props) GetCollector() TelemetryCollector
- func (p *Props) GetConfig() *config.Store
- func (p *Props) GetConfigFS() config.FS
- func (p *Props) GetConfigView() *config.View
- func (p *Props) GetErrorHandler() errorhandling.ErrorHandler
- func (p *Props) GetFS() afero.Fs
- func (p *Props) GetLogger() logger.Logger
- func (p *Props) GetTool() Tool
- func (p *Props) GetVersion() version.Version
- func (p *Props) Validate() error
- type ReleaseSource
- type SigningConfig
- type SlackHelp
- type TeamsHelp
- type TelemetryCollector
- type TelemetryConfig
- type Tool
- type ToolMetadataProvider
- type UpdatePolicy
Examples ¶
Constants ¶
const ( EventCommandInvocation = telemetrytypes.EventCommandInvocation EventCommandError = telemetrytypes.EventCommandError EventFeatureUsed = telemetrytypes.EventFeatureUsed EventUpdateCheck = telemetrytypes.EventUpdateCheck EventUpdateApplied = telemetrytypes.EventUpdateApplied EventDeletionRequest = telemetrytypes.EventDeletionRequest )
const ( // DeliveryAtLeastOnce deletes spill files after successful send. // Possible duplicates if the ack is lost; no data loss. DeliveryAtLeastOnce = telemetrytypes.DeliveryAtLeastOnce // DeliveryAtMostOnce deletes spill files before sending. // Possible data loss; no duplicates. DeliveryAtMostOnce = telemetrytypes.DeliveryAtMostOnce )
const ( UpdateCmd = FeatureID("update") InitCmd = FeatureID("init") McpCmd = FeatureID("mcp") DocsCmd = FeatureID("docs") AiCmd = FeatureID("ai") DoctorCmd = FeatureID("doctor") ConfigCmd = FeatureID("config") ChangelogCmd = FeatureID("changelog") ManCmd = FeatureID("man") )
const FrameworkBundle = "framework"
FrameworkBundle is the name under which the framework's baseline asset bundle (log defaults, the framework init template) registers.
const PackagePath = "gitlab.com/phpboyscout/go-tool-base/pkg/props"
PackagePath is this package's import path, the ConstPackage every built-in feature descriptor carries. Declared once so a generated import cannot drift from the package it names.
const TelemetryCmd = FeatureID("telemetry")
TelemetryCmd is the feature flag for the telemetry command group. Default disabled — tool authors must explicitly enable it.
Variables ¶
var ( // ErrInvalidDescriptor reports a descriptor missing a required field. ErrInvalidDescriptor = errors.NewSentinel("gtb.props.invalid_descriptor", "props: feature descriptor is incomplete") // ErrDuplicateFeature reports a second registration for one ID. ErrDuplicateFeature = errors.NewSentinel("gtb.props.duplicate_feature", "props: feature is already registered") // ErrRegistrySealed reports a registration attempted after enumeration began. ErrRegistrySealed = errors.NewSentinel("gtb.props.registry_sealed", "props: feature registry is sealed") // ErrPluginDefaultOn reports a non-builtin feature declaring itself // default-enabled. // // Adding a blank import must change what is *available*, never what is *on*. // Without that rule an import list becomes a behavioural file, and a // downstream that deliberately omits a provider cannot reason about what its // remaining imports switched on behind it. ErrPluginDefaultOn = errors.NewSentinel("gtb.props.plugin_default_on", "props: only builtin features may be default-enabled") )
Functions ¶
func ConfigFileSources ¶ added in v0.32.0
ConfigFileSources returns the paths of the loaded file layers in snapshot precedence order, deduplicating multi-document entries. The last element is the highest-precedence loaded file — the same answer Viper's ConfigFileUsed() gave (it reported the last file it loaded), which several consumers (doctor's report, telemetry's data dir, the config command's writable-target resolution) depend on for parity.
func IsValidConfigLayer ¶ added in v0.36.0
func IsValidConfigLayer(name ConfigLayer) bool
IsValidConfigLayer reports whether name is a layer this framework knows.
func RegisterFeature ¶ added in v0.36.0
func RegisterFeature(d FeatureDescriptor)
RegisterFeature adds a feature to the registry. It is called from the owning package's init, so a blank import is all a consumer needs.
It panics on a bad registration rather than returning an error: this runs at init, where there is no caller to handle a failure, and a silently dropped feature would surface much later as a missing entry in a doctor report or a scaffolded project.
func SealFeatures ¶ added in v0.36.0
func SealFeatures()
SealFeatures closes the registry. Registration afterwards fails rather than producing an enumeration that depends on when it was read.
Enumeration seals implicitly, so most callers never need this; it exists so a host can make the boundary explicit after its imports have run.
func SlogLogger ¶ added in v0.32.0
SlogLogger adapts p's logger to a *slog.Logger, falling back to a discarding logger when p is nil. The config adapters in pkg/chat and pkg/telemetry both need a slog handle from possibly-nil Props; hosting the conversion here keeps them from carrying identical copies (compare ViewOrNil).
func ViewOrNil ¶ added in v0.32.0
ViewOrNil pins a view of p's store, or returns a true-nil Reader when p or its store is absent. The nil handling is the point: returning a typed (*config.View)(nil) would box a nil pointer into a non-nil interface and silently defeat the cfg == nil guards the read adapters rely on.
Types ¶
type AssetProvider ¶
type AssetProvider interface {
GetAssets() Assets
}
AssetProvider provides access to embedded assets.
type Assets ¶
type Assets interface {
fs.FS
fs.ReadDirFS
fs.GlobFS
fs.StatFS
Slice() []fs.FS
Names() []string
Get(name string) fs.FS
Register(name string, fs fs.FS)
For(names ...string) Assets
Merge(others ...Assets) Assets
Exists(name string) (fs.FS, error)
Mount(f fs.FS, prefix string)
}
Assets is an interface that wraps a map of fs.FS pointers and implements the standard fs interfaces.
type BootstrapPolicy ¶ added in v0.29.0
type BootstrapPolicy struct {
// AutoInitialise, when true and the init feature is enabled, runs a
// non-interactive init (writing the default config) instead of failing when
// no config file is found, then continues with the freshly written config.
AutoInitialise bool `json:"auto_initialise,omitempty" yaml:"auto_initialise,omitempty"`
// SkipConfigCheck lists commands whose missing-config hard-fail is relaxed
// to a tolerant load: the pre-run falls back to the embedded default config
// rather than erroring, so the command's own PreRunE can manage bootstrap.
// An entry matches a command when it equals either the command's Name() or
// its full CommandPath() (e.g. "studio" or "krites studio") — the latter
// disambiguates same-named commands at different tree levels. A command may
// also be marked via the setup.SkipConfigCheck annotation; either mechanism
// relaxes the check.
SkipConfigCheck []string `json:"skip_config_check,omitempty" yaml:"skip_config_check,omitempty"`
// AuxiliaryCommands lists commands that take the root pre-run's auxiliary
// fast path: the framework bootstrap — config load, telemetry consent,
// collector wiring, update check — is skipped entirely (only the --debug
// flag is still honoured for logging). Cobra's own generated help,
// completion and __complete commands take this path automatically; the
// list lets a downstream tool extend the set (e.g. for its own
// bootstrap-free plumbing commands) without a framework release.
//
// This is stronger than SkipConfigCheck: an auxiliary command runs with
// props.Config left nil, so it must read nothing from configuration.
// Entries match a command's Name() or full CommandPath(), the same
// semantics as SkipConfigCheck.
AuxiliaryCommands []string `json:"auxiliary_commands,omitempty" yaml:"auxiliary_commands,omitempty"`
}
BootstrapPolicy governs how the framework treats configuration bootstrap and the missing-config gate in the root pre-run. The zero value reproduces the historical behaviour: a missing config file is a hard error when the init feature is enabled.
Neither knob skips the framework bootstrap itself (config load, telemetry, update check) — they only relax the missing-config *outcome*, preserving the "bootstrap always runs" invariant established by the 2026-06-12-bootstrap-prerun-traversal spec.
func (BootstrapPolicy) MatchesAuxiliaryList ¶ added in v0.33.0
func (b BootstrapPolicy) MatchesAuxiliaryList(name, path string) bool
MatchesAuxiliaryList reports whether a command identified by name and path is listed in AuxiliaryCommands, using the same Name()/CommandPath() equality semantics as MatchesSkipList.
func (BootstrapPolicy) MatchesSkipList ¶ added in v0.29.0
func (b BootstrapPolicy) MatchesSkipList(name, path string) bool
MatchesSkipList reports whether a command identified by name and path is listed in SkipConfigCheck. An entry may be a bare Name() or a full CommandPath(); either equality relaxes the missing-config check. Empty entries never match. The annotation half (setup.SkipConfigCheck) is evaluated separately by the root pre-run to avoid a props->setup import cycle.
type ConfigLayer ¶ added in v0.36.0
type ConfigLayer string
ConfigLayer names one layer of the configuration stack.
Which layers a tool wires used to be the framework's decision, inherited wholesale. That stops working as soon as credentials depend on layers: a regulated downstream that must not link a keychain backend, or a CI-only tool with no interactive environment, needs to decline a layer rather than have it wired on its behalf. See spec 0183 D4.
const ( // LayerDefaults is the embedded defaults every tool ships — merged // assets/config.yaml plus each feature bundle's own defaults. LayerDefaults ConfigLayer = "defaults" // LayerFiles is the tool's config files, in ConfigPaths order. LayerFiles ConfigLayer = "files" // LayerProject is a discovered project-local ".<tool>.yaml", subject to the // trust filter. LayerProject ConfigLayer = "project" // LayerEnv is environment variables under the tool's EnvPrefix. LayerEnv ConfigLayer = "env" // LayerFlags is changed CLI flags — the highest-precedence layer. LayerFlags ConfigLayer = "flags" )
func AllConfigLayers ¶ added in v0.36.0
func AllConfigLayers() []ConfigLayer
AllConfigLayers is every layer a tool may declare, in precedence order. Used to validate a declaration and to enumerate the set in generated output.
func DefaultConfigLayers ¶ added in v0.36.0
func DefaultConfigLayers() []ConfigLayer
DefaultConfigLayers is what a tool wires when it declares nothing: exactly what the framework wired before the set became declarable.
The order here is documentation, not mechanism — precedence is fixed by the store construction, so removing a layer cannot reorder the rest.
The keychain layer is deliberately absent. Wiring it is a decision the host binary makes through a blank import, precisely so a regulated build can omit it and have the linker drop go-keyring entirely; a default that switched it on would take that choice away. See spec 0183 D3 and D9.
type ConfigProvider ¶
ConfigProvider provides access to the live configuration store.
Take this when you need to write configuration, observe changes, or hold a reference across a reload.
type DeliveryMode ¶
type DeliveryMode = telemetrytypes.DeliveryMode
DeliveryMode controls the delivery guarantee for telemetry events. It is an alias for telemetrytypes.DeliveryMode; see EventType for why the canonical type lives in the leaf package.
type EventType ¶
type EventType = telemetrytypes.EventType
EventType identifies the category of telemetry event. It is an alias for telemetrytypes.EventType — the canonical type lives in the dependency-free pkg/telemetrytypes leaf so that pkg/telemetry can share it without importing pkg/props. Re-exported here (with the constants below) so commands can reference event types through props without importing pkg/telemetry.
type Feature ¶
type Feature struct {
ID FeatureID `json:"cmd" yaml:"cmd"`
Enabled bool `json:"enabled" yaml:"enabled"`
}
Feature represents the state of a feature (Enabled/Disabled).
The ID field is serialised as "cmd", which is deliberate rather than an oversight: the field was named Cmd when every feature was a command, and Tool.Features is persisted in generator manifests and tool configs. Renaming the Go identifier is a compile-time event a downstream fixes once; renaming the wire key would silently stop older manifests loading. So the identifier moved and the tag did not.
func SetFeatures ¶
func SetFeatures(mutators ...FeatureState) []Feature
SetFeatures applies a series of mutators to the standard default set.
Example ¶
package main
import (
"fmt"
"gitlab.com/phpboyscout/go-tool-base/pkg/props"
)
func main() {
features := props.SetFeatures(
props.Disable(props.InitCmd),
props.Enable(props.AiCmd),
props.Enable(props.TelemetryCmd),
)
for _, f := range features {
if f.Enabled {
fmt.Println("enabled:", f.ID)
}
}
}
Output:
type FeatureDescriptor ¶ added in v0.36.0
type FeatureDescriptor struct {
// ID is the feature's identity and its config/manifest name.
ID FeatureID
// ConstName is the exported Go identifier naming this feature's constant,
// as it must appear in generated source (e.g. "AiCmd").
//
// It cannot be derived from ID — "mcp" yields "McpCmd", not "MvpCmd" — and
// the generator emits it verbatim, so it is carried rather than computed.
// A feature with no exported constant cannot be scaffolded.
ConstName string
// ConstPackage is the import path of the package declaring ConstName.
//
// Built-in constants live in props, but a plugin's do not — the forge
// features are declared by pkg/setup/forge — and generated source must
// qualify the reference correctly. Carrying the path here is what keeps a
// feature's generator handling a property of its registration rather than a
// special case somewhere downstream.
ConstPackage string
// Kind classifies the feature. See [FeatureKind].
Kind FeatureKind
// Default reports whether the feature is enabled when a tool expresses no
// preference. Only [KindBuiltin] may set it — see [ErrPluginDefaultOn].
Default bool
}
FeatureDescriptor is everything the framework needs to know about a feature.
It is registered once, by the package that owns the feature, so a blank import is genuinely all it takes to make a feature visible everywhere — the doctor report, config validation, and the generator's handling.
func DescriptorFor ¶ added in v0.36.0
func DescriptorFor(id FeatureID) (FeatureDescriptor, bool)
DescriptorFor returns the descriptor for id.
func FeatureDescriptors ¶ added in v0.36.0
func FeatureDescriptors() []FeatureDescriptor
FeatureDescriptors returns every registered feature in a stable total order: built-ins in the order the constant block declares them, then everything else by (kind, id).
The order is derived from data, never from init() sequencing — Go orders init by dependency then filename, which is stable for one build but moves with the import graph. The doctor report and the generator's golden files both depend on this order, so it must not shift when an import is added.
Reading seals the registry.
type FeatureID ¶ added in v0.36.0
type FeatureID string
FeatureID identifies a built-in feature that can be enabled or disabled.
func AllFeatures ¶ added in v0.22.0
func AllFeatures() []FeatureID
AllFeatures is the canonical enumeration of every registered feature.
It reflects what this binary linked: a tool that blank-imports fewer providers enumerates fewer features, which is the correct runtime answer. The generator needs the complete *possible* set instead, and gets it from its own catalogue.
func FeaturesOfKind ¶ added in v0.36.0
func FeaturesOfKind(kind FeatureKind) []FeatureID
FeaturesOfKind returns the registered features of one kind, in the same order AllFeatures uses. It is what makes "every forge" a query rather than a list duplicated across the wizard, config validation and the doctor report.
type FeatureKind ¶ added in v0.36.0
type FeatureKind string
FeatureKind classifies what a feature is, so "every forge" becomes a query rather than a list somebody has to remember to update.
It is deliberately not a second identity type: FeatureID stays the currency of Enable, Disable, IsEnabled and the manifest. Kind is a property of a feature, not a different kind of thing.
const ( // KindBuiltin is a feature the framework itself ships and enumerates. KindBuiltin FeatureKind = "builtin" // KindForge is a forge integration contributed by a blank-imported package. KindForge FeatureKind = "forge" )
type FeatureState ¶
FeatureState is a functional option that mutates the list of features.
func DefaultFeatures ¶
func DefaultFeatures() []FeatureState
DefaultFeatures is the list of features enabled by default.
It is derived from the feature registry rather than hand-written, so a feature declares its own default alongside its identity and the two cannot drift. Only builtin features may be default-enabled — see ErrPluginDefaultOn.
func Disable ¶
func Disable(cmd FeatureID) FeatureState
Disable returns a FeatureState that disables the given command.
func Enable ¶
func Enable(cmd FeatureID) FeatureState
Enable returns a FeatureState that enables the given command.
type LoggerProvider ¶
LoggerProvider provides access to the application logger.
type NoopCollector ¶ added in v0.17.0
type NoopCollector struct{}
NoopCollector is a zero-behaviour TelemetryCollector. It is the default value of Props.Collector so the "always non-nil" invariant holds even before the root bootstrap resolves the real collector (e.g. on the init and help paths) and for Props constructed directly as a struct literal (tests, cmd/e2e).
The concrete *telemetry.Collector from pkg/telemetry supersedes it once the root PersistentPreRunE runs; NoopCollector reports Enabled() == false so the telemetry flush is correctly skipped while it is in place.
func (NoopCollector) BackendInfo ¶ added in v0.17.0
func (NoopCollector) BackendInfo() string
BackendInfo reports the disabled noop backend.
func (NoopCollector) Close ¶ added in v0.17.0
func (NoopCollector) Close(context.Context) error
Close is a no-op and never errors.
func (NoopCollector) Drop ¶ added in v0.17.0
func (NoopCollector) Drop() error
Drop is a no-op and never errors.
func (NoopCollector) Enabled ¶ added in v0.17.0
func (NoopCollector) Enabled() bool
Enabled always reports false: a noop collector never actively collects.
func (NoopCollector) Flush ¶ added in v0.17.0
func (NoopCollector) Flush(context.Context) error
Flush is a no-op and never errors.
func (NoopCollector) Track ¶ added in v0.17.0
func (NoopCollector) Track(EventType, string, map[string]string)
Track is a no-op.
func (NoopCollector) TrackCommand ¶ added in v0.17.0
TrackCommand is a no-op.
func (NoopCollector) TrackCommandExtended ¶ added in v0.17.0
TrackCommandExtended is a no-op.
type Option ¶ added in v0.34.0
type Option func(*Props)
Option configures a Props built by New.
func WithAssets ¶ added in v0.34.0
WithAssets sets the embedded assets bundle.
func WithCollector ¶ added in v0.34.0
func WithCollector(c TelemetryCollector) Option
WithCollector sets the telemetry collector. When omitted, New defaults a NoopCollector so callers can invoke Props.Collector unconditionally.
func WithConfig ¶ added in v0.34.0
WithConfig sets the live configuration store. It is normally left unset at construction — the root pre-run loads and assigns it — so New does not require it (see Validate's init-path exemption).
func WithErrorHandler ¶ added in v0.34.0
func WithErrorHandler(eh errorhandling.ErrorHandler) Option
WithErrorHandler sets the structured error handler. When omitted, New defaults one built from the logger and Tool.Help.
func WithVersion ¶ added in v0.34.0
WithVersion sets the runtime/ldflags version info.
type Props ¶
type Props struct {
Tool Tool
Logger logger.Logger
// Config is the live configuration store. It owns reloads, so it is the
// thing to hold; a *config.View taken from it is pinned to one snapshot and
// goes stale the moment the file changes.
//
// Read through Config.View(), which is the read surface. Where several
// values must agree with each other, use Config.With so they all resolve
// against the same snapshot rather than straddling a reload.
Config *config.Store
Assets Assets
FS afero.Fs
Version version.Version
ErrorHandler errorhandling.ErrorHandler
// Collector is always non-nil once the root command tree is built: the
// bootstrap defaults it to a NoopCollector and later replaces it with the
// resolved *telemetry.Collector. Consumers may call it unconditionally.
Collector TelemetryCollector
}
Props is the primary dependency injection container for GTB applications. When writing functions that accept Props, consider whether a narrow interface (LoggerProvider, ConfigProvider, etc.) would suffice.
func New ¶ added in v0.34.0
New constructs a Props from the required dependencies plus options for the rest, applying the framework defaults and validating the nil-field contract. It is the blessed construction path: the required fields (a named Tool, a Logger, and a filesystem) must be present, while the optional fields (Collector, ErrorHandler, Version) are defaulted when omitted. Config is left nil unless supplied — the pre-run assigns it, and the init path runs with it nil by design (see Validate).
Most of the codebase still builds Props as a struct literal; New exists so a downstream tool (or a test) can get the nil-field contract checked at one place instead of discovering a nil Logger as a panic deep in a command.
func (*Props) ApplyDefaults ¶ added in v0.34.0
func (p *Props) ApplyDefaults()
applyDefaults fills in the optional fields that the framework guarantees are non-nil: Collector (a NoopCollector), ErrorHandler (built from the logger and Tool.Help), and Version (an empty, development-flavoured Info). It never touches Config — that is the caller's / pre-run's responsibility. Idempotent and nil-safe on the optional fields, so it is safe to call from both New and the root bootstrap.
func (*Props) GetCollector ¶ added in v0.18.0
func (p *Props) GetCollector() TelemetryCollector
GetCollector returns the telemetry collector. It is always non-nil once the root command tree is built, so consumers may call it unconditionally.
func (*Props) GetConfigFS ¶ added in v0.32.0
GetConfigFS adapts the application filesystem for the config store.
Props.FS stays a full afero.Fs — OsFs live, MemMapFs under test — because that is how the rest of GTB operates. config defines its own narrower interface rather than depending on afero, so the two are bridged here in one place instead of at every construction site.
func (*Props) GetConfigView ¶ added in v0.32.0
GetConfigView returns a read surface pinned to the current snapshot.
Named GetConfigView rather than Config because Props already has a Config field and Go forbids a method sharing its name.
Take one per logical operation and let it go — holding one across a reload silently serves stale values. Reading several related keys from one view is the point: they are guaranteed to come from the same snapshot.
func (*Props) GetErrorHandler ¶
func (p *Props) GetErrorHandler() errorhandling.ErrorHandler
GetErrorHandler returns the error handler.
func (*Props) GetVersion ¶
GetVersion returns the version information.
func (*Props) Validate ¶ added in v0.34.0
Validate reports whether the required Props fields are present. It is the enforcement half of the nil-field contract that used to live only in doc comments.
Required: a named Tool, a Logger, and a filesystem — every command path reads these. NOT required: Config. It is deliberately nil on the init path (init is what CREATES the configuration) and until the root pre-run loads it, so a blanket "Config must be non-nil" check would be wrong; code that touches Config on those paths must guard it (Store.View is not nil-receiver-safe — use ViewOrNil). The optional fields are defaulted by applyDefaults, so Validate does not police them.
type ReleaseSource ¶
type ReleaseSource struct {
Type string `json:"type" yaml:"type"`
Host string `json:"host" yaml:"host"`
Owner string `json:"owner" yaml:"owner"`
Repo string `json:"repo" yaml:"repo"`
Private bool `json:"private" yaml:"private"`
// Params holds provider-specific configuration key/value pairs.
// Keys use snake_case. Valid keys are documented per provider.
Params map[string]string `json:"params,omitempty" yaml:"params,omitempty"`
}
ReleaseSource identifies the platform and repository where the tool's releases are published. Used by the self-update system to check for and download new versions. Supported types: "github", "gitlab", "bitbucket", "gitea", "codeberg", "direct".
type SigningConfig ¶ added in v0.12.0
type SigningConfig struct {
// EmbeddedKeys are ASCII-armored public keys baked into the binary,
// typically via //go:embed in an internal trustkeys package. They are
// the embedded trust anchor the SelfUpdater uses to verify the
// signature over checksums.txt. Not serialised — keys are compile-time
// build artefacts, never runtime config.
EmbeddedKeys [][]byte `json:"-" yaml:"-"`
// RequireChecksum is the tool author's baseline for checksum enforcement,
// consulted when neither config nor environment supplies one. It is a
// pointer so "unset" is distinguishable from "explicitly false": nil
// leaves the framework default (permissive), and a tool wanting
// fail-closed verification from day one sets it true.
//
// It lives here, beside the other tool-author baselines, rather than as a
// package variable in pkg/setup. The framework constructs the updater, so
// a tool cannot pass an option to it — but it does own its Props, and
// process-wide mutable state is not a configuration mechanism. Runtime
// config (`update.require_checksum`) still overrides it.
RequireChecksum *bool `json:"-" yaml:"-"`
}
SigningConfig holds tool-author configuration for self-update signature verification (Phase 2 of the remote-update-checksum spec). The zero value is safe: with no embedded keys and no external key email configured, signature verification is governed entirely by setup.DefaultRequireSignature (default false) and stays dormant.
type SlackHelp ¶ added in v0.32.0
type SlackHelp struct {
Team string `json:"team" yaml:"team"`
Channel string `json:"channel" yaml:"channel"`
}
SlackHelp provides Slack channel contact details as a support message. It implements the errorhandling.HelpConfig interface, which the go/errorhandling module defines but deliberately ships no implementations for — where a team's support channel lives is a framework/tool concern, not something an error-reporting library should have an opinion about.
Assign it to Tool.Help; the scaffolded root command does this for tools generated with `--help-type slack`.
func (SlackHelp) SupportMessage ¶ added in v0.32.0
SupportMessage renders the Slack contact line, or an empty string when the channel is not fully configured (an empty message suppresses help output).
type TeamsHelp ¶ added in v0.32.0
type TeamsHelp struct {
Team string `json:"team" yaml:"team"`
Channel string `json:"channel" yaml:"channel"`
}
TeamsHelp provides Microsoft Teams channel contact details as a support message. See SlackHelp for the rationale behind these living in GTB rather than in the errorhandling module.
func (TeamsHelp) SupportMessage ¶ added in v0.32.0
SupportMessage renders the Teams contact line, or an empty string when the channel is not fully configured (an empty message suppresses help output).
type TelemetryCollector ¶
type TelemetryCollector interface {
// Track records a telemetry event. No-op when the collector is disabled.
Track(eventType EventType, name string, extra map[string]string)
// TrackCommand records a command invocation with duration and exit code.
TrackCommand(name string, durationMs int64, exitCode int, extra map[string]string)
// TrackCommandExtended records a command invocation with full context including
// arguments and error message. When ExtendedCollection is disabled on the
// collector, args and errMsg are silently dropped — callers do not need to
// check the flag themselves.
TrackCommandExtended(name string, args []string, durationMs int64, exitCode int, errMsg string, extra map[string]string)
// Flush sends all buffered events to the backend and clears the buffer.
// Checks for and sends spill files before flushing the current buffer.
Flush(ctx context.Context) error
// BackendInfo returns a human-readable description of the active backend
// (e.g. "otlp (otlp-gateway-prod-gb-south-1.grafana.net)", "noop", "file").
BackendInfo() string
// Close flushes pending events and shuts down the backend gracefully.
// Must be called on process exit to ensure backends like OTLP flush
// their internal batch queues.
Close(ctx context.Context) error
// Drop clears all buffered events and deletes any spill files without sending.
Drop() error
// Enabled reports whether the collector is actively collecting (as opposed
// to a no-op disabled collector). Reflects the resolved decision from
// config, env override, or tool-author ForceEnabled.
Enabled() bool
}
TelemetryCollector is the interface through which commands emit telemetry events. Defined here (not in pkg/telemetry) to avoid an import cycle. The concrete implementation is *telemetry.Collector from pkg/telemetry. Props.Collector is always non-nil — when telemetry is disabled it is a noop.
type TelemetryConfig ¶
type TelemetryConfig struct {
// Endpoint is the HTTP JSON endpoint to POST events to.
// Ignored when OTelEndpoint is set or when Backend is non-nil.
Endpoint string `json:"endpoint,omitempty" yaml:"endpoint,omitempty"`
// OTelEndpoint is the OTLP/HTTP base URL (e.g. "https://collector:4318").
// Takes precedence over Endpoint when set.
OTelEndpoint string `json:"otel_endpoint,omitempty" yaml:"otel_endpoint,omitempty"`
// OTelHeaders are HTTP headers sent with every OTLP request (e.g. auth tokens).
OTelHeaders map[string]string `json:"otel_headers,omitempty" yaml:"otel_headers,omitempty"`
// OTelInsecure disables TLS for the OTLP endpoint — use only for local collectors.
OTelInsecure bool `json:"otel_insecure,omitempty" yaml:"otel_insecure,omitempty"`
// Backend is an optional factory for a custom telemetry backend.
// Typed as func(*Props) any to avoid importing pkg/telemetry from pkg/props.
// The returned value must implement telemetry.Backend — pkg/cmd/root performs
// the type assertion. Takes precedence over Endpoint and OTelEndpoint when set.
// Not serialisable — set programmatically in tool setup.
Backend func(*Props) any `json:"-" yaml:"-"`
// DeletionRequestor is an optional factory for a custom GDPR deletion requestor.
// Typed as func(*Props) any to avoid importing pkg/telemetry from pkg/props.
// The returned value must implement telemetry.DeletionRequestor — pkg/cmd/root
// performs the type assertion. If nil, falls back to sending a
// data.deletion_request event through the existing backend.
// Not serialisable — set programmatically in tool setup.
DeletionRequestor func(*Props) any `json:"-" yaml:"-"`
// ForceEnabled bypasses the user consent prompt and enables telemetry
// unconditionally. The telemetry enable/disable/reset commands remain
// functional so users can still inspect status, but collection cannot be
// turned off. Intended for enterprise tools deployed in controlled
// environments where telemetry is a contractual requirement.
// When set, the first-run consent prompt is suppressed.
ForceEnabled bool `json:"force_enabled,omitempty" yaml:"force_enabled,omitempty"`
// ExtendedCollection enables collection of command arguments and error messages
// in telemetry events. Default: false. When false, args and errors are never
// recorded regardless of what callers pass to TrackCommandExtended.
//
// Enable this only in closed enterprise environments where users are
// contractually bound by security policies. In public-facing tools, leave
// this disabled to preserve user privacy.
ExtendedCollection bool `json:"extended_collection,omitempty" yaml:"extended_collection,omitempty"`
// DeliveryMode controls the delivery guarantee. Default: DeliveryAtLeastOnce.
DeliveryMode DeliveryMode `json:"delivery_mode,omitempty" yaml:"delivery_mode,omitempty"`
// Metadata is additional key/value pairs included in every event.
// Useful for custom dimensions like environment name or deployment tier.
Metadata map[string]string `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
TelemetryConfig holds tool-author telemetry declarations. It is embedded in Tool and specifies where and how to send telemetry. The end-user's opt-in state is stored in the config file under telemetry.enabled and telemetry.local_only — endpoints are not user-configurable.
type Tool ¶
type Tool struct {
Name string `json:"name" yaml:"name"`
Summary string `json:"summary" yaml:"summary"`
Description string `json:"description" yaml:"description"`
Features []Feature `json:"features" yaml:"features"`
Help errorhandling.HelpConfig `json:"-" yaml:"-"`
// Bootstrap groups config-bootstrap lifecycle policy — auto-initialisation
// and per-command relaxation of the missing-config gate. Zero value is
// safe and reproduces historical behaviour. See BootstrapPolicy.
Bootstrap BootstrapPolicy `json:"bootstrap,omitempty" yaml:"bootstrap,omitempty"`
// ConfigLayers declares which layers of the configuration stack this tool
// wires. Empty means "unstated" and resolves to DefaultConfigLayers — the
// set the framework wired before this became declarable — so an existing
// tool keeps resolving exactly as it did. See ResolveConfigLayers.
ConfigLayers []ConfigLayer `json:"config_layers,omitempty" yaml:"config_layers,omitempty"`
// ReleaseSource is the source of truth for the tool's releases (GitHub or GitLab)
ReleaseSource ReleaseSource `json:"release_source" yaml:"release_source"`
// ReleaseProvider, when non-nil, is the release backend the self-update
// subsystem uses, taking precedence over ReleaseSource.Type registry
// lookup. It is a runtime-only dependency-injection seam — for tests
// (see go/forge/test) and custom/embedded providers — and is
// never serialised. Production tools leave it nil and are resolved from the
// release registry by ReleaseSource.Type as before. An explicit
// setup.WithReleaseProvider option takes precedence over this field.
ReleaseProvider forge.Provider `json:"-" yaml:"-"`
// UpdatePolicy is the tool author's baseline self-update posture
// (disabled / prompt / enabled). The zero value normalises to
// UpdatePolicyDisabled — the framework default — so a tool does not
// force updates out of the box. Overridable at runtime by the
// `update.policy` config key. See ResolveUpdatePolicy.
UpdatePolicy UpdatePolicy `json:"update_policy,omitempty" yaml:"update_policy,omitempty"`
// UpdateCheckInterval is the tool author's baseline throttle between
// self-update checks. The zero value means "use the framework default"
// (setup.DefaultCheckInterval, 24h) — it is NOT interpreted as
// "check every run". Overridable at runtime by the `update.check_interval`
// config key (where "0"/"0s" does mean every run). See
// setup.ResolveCheckInterval.
UpdateCheckInterval time.Duration `json:"update_check_interval,omitempty" yaml:"update_check_interval,omitempty"`
// Telemetry holds tool-author telemetry configuration.
// Zero-value is safe — tools that don't set it are unaffected.
Telemetry TelemetryConfig `json:"telemetry" yaml:"telemetry"`
// Signing holds tool-author self-update signature configuration.
// Zero-value is safe — verification stays dormant until keys and/or
// require_signature are configured.
Signing SigningConfig `json:"-" yaml:"-"`
// EnvPrefix is the environment variable prefix used by the config package.
// When set, only env vars starting with this prefix (e.g., "GTB_") are
// considered for config overrides. Empty means no prefix (all env vars match).
EnvPrefix string `json:"env_prefix,omitempty" yaml:"env_prefix,omitempty"`
// InstallHint is shown when a feature requires a full release binary
// (e.g. embedded docs) that the running binary lacks. Set it to your
// tool's recommended installation command. When empty, a generic
// message referencing Name is shown — the framework never assumes a
// particular installer.
InstallHint string `json:"install_hint,omitempty" yaml:"install_hint,omitempty"`
}
Tool holds metadata about the CLI tool: its identity, feature flags, release source for self-updates, help channel configuration, and telemetry settings. It is embedded in Props and passed to all commands.
func (Tool) GetReleaseSource ¶
GetReleaseSource returns the release source type, owner, and repo.
func (Tool) IsDisabled ¶
IsDisabled checks if a feature is disabled.
func (Tool) IsEnabled ¶
IsEnabled checks if a feature is enabled. It checks the Features slice first, falling back to built-in defaults.
Example ¶
package main
import (
"fmt"
"gitlab.com/phpboyscout/go-tool-base/pkg/props"
)
func main() {
tool := props.Tool{
Name: "mytool",
Features: props.SetFeatures(
props.Enable(props.AiCmd),
),
}
fmt.Println("AI:", tool.IsEnabled(props.AiCmd))
fmt.Println("Init:", tool.IsEnabled(props.InitCmd))
}
Output: AI: true Init: true
func (Tool) ResolveConfigLayers ¶ added in v0.36.0
func (t Tool) ResolveConfigLayers() []ConfigLayer
ResolveConfigLayers returns the layer set a tool actually wires: its own declaration, or the framework default when it declares none.
An empty declaration means "unstated", not "none". A tool wanting genuinely no layers has nothing to configure and no reason to build a store, so reading empty as an opt-out would turn an omitted field into a silently broken tool.
func (Tool) WiresConfigLayer ¶ added in v0.36.0
func (t Tool) WiresConfigLayer(layer ConfigLayer) bool
WiresConfigLayer reports whether the tool wires the named layer.
type ToolMetadataProvider ¶
type ToolMetadataProvider interface {
GetTool() Tool
}
ToolMetadataProvider provides access to tool configuration and metadata.
type UpdatePolicy ¶ added in v0.18.0
type UpdatePolicy string
UpdatePolicy governs how the self-update check behaves when a newer release is found. It is the tool author's baseline (the Tool.UpdatePolicy field), overridable at runtime by the `update.policy` config key. See https://gitlab.com/phpboyscout/go-tool-base/-/wikis/specs/0087-forced-update-feature.
const ( // UpdatePolicyDisabled never auto-updates or prompts: it only logs that an // update is available (and a persistent out-of-date WARN). This is the // framework default (the zero value normalises to it), so a tool built on // GTB does nothing surprising out of the box. UpdatePolicyDisabled UpdatePolicy = "disabled" // UpdatePolicyPrompt asks the user to update when one is available; they may // decline and continue with their command. The GTB CLI itself uses this. UpdatePolicyPrompt UpdatePolicy = "prompt" // UpdatePolicyEnabled blocks every command until the tool is updated when an // update is available (the historical forced-update behaviour, now explicit // and opt-in). UpdatePolicyEnabled UpdatePolicy = "enabled" )
func ResolveUpdatePolicy ¶ added in v0.18.0
func ResolveUpdatePolicy(toolDefault UpdatePolicy, configValue string) UpdatePolicy
ResolveUpdatePolicy computes the effective update policy. A valid `update.policy` config value wins over the tool's baseline; an empty or invalid config value falls back to the baseline; an empty or invalid baseline falls back to the framework default (UpdatePolicyDisabled). Resolution is case-insensitive. `--ci` bypass is handled by the caller and is out of scope here.
func (UpdatePolicy) Normalize ¶ added in v0.18.0
func (p UpdatePolicy) Normalize() UpdatePolicy
Normalize trims, lowercases, and maps the empty policy (the zero value / framework default) to UpdatePolicyDisabled. An unknown non-empty value is returned trimmed/lowercased so UpdatePolicy.Valid can reject it.
func (UpdatePolicy) Valid ¶ added in v0.18.0
func (p UpdatePolicy) Valid() bool
Valid reports whether p is one of the three defined policies. The empty string is NOT valid (it is a sentinel for "unset"); callers wanting the default-applied value use UpdatePolicy.Normalize.