Files

319 lines
16 KiB
Markdown
Raw Permalink Normal View History

# Consumer guide
CommandKit is distributed as three Go modules. Start with the core and add an
integration module only when the application uses that library. Core and
integration command factories return canonical `commandkit.Command` values.
Applications may install those alongside application-owned `RoutingRule`
declarations through `commandkit.New` or `App.Register`; routing rules are
installed at the application root, not as group children.
## Module selection
| Import | Use when |
| --- | --- |
| `gitea.maximumdirect.net/eric/CommandKit` | The application needs typed command declarations and common CLI behavior. |
| `gitea.maximumdirect.net/eric/CommandKit/integrations/ckconfig` | The application wants ConfigKit-backed configuration scopes and reports. |
| `gitea.maximumdirect.net/eric/CommandKit/integrations/ckprompt` | The application wants PromptKit engine configuration/comparison, five catalogs, named inspection, requirements discovery, request validation, or native smoke testing. |
2026-10-09 03:48:28 +00:00
The two integrations are independent optional imports. `ckprompt` uses the
public scope and error contracts from `ckconfig`; importing it does not add the
`config` family. An application that wants both families passes both returned
declarations to core. The [maintained examples](../../README.md#examples) show
core-only, ConfigKit-only, and composed applications.
2026-10-09 03:48:28 +00:00
## Go compatibility
2026-10-09 03:48:28 +00:00
All three modules declare a consumer minimum of Go 1.26.0. The root module also
selects ReleaseKit v0.4.2 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](../policy/release.md#consumer-minimum-and-release-toolchain)
defines the support assumption. Maintainers use Go 1.26.8 for validation and
publication; see the [release procedure](../release.md#release-toolchain).
## 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.
```go
package example
import (
"fmt"
"strconv"
commandkit "gitea.maximumdirect.net/eric/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 {
if _, err := fmt.Fprintln(invocation.Reporter().Output(), input.Count.Value); err != nil {
return commandkit.Failure("Could not write the greeting.").WithCause(err)
}
return commandkit.Success()
},
})
}
```
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.
## Invocation reporting
`Invocation.Reporter()` supplies the reporter for the current handler. Use
`Output()` or `RenderOutput()` for substantive results; use `Warn`, `Info`,
`Progress`, and `Debug` for categorized messages. Queue a summary with
`AtEnd().Info` when it belongs after the handler returns. The
[CLI reference](../cli.md#reporting) owns flag grammar, visibility, prefixes,
and status behavior.
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.
```go
Run: func(invocation commandkit.Invocation, input GreetInput) commandkit.Outcome {
if _, err := fmt.Fprintln(invocation.Reporter().Output(), input.Count.Value); err != nil {
return commandkit.Failure("Could not write the greeting.").WithCause(err)
}
return commandkit.Success()
}
```
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.
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.
### Migrating from the previous reporting API
| Previous API | Current API |
| --- | --- |
| `invocation.Streams.Out` / `.Err` | `invocation.Reporter().Output()` and categorized reporter methods. `Streams` remains the argument to `App.Execute`. |
| `Failure(err)` / `Usage(err)` | `Failure("operator-facing explanation").WithCause(err)` / `Usage("usage explanation").WithCause(err)`. Choose the explanation deliberately; the cause alone is not printed. |
| `Outcome.Diagnostics` or terminal `SeverityError` | Submit independent messages through reporter methods. Put failure context in `Explanation`; provider `Acquired.Diagnostics` remains passive data for the owning integration to submit. |
| `ReportIncomplete`, `DiagnosticDelivery`, or `DeliveryError` | Inspect `Outcome.Presentation.Output` and `.Diagnostics`, including each `DeliveryResult.State`, `RenderError`, and `WriteError`. |
## 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:
```go
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:
```go
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](../../examples/core) and
[composed consumer](../../integrations/ckprompt/examples/composed) show complete
maintained wiring with ordinary command behavior alongside aliases. The
[Go declarations and GoDoc](../../command.go) own the public API contracts;
the [CLI reference](../cli.md#application-owned-aliases) 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:
```go
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. Each comparison side gets its own initialized
and checked input, while 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](../../integrations/ckconfig/contracts.go) 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 `config` family provides scope discovery, validation, native effective YAML,
source inventory, path explanation, and ConfigKit-backed comparison for the
capabilities declared by its scopes. The [CLI reference](../cli.md#configuration-scopes)
owns operator syntax and report details.
2026-10-12 22:25:43 +00:00
## 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.
`ckprompt.FromEngine(loadEngine)` supplies eight callbacks: prompt, profile,
backend, namespace, and collection catalogs; named prompt and profile
inspection; and native requirements discovery. Concrete request validation and
smoke testing are separate capabilities, added explicitly with
`ckprompt.WithRequestValidation` and `ckprompt.WithSmokeTesting`. The suite has
ten independently optional callbacks in total. `ckprompt.Command(suite)` creates
only operations whose callbacks are present; discovery, validation, and smoke
testing can be installed independently. A composed suite can enable both
additional operations:
2026-10-12 22:25:43 +00:00
```go
suite := ckprompt.FromEngine(loadEngine)
suite = ckprompt.WithRequestValidation(suite, loadEngine, buildRequest)
suite = ckprompt.WithSmokeTesting(suite, loadEngine, nil)
2026-10-12 22:25:43 +00:00
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:
```go
suite := ckprompt.WithRequestValidation(
ckprompt.Suite[Input, State]{}, loadEngine, nil,
)
validationCommand := ckprompt.Command(suite)
```
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 eight catalog and inspection callbacks and the two explicit additions are
independently optional. For catalogs without named or requirements inspection,
narrow the suite before command capture:
```go
definitions := ckprompt.FromEngine(loadEngine)
definitions.InspectPrompt = nil
definitions.InspectProfile = nil
definitions.InspectRequirements = nil
definitionCommand := ckprompt.Command(definitions)
```
Each selected inspection, requirements, validation, smoke, or catalog operation
2026-10-12 22:25:43 +00:00
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](../../integrations/ckprompt/adapters.go) for factory
timing, omitted-factory zero values, and caller-owned objects and effects.
The loader receives invocation context, explicitly supplied selection, input,
2026-10-12 22:25:43 +00:00
and state. `FromEngine` uses PromptKit's public listing and inspection methods,
including `Engine.InspectRequirements`; validation must be opted into separately
and uses `Engine.ValidateRequest`.
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](../cli.md#promptkit-smoke-testing),
[definition inspection](../cli.md#promptkit-definition-inspection), and
[requirements contract](../cli.md#promptkit-requirements) own operator syntax
and reports.
The [composed example](../../integrations/ckprompt/examples/composed/README.md)
combines app and PromptKit config scopes, comparison and catalogs, named and
requirements inspection, request validation, smoke testing, and an
application-owned `session restore` command.
## 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 v0.8.0 inspection API](https://gitea.maximumdirect.net/eric/ConfigKit/src/commit/137769f3f18a370c68444242fb5d88e620e307e5/inspection.go),
[PromptKit engine methods](https://gitea.maximumdirect.net/eric/promptkit/src/commit/9178f3eb91c2b2138caa78e22be93c048b4bc992/engine.go),
[configuration inspection](https://gitea.maximumdirect.net/eric/promptkit/src/commit/9178f3eb91c2b2138caa78e22be93c048b4bc992/configuration_inspection.go),
[smoke diagnostics](https://gitea.maximumdirect.net/eric/promptkit/src/commit/9178f3eb91c2b2138caa78e22be93c048b4bc992/smoke_diagnostics.go),
and [PromptKit v0.16.0 consumer guidance](https://gitea.maximumdirect.net/eric/promptkit/src/commit/9178f3eb91c2b2138caa78e22be93c048b4bc992/docs/consumers/pkg-promptkit.md).