23 KiB
Consumer guide
Start with core to build commands with typed inputs and shared output formatting. Add an integration when your application uses ConfigKit or PromptKit. The core command example below shows the basic API; the maintained examples show complete applications.
Module selection
| Import | Use when |
|---|---|
go.maximumdirect.net/commandkit |
You need commands, typed inputs, help, and common output handling. |
go.maximumdirect.net/ckconfig |
You want operators to view, validate, explain, and compare ConfigKit configuration. |
go.maximumdirect.net/ckprompt |
You want operators to inspect PromptKit resources, check and preview requests, or run smoke tests. |
The following lists the current release set and earlier module combinations:
| Selection | Core | ckconfig | ckprompt | Upstream libraries and status |
|---|---|---|---|---|
| Published core | v0.5.0 | — | — | Usable independently. |
| ConfigKit integration | v0.5.0 | v0.5.0 | — | ConfigKit v1.1.0. |
| PromptKit integration | v0.5.0 | v0.5.0 | v0.5.0 | ConfigKit v1.1.0 and PromptKit v0.20.0. |
| Earlier matched integration set | v0.3.0 | v0.2.0 | v0.2.0 | ConfigKit v0.8.0 and PromptKit v0.16.0, using their earlier import paths. |
Install the published core with:
go get go.maximumdirect.net/commandkit@v0.5.0
Select either integration with go get go.maximumdirect.net/ckconfig@v0.5.0
or go get go.maximumdirect.net/ckprompt@v0.5.0. The integrations select core
v0.5.0, and ckprompt selects ckconfig v0.5.0, which includes AdaptDiagnostics.
They build independently against these released dependencies; a local workspace
is optional for development. Published ckconfig v0.3.0 also uses ConfigKit v1.1.0
and does not compose with ckprompt v0.2.0's earlier ConfigKit dependency.
Importing a module does not install its commands. Pass commands to
commandkit.New or App.Register explicitly. ckprompt depends on ckconfig's
public APIs, but you can install its promptkit commands without installing
the config family.
Go compatibility
Core declares a consumer minimum of Go 1.26.0; both current integration
modules require Go 1.27.0. The root module also
selects ReleaseKit v0.9.0 (go.maximumdirect.net/releasekit) as a Go tool, which
makes that tool's module graph part of the root consumer module requirements
even though production packages do not import ReleaseKit. The
release policy
defines the support assumption. Maintainers use Go 1.27.0 for validation and
publication; see the release procedure.
Core commands
Declare typed flags and operands with CommandSpec[I], then capture them with
NewCommand. Bindings deliver Supplied[T], which distinguishes an omitted
default from an explicitly supplied empty, false, or zero value.
package example
import (
"strconv"
commandkit "go.maximumdirect.net/commandkit"
)
type GreetInput struct {
Count commandkit.Supplied[int]
}
func Greeting() commandkit.Command {
countDefault := 1
return commandkit.NewCommand(commandkit.CommandSpec[GreetInput]{
Name: "greet", Summary: "Write a greeting.",
Flags: []commandkit.Flag[GreetInput]{
commandkit.ValueFlag(
commandkit.FlagMeta{Name: "count", Usage: "Number of greetings."},
&countDefault, strconv.Atoi,
func(input *GreetInput, value commandkit.Supplied[int]) {
input.Count = value
},
),
},
Run: func(invocation commandkit.Invocation, input GreetInput) commandkit.Outcome {
greetings := []string{}
for i := 0; i < input.Count.Value; i++ {
greetings = append(greetings, "Hello!")
}
if err := invocation.Reporter().Report(commandkit.Report{Value: greetings}); err != nil {
return commandkit.Failure("Could not write the greetings.").WithCause(err)
}
return commandkit.Success()
},
})
}
Install Greeting() with commandkit.New(identity, Greeting()). By default,
greet prints Hello!; greet --count=2 prints it twice. Add --format json to receive a JSON list. A count of zero produces an empty list.
The application supplies context.Context, argv without the executable name,
and output streams to App.Execute; it decides how the returned status maps to
process exit. New installs generated help/version and caller declarations as
one batch. Register validates each call as a batch before changing that App.
Invalid declaration errors retain their wrapped identity for errors.Is and
errors.As.
Declarations copy their metadata and slice inputs when captured, retain callback
references, and can be reused across applications. Copies of a typed binding
preserve its identity; a separately constructed lookalike does not. Supplied
values convert before input construction. A command's NewInput creates input
only when execution reaches it; help does not construct caller input or run
provider callbacks.
Shell completion
Install commandkit.Completion("app") alongside the application's other
declarations, using the basename operators actually type. The basename accepts
letters, digits, underscores, dots, hyphens, and colons, except for a leading
hyphen. Add a hint to a flag or operand's metadata when a value has useful
static choices or is a filesystem path:
commandkit.FlagMeta{
Name: "view",
Completion: commandkit.CompleteChoices(
commandkit.CompletionChoice{Value: "text", Description: "Readable text"},
commandkit.CompletionChoice{Value: "yaml", Description: "YAML output"},
),
}
commandkit.FlagMeta{Name: "config", Completion: commandkit.CompleteFiles()}
commandkit.ArgumentMeta{Name: "directory", Completion: commandkit.CompleteDirectories()}
Hints are suggestions: converters and input validation still decide which
values are accepted. An omitted hint or NoCompletion() gives no value
suggestions. CompleteChoices() with no choices also gives no suggestions.
Declarations retain copies of supplied choices. The CLI reference
describes supported shells and loading scripts; the core example
and composed example show
installation alongside aliases and integration commands.
Invocation reporting
Use Invocation.Reporter().Report to print a value in the selected output
format. Pass ordinary Go values and, optionally, a text/template for text
output. Use Warn, Info, Progress, and Debug for categorized messages.
Queue a summary with AtEnd().Info when it belongs after the handler returns.
Deferred submissions are accepted until finalization begins; later submissions
return ErrReporterClosed. The CLI reference owns flag
grammar, visibility, prefixes, and status behavior.
An application can attach a boolean input to the shared --debug option and
inspect the reporting selection separately. For example:
type input struct { debug commandkit.Supplied[bool] }
command := commandkit.NewCommand(commandkit.CommandSpec[input]{
Name: "run",
Flags: []commandkit.Flag[input]{
commandkit.BoolFlag(commandkit.FlagMeta{Name: "debug", Short: "d"}, false,
func(in *input, value commandkit.Supplied[bool]) { in.debug = value }),
},
Run: func(inv commandkit.Invocation, in input) commandkit.Outcome {
captureArtifacts := in.debug.Value
quiet := inv.ReportingMode() == commandkit.ReportingModeQuiet
_, _ = captureArtifacts, quiet // Apply application policy here.
return commandkit.Success()
},
})
_ = command
With run --debug --quiet, the input is true and explicitly supplied, while
the selected reporting mode is quiet. Application defaults and requiredness
affect input, not mode selection; explicit false counts as supplied. Captured
bindings retain the usual selection and application behavior. Applications own
any artifact creation and other effects. The mode reports selection rather than
successful delivery; see Invocation.ReportingMode.
Reports use JSON serialization to establish the common value representation
for YAML and JSON. A text template receives the original value; without one,
text uses readable scalar or string-list output and YAML for structured values.
The CLI's shared format selection and the writer-level RenderReport use the
same rules. Use DecodeYAMLReport only for safe upstream inspection YAML that
must become a report value; ordinary strings are not parsed as YAML.
Return terminal text with Failure(message, details...), Usage(message, details...), or WithExplanation on a domain outcome. Attach an optional Go
error separately with WithCause. A cause is preserved for inspection, but it
does not choose the outcome or become user-facing text. Rendering callbacks
write to their supplied writer only while the callback runs; they must not
submit another message or start another rendering operation through the same
reporter.
For example, a handler can report a count as a small mapping:
if err := invocation.Reporter().Report(commandkit.Report{
Value: map[string]int{"count": input.Count.Value},
}); err != nil {
return commandkit.Failure("Could not write the count.").WithCause(err)
}
return commandkit.Success()
This uses YAML automatically and supports --format json without a separate
encoder. Use Output() or RenderOutput() instead when you deliberately need
raw output; --format does not reformat those bytes.
After App.Execute, inspect the returned Outcome. Its Cause retains the
operation error; Presentation.Output and Presentation.Diagnostics report
delivery state and the first rendering or write errors for their destinations.
Joined operation errors support errors.Is and errors.As where the
underlying errors do. CommandKit does not retain a message transcript.
Reporter operations for one invocation are safe concurrently, but concurrent
submission order is not specified. The caller owns the writers passed to
Execute; CommandKit does not close them.
Application-owned aliases
Pass commands and aliases together as declarations. Integration factories can be called directly in the installation list, and command-scoped flag aliases name the canonical command path:
app, err := commandkit.New(
identity,
ckprompt.Command(suite),
commandkit.Alias("pk", "promptkit"),
commandkit.FlagAliasFor("promptkit show prompt", "--fmt", "--format"),
)
For a reusable collection, use []commandkit.Declaration. Go does not expand a
[]commandkit.Command into ...commandkit.Declaration, so convert command-only
slices when composing them with routing rules:
commands := []commandkit.Command{ckprompt.Command(suite)}
declarations := make([]commandkit.Declaration, 0, len(commands)+1)
for _, command := range commands {
declarations = append(declarations, command)
}
declarations = append(declarations, commandkit.Alias("pk", "promptkit"))
app, err := commandkit.New(identity, declarations...)
New and Register validate each supplied batch before publishing its commands and routing rules. Factories capture their inputs eagerly; malformed or conflicting rules are reported at installation. Identical rules are safe to contribute from reusable helpers. The core example and composed consumer show complete maintained wiring with ordinary command behavior alongside aliases. The Go declarations and GoDoc own the public API contracts; the CLI reference owns routing and operator-visible behavior.
ConfigKit scopes
For an application with one ordinary app configuration loader,
ckconfig.AppCommand supplies the state-free convenience declaration:
app, err := commandkit.New(
commandkit.Identity{Name: "Example", Version: "dev"},
ckconfig.AppCommand(loadAppConfig),
)
loadAppConfig has the signature
func(context.Context, ckconfig.Selection) (*configkit.Result[T], error).
Construction, installation, and help do not call it. Selection preserves
whether config and overlay paths were explicitly supplied, including empty
values.
Use ckconfig.Command(ckconfig.Suite[I, S]{...}) when the application needs
multiple named scopes, selector flags, scope-aware input checks, or shared
acquisition state or a custom comparison provider. A declared Diff
capability uses Scope.Compare; ckconfig.FromConfigKit and AppCommand
provide the ConfigKit-backed implementation. Selector.AgainstName opts a
selector into an independent right-side flag. Each comparison side gets its
own initialized and checked input, with only its supplied selectors applied;
state is shared by the invocation. Factory results remain caller-owned, so
separate initialization calls do not guarantee distinct objects. See
ckconfig.Suite's GoDoc for factory
timing, omitted-factory zero values (including nil pointers), and ownership.
The application owns the state type and what it caches.
ckconfig.FromConfigKit adapts a typed ConfigKit loader without calling it
during construction. The bundled comparison adapters call the loader for the
left side before the right, retain left diagnostics if right loading fails,
and collect diagnostics from both loads before native comparison. Custom
comparison callbacks own their loading and diagnostics; loader calls need not
reread sources if the application caches them.
The config family provides scope discovery, validation, safe effective configuration display,
source inventory, path explanation, and ConfigKit-backed comparison for the
capabilities declared by its scopes. The CLI reference
owns operator syntax and report details. Scope.Features declares optional modes; acquired
View.Metadata and View.Findings carry native path metadata and diagnostic
records when available. Findings.Messages contains presentations associated
with those records, while Acquired.Diagnostics carries independent messages.
Custom explanation providers implement Explain; CommandKit renders the
acquired evidence. See EffectiveWriter and ExplanationReader in the
ckconfig GoDoc for the provider boundary.
ckconfig.AdaptDiagnostics captures native diagnostic presentation evidence
for applications providing their own ConfigKit-backed CLI integration.
Set Scope.Features.Overrides only when the loader passes
Selection.Overrides to ConfigKit as part of its normal load. The integration
parses CLI records but cannot add them to an already-loaded result. The
CLI reference describes the operator syntax.
PromptKit configuration, definitions, and requirements
ckprompt.ConfigScope(loadEngine) returns a ckconfig.Scope for the PromptKit
engine's captured configuration. Add that scope to a ckconfig.Suite to expose
it under config with validate/show/sources/explain and native diff operations;
it does not install PromptKit catalog, definition, or requirements commands.
Its override feature is disabled because the released PromptKit configuration
loader does not accept ConfigKit override records.
Install resource commands and optional checks
ckprompt.FromEngine(loadEngine) supplies nine callbacks: prompt, profile,
backend, namespace, and collection catalogs; named prompt, profile, and backend
inspection; and native requirements discovery. Concrete request validation,
prepared-request preview, and smoke testing are separate capabilities, added
with ckprompt.WithRequestValidation, ckprompt.WithRequestPreparation, and
ckprompt.WithSmokeTesting. The suite has twelve independently optional
callbacks in total. ckprompt.Command(suite) creates only operations whose
callbacks are present, with canonical flags. ckprompt.Declarations(suite)
returns the command and its scoped convenience aliases for installation with
other declarations. These capabilities can be installed independently. A
composed suite can enable the additional operations:
suite := ckprompt.FromEngine(loadEngine)
suite = ckprompt.WithRequestValidation(suite, loadEngine, buildRequest)
suite = ckprompt.WithRequestPreparation(suite, loadEngine, nil)
suite = ckprompt.WithSmokeTesting(suite, loadEngine, nil)
suite.NewInput = newInput
suite.NewState = newState
suite.Selectors = selectors
definitionCommand := ckprompt.Command(suite)
A validation-only suite can start empty. With application types named Input
and State, a nil builder forwards the request fields populated by standard
flags directly to PromptKit:
suite := ckprompt.WithRequestValidation(
ckprompt.Suite[Input, State]{}, loadEngine, nil,
)
validationCommand := ckprompt.Command(suite)
Request builders and loading
If the loader is nil, the returned suite retains a wiring issue for installation to report, even if capabilities are narrowed later. A builder runs before the engine loader and receives the seed request, selection, typed input, shared state, and invocation context. It returns the concrete request; the adapter does not merge seed fields back afterward. The loader receives the original selection. Builders should start from the seed, preserve operator values and bindings, and apply defaults only when a map key is absent, so explicitly supplied empty values stay meaningful. Applications that validate and then execute a request are encouraged to share their request construction logic between those paths.
The catalog and inspection callbacks and the explicit additions are independently optional. For catalogs without named or requirements inspection, narrow the suite before command capture:
definitions := ckprompt.FromEngine(loadEngine)
definitions.InspectPrompt = nil
definitions.InspectProfile = nil
definitions.InspectBackend = nil
definitions.InspectRequirements = nil
definitionCommand := ckprompt.Command(definitions)
Each selected inspection, requirements, validation, preparation, smoke, or catalog operation
prepares one input, applies its captured selectors, checks that input, and
initializes one state value when those steps are reached. The bundled validation
adapter delegates concrete request preparation to PromptKit and carries its
native errors plus engine-loading diagnostics through the acquisition. It does
not call the model or retain a prepared execution snapshot. See
ckprompt.Suite's GoDoc for factory
timing, omitted-factory zero values, and caller-owned objects and effects.
The loader receives invocation context, explicitly supplied selection, input,
and state. FromEngine uses PromptKit's public listing and inspection methods,
including Engine.InspectRequirements; validation must be opted into separately
and uses Engine.ValidateRequest.
ckprompt.WithRequestPreparation adds a separate callback backed by
Engine.Prepare. Its optional builder receives the seeded request and returns
the complete request to prepare. The resulting native report can contain
rendered application content. See the CLI reference
for operator behavior.
Application-owned commands can use ckprompt.NewExecutionFlags,
ckprompt.NewResourceFlags, and ckprompt.NewPromptInputFlags to bind the
same canonical request flags into their own input types. Each collection exposes
individual handles and an All method, so a command can choose its applicable
subset. The application decides how to apply the resulting values to its request.
Enable namespace flags
ckprompt.WithNamespaces enables standard resource namespace selection from
application-supplied defaults. Loaders used with this option should leave view
selection to the adapter. Without it, a loader can supply its own engine view.
Direct callbacks receive the composed selection in their request. The
CLI reference lists applicable flags.
Catalog, named definition inspection, and requirements discovery do not render
messages or load request artifacts. Concrete request validation follows native
PromptKit preparation, which can resolve prompt/profile resources, load
artifacts and schemas, render messages, and perform applicable configuration
and credential checks. It does not make a model call or certify that a later
execution will succeed. Application loaders and direct callbacks retain their
own effects. Smoke testing explicitly invokes one native PromptKit diagnostic
or all four in order. Native inference checks use the configured public
promptkit.LLMClient; applications retain ownership of client and probe
configuration. A builder may customize the seeded native request, and its
complete return value is authoritative. The CLI reference,
definition inspection, and
requirements contract own operator syntax
and reports.
The composed example
combines app and PromptKit config scopes, comparison and catalogs, named and
requirements inspection, request validation, smoke testing, and an
application-owned session restore command.
Namespace views
An application can return a scoped PromptKit view from its own engine loader:
func loadScopedEngine(ctx context.Context, selection ckconfig.Selection, input Input, state State) (*promptkit.Engine, error) {
engine, err := loadEngine(ctx, selection, input, state)
if err != nil {
return nil, err
}
return engine.WithNamespaces(promptkit.Namespaces{
Prompt: "editorial", Profile: "team", Backend: "providers",
})
}
These are independent defaults for unqualified caller-supplied prompt, profile,
and backend names; qualified IDs pass through. Stored references and configuration
inspection are unchanged, while namespace and collection inventories remain global.
Catalog --namespace filters are separate from these defaults. See the released
namespace API.
Outcomes and ownership
Core classifies usage and operational failures while preserving application
domain outcomes and supported error identity. Applications choose domain
classification and status. A terminal explanation is presented separately from
the optional operation cause, while report and diagnostic delivery observations
are available in Outcome.Presentation. The caller owns process startup,
signal handling, and exit mapping; Execute never exits the process.
Go declarations and GoDoc own exact exported contracts. ConfigKit owns configuration decoding, validation, and provenance. PromptKit owns prompt formats, profile resolution, and inspection details. See the ConfigKit v1.1.0 source records, PromptKit engine methods, configuration inspection, smoke diagnostics, and PromptKit v0.20.0 consumer guidance.