Help us shape the future of Go! Take the 2026 Go Developer Survey

props

package
v0.50.0 Latest Latest
Warning

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

Go to latest
Published: Nov 1, 2026 License: MIT Imports: 30 Imported by: 0

README

Props

The core dependency injection container for GTB applications.

Props holds global state including:

  • Tool metadata
  • Logger instance
  • Configuration container
  • Filesystem abstraction (Afero)
  • Version information

For detailed documentation on design rationale and usage patterns, see the Props Component Documentation.

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

Examples

Constants

View Source
const (
	// ChatLinkPrefix prefixes a chat provider link: "chat-claude".
	ChatLinkPrefix = "chat-"
	// ForgeLinkPrefix prefixes a forge adapter link: "forge-github".
	ForgeLinkPrefix = "forge-"
)

Link prefixes name the features a generated project declares for the adapters it blank-imports (#81): one per chat provider the author chose and one per forge adapter, so the running tool's feature set records the author's exact choice rather than only which modules happen to be linked.

View Source
const (
	EventCommandInvocation = telemetrytypes.EventCommandInvocation
	EventCommandError      = telemetrytypes.EventCommandError
	EventFeatureUsed       = telemetrytypes.EventFeatureUsed
	EventUpdateCheck       = telemetrytypes.EventUpdateCheck
	EventUpdateApplied     = telemetrytypes.EventUpdateApplied
	EventDeletionRequest   = telemetrytypes.EventDeletionRequest
)
View Source
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
)
View Source
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")
)
View Source
const FrameworkBundle = "framework"

FrameworkBundle is the name under which the framework's baseline asset bundle (log defaults, the framework init template) registers.

View Source
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.

View Source
const ReleaseSourceStatic = "static"

ReleaseSourceStatic is the ReleaseSource.Type of the static release channel: releases discovered and retrieved from BaseURL with no forge involved.

View Source
const TelemetryCmd = FeatureID("telemetry")

TelemetryCmd is the feature flag for the telemetry command group. Default disabled — tool authors must explicitly enable it.

Variables

View Source
var (
	// ErrUnknownConfigLayer is a declared layer the framework does not know.
	ErrUnknownConfigLayer = errors.NewSentinel("gtb.props.unknown_config_layer", "unknown config layer")
	// ErrDuplicateConfigLayer is a layer declared more than once.
	ErrDuplicateConfigLayer = errors.NewSentinel("gtb.props.duplicate_config_layer", "duplicate config layer")
	// ErrConfigLayerOrder is a declared order that breaks one of spec 0204
	// D1's constraints.
	ErrConfigLayerOrder = errors.NewSentinel("gtb.props.config_layer_order", "config layer order")
	// ErrConfigSource is a source slot declared in a way spec 0204 D15
	// refuses.
	ErrConfigSource = errors.NewSentinel("gtb.props.config_source", "config source declaration")
	// ErrConfigFormat is an own config format that is not writable.
	ErrConfigFormat = errors.NewSentinel("gtb.props.config_format", "config format cannot be a tool's own")
	// ErrConfigKeyCase is a key case other than folded or exact.
	ErrConfigKeyCase = errors.NewSentinel("gtb.props.config_key_case", "unknown config key case")
)
View Source
var (
	// ErrInvalidDescriptor reports a descriptor missing a required field.
	ErrInvalidDescriptor = features.ErrInvalidDescriptor
	// ErrDuplicateFeature reports a second registration for one ID.
	ErrDuplicateFeature = features.ErrDuplicateFeature

	// 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 BoolPtr added in v0.43.0

func BoolPtr(v bool) *bool

BoolPtr returns a pointer to v, for the tri-state baselines on Tool (RequireChecksum) that distinguish unset from explicitly false. A generated root command uses it in a struct literal, where a variable would need a package-level declaration.

func ConfigFileSources added in v0.32.0

func ConfigFileSources(snap *config.Snapshot) []string

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 DeclareLinks(prefix string, names ...string)

DeclareLinks declares one link-kind feature per name under prefix on the default registry. A generated cmd/<name>/chat.go or forge.go calls it from init beside the blank imports it records, so the declaration and the link are the same file. It panics on a duplicate, as any init-time declaration does.

func DeclareLinksOn added in v0.43.0

func DeclareLinksOn(r features.Registry, prefix string, names ...string) error

DeclareLinksOn is DeclareLinks against a caller's registry. A link defaults on: its presence in the binary is its enablement (spec 0199 OQ3), and it carries no generated-code constant because nothing scaffolds a toggle for it.

func IsValidConfigLayer added in v0.36.0

func IsValidConfigLayer(name ConfigLayer) bool

IsValidConfigLayer reports whether name is a layer this framework knows.

func LinkedNames added in v0.43.0

func LinkedNames(set features.Set, prefix string) []string

LinkedNames returns the names declared as links under prefix and enabled in set, in the set's order, with the prefix stripped: the chat providers or forge adapters this binary declared it links. Empty when the tool declared none, which a hand-wired tool may not have.

func MarshalYAML added in v0.46.0

func MarshalYAML(v any) ([]byte, error)

MarshalYAML encodes v as YAML indented by two, the step go/config's format-preserving edits use, so a file the framework writes and the keys later added to it agree.

func RegisterFeature added in v0.36.0

func RegisterFeature(d FeatureDescriptor)

RegisterFeature declares a feature on the default 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 SlogLogger added in v0.32.0

func SlogLogger(p *Props) *slog.Logger

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 StatesOf added in v0.43.0

func StatesOf(fs []Feature) []features.State

StatesOf converts the manifest's feature list to the core's states.

func ValidateConfigFormat added in v0.46.0

func ValidateConfigFormat(format string) error

ValidateConfigFormat refuses an own format that is not one of the writable four, since init writes the file and config set edits it.

func ValidateConfigLayers added in v0.46.0

func ValidateConfigLayers(layers []ConfigLayer) error

ValidateConfigLayers refuses a declaration naming an unknown or repeated layer, or breaking one of spec 0204 D1's ordering constraints. Each constraint is a way to make a tool quietly unsafe, so each refusal says which one and why it exists.

func ValidateConfigSpec added in v0.46.0

func ValidateConfigSpec(spec ConfigSpec) error

ValidateConfigSpec refuses a declaration spec 0204 D1 or D15 refuses: its layers as ValidateConfigLayers does, with each source's name a layer too, and every source a valid, unique, placed slot.

func ViewOrNil added in v0.32.0

func ViewOrNil(p *Props) config.Reader

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 AssetMap

type AssetMap map[string]fs.FS

AssetMap is a custom type for a map of filesystems.

type AssetProvider

type AssetProvider interface {
	GetAssets() *Assets
}

AssetProvider provides access to embedded assets.

type Assets

type Assets struct {
	// contains filtered or unexported fields
}

Assets is a named set of fs.FS bundles that reads as one filesystem: static files shadow by registration order, structured config files merge. It implements fs.FS, fs.ReadDirFS, fs.GlobFS and fs.StatFS.

func NewAssets

func NewAssets(assets ...AssetMap) *Assets

NewAssets creates a new Assets wrapper with the given AssetMap pointers. The framework's baseline bundle registers first, so every later bundle — the tool's own, feature bundles applied at root construction — overrides it in the merged structured reads.

func (*Assets) Exists

func (a *Assets) Exists(name string) (fs.FS, error)

func (*Assets) For

func (a *Assets) For(names ...string) *Assets

For returns a subset of assets identified by the given names — exactly those, with no implicit framework bundle.

func (*Assets) Get

func (a *Assets) Get(name string) fs.FS

Get returns the filesystem registered with the given name.

func (*Assets) Glob added in v0.43.0

func (a *Assets) Glob(pattern string) ([]string, error)

Glob implements fs.GlobFS.

func (*Assets) Merge

func (a *Assets) Merge(others ...*Assets) *Assets

Merge appends the internal filesystems of the given Assets to the current wrapper and returns it.

func (*Assets) Mount

func (a *Assets) Mount(f fs.FS, prefix string)

Mount attaches a filesystem at a specific prefix.

func (*Assets) Names

func (a *Assets) Names() []string

func (*Assets) Open added in v0.43.0

func (a *Assets) Open(name string) (fs.File, error)

Open implements the fs.FS interface.

func (*Assets) ReadDir added in v0.43.0

func (a *Assets) ReadDir(name string) ([]fs.DirEntry, error)

ReadDir implements fs.ReadDirFS.

func (*Assets) Register

func (a *Assets) Register(name string, fs fs.FS)

Register adds the given filesystem to the wrapper with a name.

func (*Assets) SetLogger added in v0.43.0

func (a *Assets) SetLogger(l logger.Logger)

SetLogger wires a logger so malformed merge bundles surface as WARNs instead of vanishing silently. It is deliberately not part of the Assets interface — the framework wires it during root construction via a type assertion, and a nil logger simply keeps the previous silent behaviour.

func (*Assets) Slice

func (a *Assets) Slice() []fs.FS

Slice returns the internal slice of fs.FS pointers in order.

func (*Assets) Stat added in v0.43.0

func (a *Assets) Stat(name string) (fs.FileInfo, error)

Stat implements fs.StatFS.

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 the framework's order. Used to validate a declaration and to enumerate the set in generated output.

func CanonicalConfigLayers added in v0.46.0

func CanonicalConfigLayers(layers []ConfigLayer) []ConfigLayer

CanonicalConfigLayers returns a copy of layers in the framework's order, which is what a declaration made before spec 0204 resolved to.

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, in the same order.

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

type ConfigProvider interface {
	GetConfig() *config.Store
}

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 ConfigSource added in v0.46.0

type ConfigSource struct {
	// Name is unique in the stack, a config key segment and a command word:
	// it names the slot's layer, its settings and `init config <name>`.
	Name string `json:"name" yaml:"name"`
	// Kind picks the adapter: vault, consul, aws-s3, file, ...
	Kind string `json:"kind" yaml:"kind"`
	// Required, when nil, is true: an unreachable or unconfigured slot stops
	// the tool (spec 0204 D6).
	Required *bool `json:"required,omitempty" yaml:"required,omitempty"`
	// Writable, when nil, is the kind's default, which is read-only for every
	// kind but keychain (spec 0204 D7, D12).
	Writable *bool `json:"writable,omitempty" yaml:"writable,omitempty"`
}

ConfigSource is one declared config source slot.

func (ConfigSource) IsRequired added in v0.46.0

func (s ConfigSource) IsRequired() bool

IsRequired reports whether the slot's absence stops the tool.

func (ConfigSource) IsWritable added in v0.46.0

func (s ConfigSource) IsWritable(kindDefault bool) bool

IsWritable reports whether config writes may land in the slot, given its kind's default.

type ConfigSourceState added in v0.46.0

type ConfigSourceState string

ConfigSourceState is how a declared config source slot fared when the root built the store.

const (
	// ConfigSourceBuilt is a slot whose backend is a layer of the store.
	ConfigSourceBuilt ConfigSourceState = "built"
	// ConfigSourceUnconfigured is an optional slot nobody configured, left out.
	ConfigSourceUnconfigured ConfigSourceState = "not configured"
	// ConfigSourceUnavailable is an optional slot whose backend could not be
	// built, left out.
	ConfigSourceUnavailable ConfigSourceState = "unavailable"
)

type ConfigSourceStatus added in v0.46.0

type ConfigSourceStatus struct {
	Slot  ConfigSource
	State ConfigSourceState
	// Writable and Sensitive are the built layer's, after the slot's own
	// writable setting and the kind's default.
	Writable  bool
	Sensitive bool
	// Credential names the link of the slot's credential chain that answered,
	// as its factory reported it; empty when the kind reports none.
	Credential string
	// Err is why an unavailable slot was left out.
	Err string
}

ConfigSourceStatus records one declared slot's outcome (spec 0204 D10), so doctor can report the stack without building it again. A required slot that fails stops the root instead, so it never has a status.

type ConfigSpec added in v0.46.0

type ConfigSpec struct {
	// Layers is the stack by name, lowest precedence first: the order is the
	// precedence. Empty resolves to DefaultConfigLayers.
	Layers []ConfigLayer `json:"layers,omitempty" yaml:"layers,omitempty"`

	// Format is the format of the tool's own config file, the one init writes
	// and config set edits: yaml, toml, json or hcl. Empty is yaml. A format
	// other than yaml must be linked (spec 0204 D2).
	Format string `json:"format,omitempty" yaml:"format,omitempty"`

	// Sources are the config source slots the tool declares, each placed by
	// name in Layers (spec 0204 D15). Where each connects is runtime
	// configuration under config.sources.<name>, never declared here.
	Sources []ConfigSource `json:"sources,omitempty" yaml:"sources,omitempty"`

	// KeyCase is how every Store the tool builds compares keys. Empty is
	// folded (spec 0209 D1).
	KeyCase KeyCase `json:"key_case,omitempty" yaml:"key_case,omitempty"`
}

ConfigSpec is how a tool declares its configuration stack. See spec 0204.

func (ConfigSpec) WithKeyCase added in v0.48.0

func (s ConfigSpec) WithKeyCase(opts []config.StoreOption) []config.StoreOption

WithKeyCase appends the store option the spec's key case needs to opts. It is the one place a Store learns the tool's mode, so no Store the tool builds can disagree with another about a key (spec 0209 D2).

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 Enumerator added in v0.43.0

type Enumerator interface {
	Descriptors() []features.Descriptor
}

Enumerator is anything that lists descriptors: a features.Snapshot or a features.Set.

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)
		}
	}
}

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
	// Dynamic reports whether a flag backend may override the static state at
	// evaluation time (spec 0199 D7). Every built-in is static.
	Dynamic bool
	// Order places a non-builtin feature among its kind: features with an
	// Order sort by it after the built-ins, features without one sort after
	// those by kind and ID. The forges use it so a chooser lists them as the
	// framework intends (GitHub first, the default backend) rather than
	// alphabetically. Zero means unordered.
	Order int
}

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. It implements features.Descriptor and adds the two generated-code identifiers, which are GTB's concern rather than the core's (spec 0199 D2).

func DescriptorsIn added in v0.43.0

func DescriptorsIn(s Enumerator) []FeatureDescriptor

DescriptorsIn returns GTB's descriptors in s, in the snapshot's order. It is what a reader with a Set or a Snapshot in hand uses in place of the removed process-wide enumerations (spec 0199 D3).

func (FeatureDescriptor) DefaultOn added in v0.43.0

func (d FeatureDescriptor) DefaultOn() bool

DefaultOn implements features.Descriptor.

func (FeatureDescriptor) FeatureID added in v0.43.0

func (d FeatureDescriptor) FeatureID() features.ID

FeatureID implements features.Descriptor.

func (FeatureDescriptor) FeatureKind added in v0.43.0

func (d FeatureDescriptor) FeatureKind() features.Kind

FeatureKind implements features.Descriptor.

func (FeatureDescriptor) IsDynamic added in v0.43.0

func (d FeatureDescriptor) IsDynamic() bool

IsDynamic implements features.Descriptor.

func (FeatureDescriptor) Rank added in v0.43.0

func (d FeatureDescriptor) Rank() (int, bool)

Rank implements features.Ranked: built-ins keep the order the constant block declares, a feature with an Order follows them in that order, and everything else is unranked and sorts by kind and ID.

type FeatureID added in v0.36.0

type FeatureID = features.ID

FeatureID identifies a built-in feature that can be enabled or disabled.

type FeatureKind added in v0.36.0

type FeatureKind = features.Kind

FeatureKind classifies what a feature is, so "every forge" becomes a query rather than a list somebody has to remember to update. It is the core's Kind; the constants are GTB's.

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"
	// KindLink is a feature whose only effect is a blank import: declaring it
	// is what links the backend (the OS keychain), so its presence in a binary
	// is its enablement, and the generator toggles it by writing or removing
	// the file that imports it (spec 0199 OQ3). It has no runtime switch and
	// never appears in SetFeatures.
	KindLink FeatureKind = "link"
)

type FeatureState

type FeatureState func([]Feature) []Feature

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 IO added in v0.43.0

type IO interface {
	In() io.Reader
	Out() io.Writer
	Err() io.Writer
	// Interactive reports whether In is a terminal a person is typing at.
	Interactive() bool
	// Accessible reports whether forms should run as line prompts rather than
	// a TUI: a screen reader, TERM=dumb, or a test.
	Accessible() bool
}

IO is where an invocation reads and writes, and whether that is a person. It is one field on Props rather than three so the terminal is a thing Props has, and so a replacement (a recorded session, a remote terminal, an accessible-mode wrapper) swaps in whole (spec 0198 D1).

type KeyCase added in v0.48.0

type KeyCase string

KeyCase is a config stack's key mode: folded to lower case, or exact.

const (
	// KeyCaseFolded folds every key to lower case, so Log.Level is log.level.
	KeyCaseFolded KeyCase = "folded"
	// KeyCaseExact keeps every key as its source spells it.
	KeyCaseExact KeyCase = "exact"
)

type LoggerProvider

type LoggerProvider interface {
	GetLogger() logger.Logger
}

LoggerProvider provides access to the application logger.

type MCPConfig added in v0.43.0

type MCPConfig struct {
	Mode MCPMode `json:"mode,omitempty" yaml:"mode,omitempty"`
}

MCPConfig is the tool author's MCP posture, rendered into the generated root from properties.mcp in the manifest. The running binary never reads the manifest; changing the mode is a regenerate and a rebuild.

func (MCPConfig) Direct added in v0.43.0

func (c MCPConfig) Direct() bool

Direct reports whether the tool publishes native per-command tools.

type MCPMode added in v0.43.0

type MCPMode string

MCPMode is the publication mode the tool's MCP server uses: compact (three discovery tools) or direct (one native tool per command). The zero value is compact, so a tool that states nothing publishes compactly (spec 0201 D3).

const (
	// MCPCompact publishes search_tools, get_tool_details and call_tool.
	MCPCompact MCPMode = "compact"
	// MCPDirect publishes every exposed command as its own tool.
	MCPDirect MCPMode = "direct"
)

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

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

Flush is a no-op and never errors.

func (NoopCollector) Track added in v0.17.0

Track is a no-op.

func (NoopCollector) TrackCommand added in v0.17.0

func (NoopCollector) TrackCommand(string, int64, int, map[string]string)

TrackCommand is a no-op.

func (NoopCollector) TrackCommandExtended added in v0.17.0

func (NoopCollector) TrackCommandExtended(string, []string, int64, int, string, map[string]string)

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

func WithAssets(a *Assets) Option

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

func WithConfig(store *config.Store) Option

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 WithFeatures added in v0.43.0

func WithFeatures(s features.Snapshot) Option

WithFeatures names the snapshot the feature set is resolved from. When omitted, New snapshots the default registry, which is every feature this binary's imports declared.

func WithFlags added in v0.43.0

func WithFlags(e features.Evaluator) Option

WithFlags sets the request-time Evaluator. When omitted it is Features.

func WithResolver added in v0.43.0

func WithResolver(r features.Resolver) Option

WithResolver replaces the default Resolver (defaults, then the tool's states in order) for a consumer with another precedence.

func WithSet added in v0.43.0

func WithSet(s features.Set) Option

WithSet supplies a resolved Set directly, bypassing resolution; a test or a consumer with its own Set implementation uses this.

func WithVersion added in v0.34.0

func WithVersion(v version.Info) Option

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.Info
	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
	// IO is this invocation's streams and whether a person is at them. Nil
	// reads as StdIO{}, the process's; the root fills it from the command once
	// (spec 0198 D1). Read through GetIO.
	IO IO
	// Features is what this tool has: the resolved enabled state of every
	// feature this binary declared, and the contributions of the enabled ones
	// (spec 0199 D3). New resolves it from a snapshot of the default registry
	// and Tool.Features; ApplyDefaults does the same for a literal Props. Every
	// static decision (which commands exist, what doctor inventories) reads it.
	Features features.Set
	// Flags answers whether a feature is on for a request. It defaults to
	// Features (every answer static); a service wires a features.Dynamic over a
	// backend for the features that opt in (spec 0199 D10).
	Flags features.Evaluator
	// LogLevel is the level the root's --debug and the config's log.level
	// move, for a command that builds its own slog handler (the MCP server
	// does: its stdout carries the protocol, so it logs to stderr on its own
	// handler). Per root, like everything else here (spec 0202 D4). Read
	// through GetLogLevel.
	LogLevel *slog.LevelVar
	// SourceStatuses is how each declared config source slot fared when the
	// root built the store, in declared order (spec 0204 D10).
	SourceStatuses []ConfigSourceStatus
	// contains filtered or unexported fields
}

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

func New(tool Tool, log logger.Logger, fs afero.Fs, opts ...Option) (*Props, error)

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.

Example (Features)
package main

import (
	"fmt"

	"github.com/spf13/afero"

	"gitlab.com/phpboyscout/go-tool-base/pkg/logger"
	"gitlab.com/phpboyscout/go-tool-base/pkg/props"
)

func main() {
	tool := props.Tool{
		Name: "mytool",
		Features: props.SetFeatures(
			props.Enable(props.AiCmd),
		),
	}

	p, err := props.New(tool, logger.NewNoop(), afero.NewMemMapFs())
	if err != nil {
		panic(err)
	}

	fmt.Println("AI:", p.Features.Enabled(props.AiCmd))
	fmt.Println("Init:", p.Features.Enabled(props.InitCmd))
}
Output:
AI: true
Init: true

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) GetAssets

func (p *Props) GetAssets() *Assets

GetAssets returns the embedded assets.

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) GetConfig

func (p *Props) GetConfig() *config.Store

GetConfig returns the live configuration store.

func (*Props) GetConfigFS added in v0.32.0

func (p *Props) GetConfigFS() config.FS

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

func (p *Props) GetConfigView() *config.View

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) GetFS

func (p *Props) GetFS() afero.Fs

GetFS returns the application filesystem.

func (*Props) GetFeatures added in v0.43.0

func (p *Props) GetFeatures() features.Set

GetFeatures returns the resolved feature set. New and ApplyDefaults populate Features; a literal Props that skipped both (a test building one command) resolves here from the default registry, dropping any enable of a feature nothing declared, the way ApplyDefaults would. Nil-safe by design, like GetIO: a command reads its gate through this and never dereferences the field.

func (*Props) GetFlags added in v0.43.0

func (p *Props) GetFlags() features.Evaluator

GetFlags returns the request-time evaluator, defaulting to the feature set.

func (*Props) GetIO added in v0.43.0

func (p *Props) GetIO() IO

GetIO returns the invocation's streams, the process's when none were set.

func (*Props) GetLogLevel added in v0.43.0

func (p *Props) GetLogLevel() *slog.LevelVar

GetLogLevel returns the level var the root moves, creating it on a Props nobody defaulted so every reader shares one. A nil Props answers with a var nothing will ever move.

func (*Props) GetLogger

func (p *Props) GetLogger() logger.Logger

GetLogger returns the application logger.

func (*Props) GetTool

func (p *Props) GetTool() Tool

GetTool returns the tool metadata.

func (*Props) GetVersion

func (p *Props) GetVersion() version.Info

GetVersion returns the version information.

func (*Props) Validate added in v0.34.0

func (p *Props) Validate() error

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"`
	// BaseURL is the static release channel's one setting (spec 0203 D1): the
	// HTTPS location the pointer and the per-tag manifests are published under.
	// Read only when Type is ReleaseSourceStatic; the forge fields are unused then.
	BaseURL string `json:"base_url,omitempty" yaml:"base_url,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 and Host address the connection, and become a forge.Endpoint at construction. Owner and Repo name what a provider is asked to operate on, and are passed per call rather than baked into the provider.

Provider-specific settings — the direct provider's url_template and its siblings — are NOT here. They live in the tool's configuration under the source's own subtree (direct.url_template, and so on), which is what lets two sources of one type coexist. A Params map used to carry them; spec 0192 removed it when go/forge v0.12.0 stopped reading one.

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:"-"`

	// AnchorOnlyKeys are ASCII-armored public keys that join the trust set,
	// and so the embedded-versus-WKD cross-check, but never validate a
	// release signature. A rotation-authority key published beside the
	// release keys belongs here rather than in EmbeddedKeys.
	AnchorOnlyKeys [][]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

func (s SlackHelp) SupportMessage() string

SupportMessage renders the Slack contact line, or an empty string when the channel is not fully configured (an empty message suppresses help output).

type StdIO added in v0.43.0

type StdIO struct {
	Stdin  io.Reader
	Stdout io.Writer
	Stderr io.Writer
	// AccessibleMode asks for line prompts; the root's --accessible flag and
	// GTB_ACCESSIBLE set it.
	AccessibleMode bool
}

StdIO is the process's streams, or the ones a caller supplies. The zero value is stdin, stdout and stderr.

func (StdIO) Accessible added in v0.43.0

func (s StdIO) Accessible() bool

Accessible reports the explicit switch, or TERM=dumb, which is huh's own rule for switching to line prompts, applied here so the two agree.

func (StdIO) Err added in v0.43.0

func (s StdIO) Err() io.Writer

Err returns Stderr, or the process's.

func (StdIO) In added in v0.43.0

func (s StdIO) In() io.Reader

In returns Stdin, or the process's.

func (StdIO) Interactive added in v0.43.0

func (s StdIO) Interactive() bool

Interactive reports whether In is a terminal. A reader that is not an *os.File is not, and neither is a file that is not a terminal: /dev/null is a character device, which is why the old ModeCharDevice check said yes to a stdin nobody was typing at.

func (StdIO) Out added in v0.43.0

func (s StdIO) Out() io.Writer

Out returns Stdout, or the process's.

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

func (t TeamsHelp) SupportMessage() string

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"`

	// Config declares the tool's configuration stack and its order. See
	// ConfigSpec and ResolveConfigLayers.
	Config ConfigSpec `json:"config,omitempty" yaml:"config,omitempty"`

	// ConfigLayers declares which layers of the configuration stack this tool
	// wires, resolved in the framework's order whatever order it lists.
	//
	// Deprecated: use Config.Layers, whose order is the precedence (spec 0204
	// D13).
	ConfigLayers []ConfigLayer `json:"config_layers,omitempty" yaml:"config_layers,omitempty"`
	// MCP is the publication mode of the tool's MCP server; the zero value is
	// compact. See MCPConfig.
	MCP MCPConfig `json:"mcp,omitempty" yaml:"mcp,omitempty"`

	// ReleaseSource is the source of truth for the tool's releases; see
	// ReleaseSource for the supported types.
	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.
	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) ConfigFilename added in v0.46.0

func (t Tool) ConfigFilename() string

ConfigFilename is the base name of the tool's own config file: config plus its format's extension.

func (Tool) GetReleaseSource

func (t Tool) GetReleaseSource() (sourceType, owner, repo string)

GetReleaseSource returns the release source type, owner, and repo.

func (Tool) ResolveConfigLayers added in v0.36.0

func (t Tool) ResolveConfigLayers() []ConfigLayer

ResolveConfigLayers returns the layers a tool wires, lowest precedence first: Config.Layers as declared, else the deprecated ConfigLayers in the framework's order, else the framework default.

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) ResolvedConfigSpec added in v0.46.0

func (t Tool) ResolvedConfigSpec() ConfigSpec

ResolvedConfigSpec is Config with its layer list resolved, the declaration the store builds from.

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.

Directories

Path Synopsis
Package test provides a public test-fixture helper for constructing a fully-wired *props.Props with hermetic, safe defaults.
Package test provides a public test-fixture helper for constructing a fully-wired *props.Props with hermetic, safe defaults.

Jump to

Keyboard shortcuts

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