Refresh operator documentation and CLI help

This commit is contained in:
2026-11-07 23:31:14 +00:00
parent 69fe35c480
commit c648d1de33
24 changed files with 1088 additions and 812 deletions

View File

@@ -1,36 +1,30 @@
# CommandKit ckconfig
`ckconfig` is the optional ConfigKit integration for CommandKit. It adapts
ConfigKit loaders and scopes into reusable `commandkit.Command` declarations;
installing the integration is explicit and does not modify an application by
itself.
`ckconfig` adds configuration inspection commands to Go applications that use
CommandKit and ConfigKit. Operators can view settings, validate configuration,
find where values came from, and compare two configurations.
The application chooses which commands to install. Importing the package alone
does not install commands.
## Module
The module path is `go.maximumdirect.net/ckconfig`. Version v0.4.0 requires Go
1.27.0 and uses `go.maximumdirect.net/commandkit v0.4.0` and
`go.maximumdirect.net/configkit v1.1.0`; it does not import PromptKit. See the
[consumer guide](https://gitea.maximumdirect.net/eric/CommandKit/src/branch/main/docs/consumers/guide.md#configkit-scopes)
for exact callback and lifetime contracts.
The module path is `go.maximumdirect.net/ckconfig`. It does not import PromptKit.
See the [consumer guide](https://forge.maximumdirect.net/base-layer/commandkit/src/branch/main/docs/consumers/guide.md#module-selection)
for compatible releases and Go requirements.
## Example
For one application configuration scope, install the convenience declaration:
To inspect one application configuration, pass your loader to `AppCommand`:
```go
app, err := commandkit.New(identity, ckconfig.AppCommand(loadAppConfig))
```
Use `ckconfig.Command(ckconfig.Suite[Input, State]{...})` to define named
scopes, selectors, input checks, and state shared by the selected acquisitions
or comparison in a config invocation. `ckconfig.FromConfigKit` adapts a typed
ConfigKit loader. The `config` command family exposes scope discovery and the
validate, show, sources, explain, and diff operations supported by the declared
scopes. Source reports include native destination, placement, destination
fallback, version evidence, and empty-import selections. Comparison returns
ConfigKit's native records to shared report rendering; the
[CLI reference](https://gitea.maximumdirect.net/eric/CommandKit/src/branch/main/docs/cli.md#compare-configuration-selections)
owns its operator details.
For multiple named configurations or application-specific flags, use
`ckconfig.Command` with a `ckconfig.Suite`. The
[integration guide](https://forge.maximumdirect.net/base-layer/commandkit/src/branch/main/docs/consumers/guide.md#configkit-scopes)
explains loader setup, inputs, and shared state.
The maintained [simple example](examples/simple) shows a complete consumer.
The [CommandKit CLI reference](https://gitea.maximumdirect.net/eric/CommandKit/src/branch/main/docs/cli.md#configuration-scopes)

View File

@@ -1,10 +1,11 @@
// Package ckconfig, provided by go.maximumdirect.net/ckconfig, provides
// CommandKit's optional ConfigKit v1.1.0 integration, requiring Go 1.27.0. Its
// canonical Command declaration installs the standard config command family
// through the core commandkit package. Applications can install CommandKit
// RoutingRule declarations alongside it through commandkit.New or
// App.Register. Source reports preserve ConfigKit's destination, placement,
// version, and empty-import evidence. Invocation reporting applies CommandKit's shared
// visibility and delivery policy; provider warnings remain independent of
// failure explanations. Importing ckconfig alone installs no commands.
// Package ckconfig adds commands to view, validate, explain, and compare
// ConfigKit configuration in CommandKit applications.
//
// Use AppCommand for one application configuration loader, or Command with a
// Suite for multiple named configurations and application-specific flags.
// Install the returned command with commandkit.New or App.Register.
// Importing this package alone installs no commands.
//
// ConfigKit owns configuration loading and interpretation. CommandKit formats
// reports and messages using its shared reporting policy.
package ckconfig

View File

@@ -1,37 +1,90 @@
# Simple consumer
# ConfigKit example
This example imports `go.maximumdirect.net/ckconfig` and
`go.maximumdirect.net/commandkit`; ckconfig v0.4.0 uses core
v0.4.0, ConfigKit v1.1.0, and Go 1.27.0. Set `GOWORK=off GOTOOLCHAIN=local`
for the commands below.
This example adds configuration inspection and a custom `greet` command to a
small application. It uses local fixtures and in-memory defaults, with no
external services. See [module selection](../../../../docs/consumers/guide.md#module-selection)
for dependencies and Go requirements. Use Go 1.27.0 on `PATH` for this checkout.
Run the maintained example with:
From the repository root:
```sh
cd integrations/ckconfig/examples/simple
export GOWORK=off GOTOOLCHAIN=local
go run . config show
go run . config show --config fixtures/before.yml --verbose
go run . config show --config fixtures/before.yml --quiet
```
The output includes the default greeting, `Hello`, and schema version 2.
This example can run against its declared released dependencies with the
workspace disabled.
## Inspect and override a value
```sh
# Discover configuration paths under schema.
go run . config explain --list --under /schema
# Change the greeting for this invocation and show just that value.
go run . config show --path /greeting --set '/greeting="Hi"'
```
The second command prints `Hi`. It does not edit a file.
## Compare configurations
```sh
go run . config diff --config fixtures/before.yml --against-config fixtures/after.yml
```
The report includes `changes.greeting: Goodbye`.
```sh
# Expected status 3: the overridden greeting differs from the ordinary default.
go run . config diff --set '/greeting="Hi"' --details --check
```
This compares the overridden left side with ordinary configuration on the
right. `go run` prints `exit status 3` and itself exits with status 1; a built
executable returns the application's status 3 directly.
## Inspect warnings and failed loads
```sh
# Schema version 1 produces a warning, visible in verbose mode.
go run . config show --config fixtures/before.yml --verbose
# Quiet mode hides the configuration report and warning.
go run . config show --config fixtures/before.yml --quiet
```
Ordinary mode hides the warning but still displays configuration.
```sh
# Intentional failure: show detailed validation findings for the invalid fixture.
go run . config validate --config fixtures/invalid.yml --details --severity error
# Intentional failure: still show the source recorded before loading failed.
go run . config sources --config fixtures/invalid.yml --source-id source-1
```
Both commands retain the load failure. `source-1` belongs to this fixture's
load; other configurations can have different source IDs. See the
[CLI reference](../../../../docs/cli.md#configuration-scopes) for report and status rules.
## Try a custom command
```sh
go run . greet
go run . greet --count=0 --tag=first --tag second
```
The `config` family comes from one typed ConfigKit scope. The custom `greet`
command uses CommandKit's typed bindings and shows the difference between an
omitted `--count` and an explicitly supplied zero, plus ordered repeated tags.
With no `--config`, application settings use the in-memory default. A supplied
configuration and overlay are passed to ConfigKit unchanged. The comparison
command loads the two local fixtures and reports `changes.greeting: Goodbye`.
The before fixture uses schema version 1, which ConfigKit reports as a provider
warning in verbose mode. Ordinary mode hides the warning while preserving the
effective YAML report; quiet mode suppresses that report.
The invalid fixture demonstrates a failed-load report and captured source
inventory; `source-1` is the ID from that fixture's acquisition. The scope opts
in to transient overrides and passes them into its normal ConfigKit load. The
diff example compares an overridden left side with ordinary configuration on
the right and returns status 3 because the values differ.
The handler reports its greeting, count, and tags. It distinguishes an omitted
count from an explicitly supplied zero and preserves repeated tag order.
## How it is wired
One ConfigKit scope supplies the standard `config` commands. Without `--config`,
its loader uses an in-memory default. Supplied configuration and overlay values
are passed to ConfigKit unchanged. The scope enables temporary overrides and
passes them into its ordinary ConfigKit load. The
[consumer guide](../../../../docs/consumers/guide.md#configkit-scopes) explains
loader and scope setup.
Run `GOWORK=off GOTOOLCHAIN=local go test ./...` from this example directory to
check it against its declared dependencies.

View File

@@ -73,13 +73,13 @@ func newConfigOperationCommand[I, S any](prepared *preparedSuite[I, S], operatio
scopeChoices = append(scopeChoices, commandkit.CompletionChoice{Value: "all"})
}
flags := []commandkit.Flag[*configCommandInput[I, S]]{
commandkit.ValueFlag(commandkit.FlagMeta{Name: "scope", Usage: "Configuration scope name or all where supported.", Completion: commandkit.CompleteChoices(scopeChoices...)}, &defaultScope, parseConfigString,
commandkit.ValueFlag(commandkit.FlagMeta{Name: "scope", Usage: "Select a named configuration, or all where supported.", Completion: commandkit.CompleteChoices(scopeChoices...)}, &defaultScope, parseConfigString,
func(input **configCommandInput[I, S], value commandkit.Supplied[string]) { (*input).scope = value }),
commandkit.ValueFlag(commandkit.FlagMeta{Name: "config", Usage: "Configuration source.", Completion: commandkit.CompleteFiles()}, (*string)(nil), parseConfigString,
commandkit.ValueFlag(commandkit.FlagMeta{Name: "config", Usage: "Select the configuration source.", Completion: commandkit.CompleteFiles()}, (*string)(nil), parseConfigString,
func(input **configCommandInput[I, S], value commandkit.Supplied[string]) {
(*input).selection.Config = value
}),
commandkit.ValueFlag(commandkit.FlagMeta{Name: "overlay", Usage: "Overlay source.", Completion: commandkit.CompleteFiles()}, (*string)(nil), parseConfigString,
commandkit.ValueFlag(commandkit.FlagMeta{Name: "overlay", Usage: "Select the configuration overlay.", Completion: commandkit.CompleteFiles()}, (*string)(nil), parseConfigString,
func(input **configCommandInput[I, S], value commandkit.Supplied[string]) {
(*input).selection.Overlay = value
}),
@@ -89,11 +89,11 @@ func newConfigOperationCommand[I, S any](prepared *preparedSuite[I, S], operatio
}
if operation == Validate {
flags = append(flags,
commandkit.BoolFlag(commandkit.FlagMeta{Name: "details", Usage: "Show native validation findings."}, false, func(input **configCommandInput[I, S], value commandkit.Supplied[bool]) { (*input).details = value }),
commandkit.BoolFlag(commandkit.FlagMeta{Name: "details", Usage: "Show detailed validation findings."}, false, func(input **configCommandInput[I, S], value commandkit.Supplied[bool]) { (*input).details = value }),
commandkit.RepeatFlag(commandkit.FlagMeta{Name: "severity", Usage: "Filter detailed findings by error or warning."}, []configkit.Severity(nil), parseValidationSeverity, func(input **configCommandInput[I, S], value commandkit.Supplied[[]configkit.Severity]) {
(*input).severities = value.Value
}),
commandkit.RepeatFlag(commandkit.FlagMeta{Name: "code", Usage: "Filter detailed findings by exact native code."}, []string(nil), parseConfigString, func(input **configCommandInput[I, S], value commandkit.Supplied[[]string]) {
commandkit.RepeatFlag(commandkit.FlagMeta{Name: "code", Usage: "Filter detailed findings by exact diagnostic code."}, []string(nil), parseConfigString, func(input **configCommandInput[I, S], value commandkit.Supplied[[]string]) {
(*input).codes = value.Value
}),
)
@@ -114,16 +114,16 @@ func newConfigOperationCommand[I, S any](prepared *preparedSuite[I, S], operatio
flags = append(flags,
commandkit.ValueFlag(commandkit.FlagMeta{Name: "path", Usage: "JSON Pointer path to explain."}, (*string)(nil), parseConfigString, func(input **configCommandInput[I, S], value commandkit.Supplied[string]) { (*input).path = value }),
commandkit.BoolFlag(commandkit.FlagMeta{Name: "list", Usage: "List configuration paths."}, false, func(input **configCommandInput[I, S], value commandkit.Supplied[bool]) { (*input).list = value }),
commandkit.BoolFlag(commandkit.FlagMeta{Name: "historical", Usage: "Include historical-only paths when listing."}, false, func(input **configCommandInput[I, S], value commandkit.Supplied[bool]) { (*input).historical = value }),
commandkit.BoolFlag(commandkit.FlagMeta{Name: "historical", Usage: "Include paths that exist only in configuration history."}, false, func(input **configCommandInput[I, S], value commandkit.Supplied[bool]) { (*input).historical = value }),
commandkit.ValueFlag(commandkit.FlagMeta{Name: "under", Usage: "List paths under a JSON Pointer."}, (*string)(nil), parseConfigString, func(input **configCommandInput[I, S], value commandkit.Supplied[string]) { (*input).under = value }),
commandkit.BoolFlag(commandkit.FlagMeta{Name: "explicit", Usage: "Filter listed current paths by explicitness."}, false, func(input **configCommandInput[I, S], value commandkit.Supplied[bool]) { (*input).explicit = value }),
commandkit.BoolFlag(commandkit.FlagMeta{Name: "explicit", Usage: "Filter current paths by whether ConfigKit marks them explicit."}, false, func(input **configCommandInput[I, S], value commandkit.Supplied[bool]) { (*input).explicit = value }),
)
}
if operation == Show {
flags = append(flags, commandkit.ValueFlag(commandkit.FlagMeta{Name: "path", Usage: "JSON Pointer to one effective value."}, (*string)(nil), parseConfigString, func(input **configCommandInput[I, S], value commandkit.Supplied[string]) { (*input).path = value }))
}
if operation == Sources {
flags = append(flags, commandkit.ValueFlag(commandkit.FlagMeta{Name: "source-id", Usage: "Select one source ID from this acquisition."}, (*string)(nil), parseConfigString, func(input **configCommandInput[I, S], value commandkit.Supplied[string]) { (*input).sourceID = value }))
flags = append(flags, commandkit.ValueFlag(commandkit.FlagMeta{Name: "source-id", Usage: "Show one source ID from this configuration load."}, (*string)(nil), parseConfigString, func(input **configCommandInput[I, S], value commandkit.Supplied[string]) { (*input).sourceID = value }))
}
flags = append(flags, commandkit.CaptureFlags(prepared.bindingsByOperation[operation], func(input **configCommandInput[I, S]) *commandkit.FlagValues[I] {
if input == nil || *input == nil {
@@ -160,7 +160,7 @@ func newConfigOperationCommand[I, S any](prepared *preparedSuite[I, S], operatio
func comparisonOperationFlags[I, S any]() []commandkit.Flag[*configCommandInput[I, S]] {
return []commandkit.Flag[*configCommandInput[I, S]]{
commandkit.BoolFlag(commandkit.FlagMeta{Name: "details", Usage: "Show native before and after difference evidence."}, false, func(input **configCommandInput[I, S], value commandkit.Supplied[bool]) { (*input).details = value }),
commandkit.BoolFlag(commandkit.FlagMeta{Name: "details", Usage: "Show before and after values with presence and redaction details."}, false, func(input **configCommandInput[I, S], value commandkit.Supplied[bool]) { (*input).details = value }),
commandkit.BoolFlag(commandkit.FlagMeta{Name: "check", Usage: "Return status 3 when differences are found."}, false, func(input **configCommandInput[I, S], value commandkit.Supplied[bool]) { (*input).check = value }),
commandkit.ValueFlag(commandkit.FlagMeta{Name: "against-config", Usage: "Configuration source for the right side.", Completion: commandkit.CompleteFiles()}, (*string)(nil), parseConfigString,
func(input **configCommandInput[I, S], value commandkit.Supplied[string]) {
@@ -191,7 +191,7 @@ func operationDeclaration(operation Operation) (string, string) {
case Explain:
return "explain", "Explain a configuration path."
case Diff:
return "diff", "Compare two configuration selections."
return "diff", "Compare two configurations."
default:
return string(operation), "Inspect configuration."
}