cli

package
v0.3.2 Latest Latest
Warning

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

Go to latest
Published: Oct 14, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package cli is the command-line engine: it validates a whole command tree once, resolves an argument vector against it, renders the help the tree implies, and returns a typed error carrying an exit status. It never ends the process.

Package cli — the engine's configuration: two writers, and deliberately nothing else.

Package cli — the one error the engine builds rather than declares. The domain's codes and sentinels are declared in internal/core/app/cli (ADR 0160); this file holds only the WrapParams that raise HELP_WRITE_FAILED over the writer's own error.

Package cli — resolution and dispatch: the loop that turns an argument vector into one corecli.InvocationValue and one corecli.Action call.

Package cli — the one seam between this domain and internal/core/app/config.

Package cli — the help, generated from the declarations and from nowhere else.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func New

func New(cfg Config, root corecli.CommandValue) (runner corecli.Executor, err error)

New validates a command tree and returns the corecli.Executor that runs it.

The WHOLE tree is validated here, not the branch an invocation happens to take. A declaration is a wiring fact, and a wiring fault discovered by the operator who typed the one command nobody had tried is a fault discovered in production: `tool db migrate` being unreachable must fail on the first run of the binary, not on the first run of the migration.

Every refusal is one of internal/core/app/cli's sentinels and carries EX_CONFIG (78). A corecli.Binder is CALLED here, once, on a throwaway set — that is what proves it binds no reserved name — so a Binder must be safe to call more than once. A Binder that panics is deliberately not recovered: it runs inside the caller's own main, on the line that wrote it, and a recovered panic there would replace a stack pointing at the bug with a sentence about a tree.

Types

type Config

type Config struct {
	// Output is where a command's OWN output goes. It reaches a [corecli.Action] as
	// InvocationValue.Output, so a command never has to reach for a process
	// stream itself.
	//
	// The zero value resolves to os.Stderr, NOT os.Stdout. ADR 0030 makes
	// stdout a protocol channel that no SDK default may claim: a tool whose
	// output is a JSON document on a pipe sets Output: os.Stdout in main,
	// which is one visible line at the one place that can honestly make the
	// choice. A zero value is what somebody who has not yet learned the
	// question exists chose, so it must not be the dangerous one.
	Output io.Writer
	// ErrOutput is where the generated help, and the usage that follows a bad
	// command line, are written. The zero value resolves to os.Stderr.
	//
	// It is a SECOND writer rather than the same one so that setting Output to
	// os.Stdout for a machine-readable tool does not put a help page in the
	// middle of the document a consumer is parsing.
	//
	// What is deliberately NOT written here is the error itself. The engine
	// owns the help — it is generated from the declarations and only the SDK
	// can render it — and the caller owns how an error is presented, because a
	// caller may want it as a line on stderr, as a log record, or as JSON. Two
	// voices printing one failure is how a CLI ends up saying it twice.
	ErrOutput io.Writer
}

Config configures one New. Its zero value is a working configuration.

It carries two writers and nothing else. There is no Signals field, no shutdown budget, no logger, no config source and no env prefix: every one of those is a domain the SDK already ships, and a CLI that grew its own copy would be a second, weaker one that the first would eventually disagree with. ADR 0065 §D6 lists what this domain composes and what it refuses to redo.

Config is a published concrete shape (pkg/v1/app/cli.Config aliases it), so ADR 0040 applies: it may still change while the module is v0, said out loud, and not after v1.

type FlagSourceValue

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

FlagSourceValue is one invocation's flags, shaped as the flat key→value layer internal/core/app/config.Source yields. It is the whole of what this domain contributes to configuration, and everything it deliberately is not is listed in FlagSource.

FlagSourceValue is a published concrete shape (pkg/v1/app/cli.FlagSource returns it), so ADR 0040 applies: it may still change while the module is v0, said out loud, and not after v1.

func FlagSource

func FlagSource(invocation corecli.InvocationValue) *FlagSourceValue

FlagSource snapshots the flags an operator ACTUALLY TYPED on one invocation and returns them as a configuration layer.

Only what was set

It walks flag.FlagSet.Visit and never VisitAll. That one word is the whole value of this adapter: VisitAll yields every DECLARED flag, including the ones nobody passed, carrying their Go defaults — so a flag with a zero default would override the file and the environment on every single run, and a configuration file would appear to be ignored for reasons nothing reports. Visit yields only what the command line actually carried, which is exactly what "the flags are the highest-precedence layer" is supposed to mean.

The flag name IS the config key

No transformation: no camelCase to snake_case, no "-" to "_", no automatic prefixing. Every such rule is a second grammar a reader has to learn and a generator has to reproduce, and its failure mode is a key nobody typed silently not matching a field. A flag that feeds `database.max_conns` declares itself as "database.max_conns" — package flag accepts a dot in a name and parses -database.max_conns=5 without help. And when the name is wrong, the config schema (ADR 0061) REFUSES an unaddressed key rather than ignoring it, so the mistake is loud at startup.

What this is not

It is not a configuration loader. It reads no file, no environment variable and no default; it does no layering, no decoding and no validation. Those are the config domain, they already exist, and a CLI carrying its own copy would be a second one to disagree with. The intended shape is one line:

err := config.LoadSchema(&cfg, schema,
	config.FileSource("toml", "/etc/tool.toml"),
	config.EnvSource("TOOL_"),
	cli.FlagSource(invocation)) // last wins

Precedence inside one invocation

The sets are visited root first, so a leaf flag overrides an ancestor flag of the same name — the same "later wins" direction config.Load already uses between sources, rather than a second rule to remember.

func (*FlagSourceValue) Load

func (s *FlagSourceValue) Load() (values map[string]any, err error)

Load returns the layer. It satisfies internal/core/app/config.Source.

Jump to

Keyboard shortcuts

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