From 0183022a28a1c6574c5e964171f30efd1bf39624 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sat, 7 Nov 2026 04:27:28 +0000 Subject: [PATCH] Add scoped PromptKit flag aliases --- docs/cli.md | 13 ++ docs/consumers/guide.md | 4 +- docs/roadmap/implementation.md | 7 +- integrations/ckprompt/command.go | 28 ++-- integrations/ckprompt/declarations.go | 31 +++++ integrations/ckprompt/declarations_test.go | 123 ++++++++++++++++++ .../ckprompt/examples/composed/README.md | 4 +- .../ckprompt/examples/composed/main.go | 4 +- integrations/ckprompt/namespaces.go | 14 +- integrations/ckprompt/preparation.go | 4 +- integrations/ckprompt/request_flags.go | 13 +- integrations/ckprompt/smoke_command.go | 12 +- integrations/ckprompt/validation_command.go | 18 ++- 13 files changed, 237 insertions(+), 38 deletions(-) create mode 100644 integrations/ckprompt/declarations.go create mode 100644 integrations/ckprompt/declarations_test.go diff --git a/docs/cli.md b/docs/cli.md index caab668..cb9c411 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -574,6 +574,19 @@ controls the engine view and these flags are unavailable. Catalog namespace filters remain separate; configuration inspection has no resource namespace selectors. +### PromptKit convenience aliases + +Applications that install `ckprompt.Declarations(suite)` provide scoped +convenience spellings for the enabled PromptKit commands. Each applicable +`--llm-NAME` flag also accepts `--NAME`; `--llm-input-text` additionally accepts +`--llm-input` and `--input` for literal text. Use `--llm-input-file` or +`--input-file` for a file reference. Canonical and alias spellings share the +same binding, including their occurrence order and explicit-value semantics. + +Applications that install `ckprompt.Command(suite)` receive the canonical +spellings only. The aliases do not apply to application-owned commands unless +the application installs them separately. + ## PromptKit prepared-request preview When `Suite.PrepareRequest` is present, `ckprompt.Command(suite)` installs diff --git a/docs/consumers/guide.md b/docs/consumers/guide.md index b7e959b..f97910d 100644 --- a/docs/consumers/guide.md +++ b/docs/consumers/guide.md @@ -265,7 +265,9 @@ 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; these capabilities can be installed independently. A +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: ```go diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 4d3e877..e54d53f 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -1,6 +1,6 @@ # Shared reporting and PromptKit v0.20.0 implementation plan -Status: implementation in progress; Stages 1–12 complete. +Status: implementation in progress; Stages 1–13 complete. ## Scope, authority, and execution @@ -466,6 +466,11 @@ namespace membership or requirements-analysis correctness. ## Stage 13: Generated scoped aliases and complete-family installation +**Complete.** `Declarations` generates aliases from each command's selected +bindings; `Command` remains canonical-only. The root, ckconfig, and ckprompt +workspace suites passed with Go 1.27.0, including alias routing, occurrence +order, optional commands, help, completion, and conflict checks. + Add `Declarations(suite) []commandkit.Declaration`, assembling the canonical family and generated exact-command `FlagAliasFor` rules. Retain `Command(suite)` for canonical-only installation. Generate aliases from the selected flags and diff --git a/integrations/ckprompt/command.go b/integrations/ckprompt/command.go index 5d1692a..085b021 100644 --- a/integrations/ckprompt/command.go +++ b/integrations/ckprompt/command.go @@ -63,6 +63,10 @@ type inputAssignment struct { // Named resource inspections return native reports through shared formatting. // Catalog commands accept config/overlay selection and suite selectors. func Command[I, S any](suite Suite[I, S]) commandkit.Command { + return buildCommand(suite, nil) +} + +func buildCommand[I, S any](suite Suite[I, S], aliases *[]commandkit.Declaration) commandkit.Command { if suite.InspectPrompt == nil && suite.InspectProfile == nil && suite.InspectBackend == nil && suite.InspectRequirements == nil && suite.ValidateRequest == nil && suite.PrepareRequest == nil && suite.SmokeTest == nil && suite.ListPrompts == nil && suite.ListProfiles == nil && suite.ListBackends == nil && suite.ListNamespaces == nil && suite.ListCollections == nil { command := commandkit.NoCommand() for _, issue := range suite.issues { @@ -75,16 +79,16 @@ func Command[I, S any](suite Suite[I, S]) commandkit.Command { rootCommands := make([]commandkit.Command, 0, 3) showCommands := make([]commandkit.Command, 0, 2) if suite.InspectPrompt != nil { - showCommands = append(showCommands, newDefinitionCommand(suite, "prompt")) + showCommands = append(showCommands, newDefinitionCommand(suite, "prompt", aliases)) } if suite.InspectProfile != nil { - showCommands = append(showCommands, newDefinitionCommand(suite, "profile")) + showCommands = append(showCommands, newDefinitionCommand(suite, "profile", aliases)) } if suite.InspectBackend != nil { - showCommands = append(showCommands, newDefinitionCommand(suite, "backend")) + showCommands = append(showCommands, newDefinitionCommand(suite, "backend", aliases)) } if suite.PrepareRequest != nil { - showCommands = append(showCommands, newPreparationCommand(suite)) + showCommands = append(showCommands, newPreparationCommand(suite, aliases)) } if len(showCommands) != 0 { rootCommands = append(rootCommands, commandkit.Group("show", "Show a named PromptKit definition.", showCommands...)) @@ -110,16 +114,16 @@ func Command[I, S any](suite Suite[I, S]) commandkit.Command { } requirementsCommands := make([]commandkit.Command, 0, 2) if suite.InspectRequirements != nil { - requirementsCommands = append(requirementsCommands, newRequirementsCommand(suite)) + requirementsCommands = append(requirementsCommands, newRequirementsCommand(suite, aliases)) } if suite.ValidateRequest != nil { - requirementsCommands = append(requirementsCommands, newValidationCommand(suite)) + requirementsCommands = append(requirementsCommands, newValidationCommand(suite, aliases)) } if len(requirementsCommands) != 0 { rootCommands = append(rootCommands, commandkit.Group("requirements", "Inspect and validate prompt requirements.", requirementsCommands...)) } if suite.SmokeTest != nil { - rootCommands = append(rootCommands, newSmokeTestCommand(suite)) + rootCommands = append(rootCommands, newSmokeTestCommand(suite, aliases)) } command := commandkit.RootGroup("promptkit", "Inspect, list, validate requests, and run PromptKit smoke tests.", rootCommands...) for _, issue := range suite.issues { @@ -231,7 +235,7 @@ func prepareCatalogInput[I, S any](suite Suite[I, S], input *catalogInput[I]) (I return prepareSuiteInput(suite, input.selection, input.captured) } -func newDefinitionCommand[I, S any](suite Suite[I, S], name string) commandkit.Command { +func newDefinitionCommand[I, S any](suite Suite[I, S], name string, aliases *[]commandkit.Declaration) commandkit.Command { flags := commandkit.CaptureFlags(suite.Selectors, func(input **definitionInput[I]) *commandkit.FlagValues[I] { if input == nil || *input == nil { return nil @@ -247,7 +251,7 @@ func newDefinitionCommand[I, S any](suite Suite[I, S], name string) commandkit.C }), ) if suite.namespaces != nil { - flags = append(flags, namespaceFlags(func(input **definitionInput[I]) *namespaceInput { return &(*input).namespaces }, name)...) + flags = appendRequestBindings(flags, "promptkit show "+name, aliases, namespaceFlags(func(input **definitionInput[I]) *namespaceInput { return &(*input).namespaces }, name)...) } spec := commandkit.CommandSpec[*definitionInput[I]]{ @@ -276,7 +280,7 @@ func newDefinitionCommand[I, S any](suite Suite[I, S], name string) commandkit.C return commandkit.NewCommand(spec) } -func newRequirementsCommand[I, S any](suite Suite[I, S]) commandkit.Command { +func newRequirementsCommand[I, S any](suite Suite[I, S], aliases *[]commandkit.Declaration) commandkit.Command { flags := commandkit.CaptureFlags(suite.Selectors, func(input **requirementsInput[I]) *commandkit.FlagValues[I] { if input == nil || *input == nil { return nil @@ -296,10 +300,10 @@ func newRequirementsCommand[I, S any](suite Suite[I, S]) commandkit.Command { resources := NewResourceFlags(func(input **requirementsInput[I]) *ResourceInput { return &(*input).resources }) inputs := NewPromptInputFlags(func(input **requirementsInput[I]) *PromptInputs { return &(*input).inputs }) - flags = append(flags, resources.Profile, resources.SessionID, inputs.Variable) + flags = appendRequestBindings(flags, "promptkit requirements show", aliases, requestBinding[*requirementsInput[I]]{resources.Profile, requestFlags.Profile}, requestBinding[*requirementsInput[I]]{resources.SessionID, requestFlags.SessionID}, requestBinding[*requirementsInput[I]]{inputs.Variable, requestFlags.Var}) if suite.namespaces != nil { - flags = append(flags, namespaceFlags(func(input **requirementsInput[I]) *namespaceInput { return &(*input).namespaces }, "prompt", "profile")...) + flags = appendRequestBindings(flags, "promptkit requirements show", aliases, namespaceFlags(func(input **requirementsInput[I]) *namespaceInput { return &(*input).namespaces }, "prompt", "profile")...) } spec := commandkit.CommandSpec[*requirementsInput[I]]{ diff --git a/integrations/ckprompt/declarations.go b/integrations/ckprompt/declarations.go new file mode 100644 index 0000000..044d938 --- /dev/null +++ b/integrations/ckprompt/declarations.go @@ -0,0 +1,31 @@ +package ckprompt + +import commandkit "go.maximumdirect.net/commandkit" + +type requestBinding[I any] struct { + flag commandkit.Flag[I] + definition requestFlagDefinition +} + +// appendRequestBindings installs the selected handles and, when requested, +// records their convenience aliases for the exact owning command. +func appendRequestBindings[I any](flags []commandkit.Flag[I], path string, aliases *[]commandkit.Declaration, bindings ...requestBinding[I]) []commandkit.Flag[I] { + for _, binding := range bindings { + flags = append(flags, binding.flag) + if aliases != nil { + for _, name := range binding.definition.aliases { + *aliases = append(*aliases, commandkit.FlagAliasFor(path, "--"+name, "--"+binding.definition.meta.Name)) + } + } + } + return flags +} + +// Declarations returns the canonical PromptKit command family and scoped +// convenience aliases for the operations and flags enabled by suite. Command +// remains available for installations that want only canonical spellings. +func Declarations[I, S any](suite Suite[I, S]) []commandkit.Declaration { + var aliases []commandkit.Declaration + command := buildCommand(suite, &aliases) + return append([]commandkit.Declaration{command}, aliases...) +} diff --git a/integrations/ckprompt/declarations_test.go b/integrations/ckprompt/declarations_test.go new file mode 100644 index 0000000..5ba7b55 --- /dev/null +++ b/integrations/ckprompt/declarations_test.go @@ -0,0 +1,123 @@ +package ckprompt_test + +import ( + "bytes" + "context" + "io" + "strings" + "testing" + + "go.maximumdirect.net/ckprompt" + commandkit "go.maximumdirect.net/commandkit" + promptkit "go.maximumdirect.net/promptkit" +) + +func TestDeclarationsInstallOnlySelectedScopedAliases(t *testing.T) { + var got ckprompt.PreparationRequest + calls := 0 + suite := ckprompt.WithNamespaces(ckprompt.Suite[struct{}, struct{}]{PrepareRequest: func(_ context.Context, request ckprompt.PreparationRequest, _, _ struct{}) (commandkit.Acquired[*promptkit.PreparedRun], error) { + calls++ + got = request + return commandkit.Acquired[*promptkit.PreparedRun]{Value: &promptkit.PreparedRun{}}, nil + }}, promptkit.Namespaces{Prompt: "default"}) + declarations := append(ckprompt.Declarations(suite), commandkit.Alias("preview", "promptkit show prepared"), commandkit.Completion("app")) + app, err := commandkit.New(commandkit.Identity{Name: "app"}, declarations...) + if err != nil { + t.Fatal(err) + } + var help bytes.Buffer + for _, args := range [][]string{{"promptkit", "show", "prepared", "--help"}, {"__complete", "promptkit", "show", "prepared", ""}} { + result := app.Execute(context.Background(), args, commandkit.Streams{Out: &help, Err: io.Discard}) + if result.Kind != commandkit.OutcomeSuccess || calls != 0 { + t.Fatalf("metadata %v = %#v calls=%d", args, result, calls) + } + } + if !strings.Contains(help.String(), "--llm-input-text") || !strings.Contains(help.String(), "--input") { + t.Fatalf("alias help = %q", help.String()) + } + result := app.Execute(context.Background(), []string{"preview", "p", "--profile=", "--prompt-namespace=", "--input-file=x=path", "--llm-input=x=literal", "--input-text=y=first", "--llm-input-file=y=last", "--input=z=", "--model=fixture"}, commandkit.Streams{Out: io.Discard, Err: io.Discard}) + if result.Kind != commandkit.OutcomeSuccess || calls != 1 || got.Namespaces == nil || got.Namespaces.Prompt != "" || got.Request.Execution == nil || got.Request.Execution.Model != "fixture" || got.Request.Inputs["x"] != promptkit.Inline("literal") || got.Request.Inputs["y"] != promptkit.File("last") || got.Request.Inputs["z"] != promptkit.Inline("") { + t.Fatalf("result=%#v request=%#v calls=%d", result, got, calls) + } + for _, flag := range []string{"--backend=unavailable", "--unknown=value"} { + result = app.Execute(context.Background(), []string{"preview", "p", flag}, commandkit.Streams{Out: io.Discard, Err: io.Discard}) + if result.Kind != commandkit.OutcomeUsage || calls != 1 { + t.Fatalf("inapplicable %s = %#v", flag, result) + } + } + canonical, err := commandkit.New(commandkit.Identity{Name: "app"}, ckprompt.Command(suite)) + if err != nil { + t.Fatal(err) + } + help.Reset() + result = canonical.Execute(context.Background(), []string{"promptkit", "show", "prepared", "--help"}, commandkit.Streams{Out: &help, Err: io.Discard}) + if result.Kind != commandkit.OutcomeSuccess || strings.Contains(help.String(), "--input ") { + t.Fatalf("canonical help = %q", help.String()) + } + result = canonical.Execute(context.Background(), []string{"promptkit", "show", "prepared", "p", "--input=x=text"}, commandkit.Streams{Out: io.Discard, Err: io.Discard}) + if result.Kind != commandkit.OutcomeUsage { + t.Fatalf("canonical alias accepted: %#v", result) + } +} + +func TestDeclarationsDetectAliasConflicts(t *testing.T) { + type input struct{ profile string } + selector := commandkit.ValueFlag(commandkit.FlagMeta{Name: "profile"}, (*string)(nil), func(v string) (string, error) { return v, nil }, func(i *input, v commandkit.Supplied[string]) { i.profile = v.Value }) + suite := ckprompt.Suite[input, struct{}]{Selectors: []commandkit.Flag[input]{selector}, PrepareRequest: func(context.Context, ckprompt.PreparationRequest, input, struct{}) (commandkit.Acquired[*promptkit.PreparedRun], error) { + return commandkit.Acquired[*promptkit.PreparedRun]{Value: &promptkit.PreparedRun{}}, nil + }} + if _, err := commandkit.New(commandkit.Identity{Name: "app"}, ckprompt.Declarations(suite)...); err == nil { + t.Fatal("selector conflicting with scoped alias was accepted") + } + if _, err := commandkit.New(commandkit.Identity{Name: "app"}, ckprompt.Command(suite)); err != nil { + t.Fatalf("canonical selector unexpectedly rejected: %v", err) + } +} + +func TestDeclarationsCoverEnabledCommandsAndNamespaces(t *testing.T) { + var requirements ckprompt.RequirementsRequest + var smoke ckprompt.SmokeRequest + suite := ckprompt.WithNamespaces(ckprompt.Suite[struct{}, struct{}]{ + InspectPrompt: func(context.Context, ckprompt.PromptRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.PromptInspection], error) { + return commandkit.Acquired[*promptkit.PromptInspection]{Value: &promptkit.PromptInspection{}}, nil + }, + InspectProfile: func(context.Context, ckprompt.ProfileRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.ProfileInspection], error) { + return commandkit.Acquired[*promptkit.ProfileInspection]{Value: &promptkit.ProfileInspection{}}, nil + }, + InspectBackend: func(context.Context, ckprompt.BackendRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.BackendInspection], error) { + return commandkit.Acquired[*promptkit.BackendInspection]{Value: &promptkit.BackendInspection{}}, nil + }, + InspectRequirements: func(_ context.Context, req ckprompt.RequirementsRequest, _, _ struct{}) (commandkit.Acquired[*promptkit.RequirementInspection], error) { + requirements = req + return commandkit.Acquired[*promptkit.RequirementInspection]{Value: &promptkit.RequirementInspection{}}, nil + }, + ValidateRequest: func(context.Context, ckprompt.ValidationRequest, struct{}, struct{}) (commandkit.Acquired[struct{}], error) { + return commandkit.Acquired[struct{}]{}, nil + }, + PrepareRequest: func(context.Context, ckprompt.PreparationRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.PreparedRun], error) { + return commandkit.Acquired[*promptkit.PreparedRun]{Value: &promptkit.PreparedRun{}}, nil + }, + SmokeTest: func(_ context.Context, req ckprompt.SmokeRequest, _, _ struct{}) (commandkit.Acquired[*promptkit.SmokeTestReport], error) { + smoke = req + return commandkit.Acquired[*promptkit.SmokeTestReport]{Value: &promptkit.SmokeTestReport{}}, nil + }, + }, promptkit.Namespaces{Prompt: "original", Profile: "profiles", Backend: "backends"}) + app, err := commandkit.New(commandkit.Identity{Name: "app"}, ckprompt.Declarations(suite)...) + if err != nil { + t.Fatal(err) + } + run := func(args ...string) commandkit.Outcome { + return app.Execute(context.Background(), args, commandkit.Streams{Out: io.Discard, Err: io.Discard}) + } + if result := run("promptkit", "requirements", "show", "p", "--profile=first", "--llm-profile=last", "--var=x=y", "--profile-namespace=custom"); result.Kind != commandkit.OutcomeSuccess || requirements.Inspection.ProfileID != "last" || requirements.Inspection.Vars["x"] != "y" || requirements.Namespaces == nil || requirements.Namespaces.Profile != "custom" { + t.Fatalf("requirements = %#v %#v", result, requirements) + } + if result := run("promptkit", "test", "authentication", "--backend=local", "--prompt=p", "--temperature=0", "--extra-params={}", "--backend-namespace="); result.Kind != commandkit.OutcomeSuccess || smoke.Request.BackendID != "local" || smoke.Request.PromptID != "p" || smoke.Request.Execution == nil || smoke.Request.Execution.Temperature == nil || *smoke.Request.Execution.Temperature != 0 || smoke.Request.Execution.ExtraParams == nil || smoke.Namespaces == nil || smoke.Namespaces.Backend != "" { + t.Fatalf("smoke = %#v %#v", result, smoke) + } + for _, args := range [][]string{{"promptkit", "show", "prompt", "p", "--prompt-namespace=x"}, {"promptkit", "show", "profile", "p", "--profile-namespace=x"}, {"promptkit", "show", "backend", "p", "--backend-namespace=x"}, {"promptkit", "requirements", "validate", "p", "--input=x=value"}} { + if result := run(args...); result.Kind != commandkit.OutcomeSuccess { + t.Fatalf("%v = %#v", args, result) + } + } +} diff --git a/integrations/ckprompt/examples/composed/README.md b/integrations/ckprompt/examples/composed/README.md index 01567a6..0e5e654 100644 --- a/integrations/ckprompt/examples/composed/README.md +++ b/integrations/ckprompt/examples/composed/README.md @@ -25,11 +25,11 @@ go run . promptkit show prompt briefing --campaign spring --format yaml go run . pk show prompt briefing --campaign spring --fmt yaml go run . promptkit show profile spring --campaign spring go run . promptkit requirements show briefing --campaign spring -go run . promptkit requirements show briefing --campaign spring --llm-var mode=full --format yaml +go run . promptkit requirements show briefing --campaign spring --var mode=full --format yaml go run . promptkit requirements validate briefing --campaign spring --llm-input-file topic=fixtures/topic.txt go run . promptkit requirements validate briefing --campaign spring --llm-input-file topic=fixtures/topic.txt --llm-var mode=full --llm-input-file notes=fixtures/notes.txt --format yaml go run . promptkit requirements validate briefing --campaign spring -go run . promptkit test text_inference --campaign spring --llm-prompt briefing --llm-profile spring +go run . promptkit test text_inference --campaign spring --prompt briefing --profile spring go run . smoke text_inference --campaign spring --llm-prompt briefing --llm-profile spring go run . promptkit test all --campaign spring --llm-prompt briefing --llm-profile spring go run . pk --hlep diff --git a/integrations/ckprompt/examples/composed/main.go b/integrations/ckprompt/examples/composed/main.go index 798dfe1..e963183 100644 --- a/integrations/ckprompt/examples/composed/main.go +++ b/integrations/ckprompt/examples/composed/main.go @@ -249,7 +249,7 @@ func newAppWithEngineLoader( if smokeTest != nil { definitions.SmokeTest = smokeTest } - definitionCommand := ckprompt.Command(definitions) + definitionDeclarations := ckprompt.Declarations(definitions) store := map[string]session{ "session-42": {ID: "session-42", Campaign: "spring", Summary: "A synthetic planning session."}, @@ -291,7 +291,6 @@ func newAppWithEngineLoader( }) declarations := []commandkit.Declaration{ configCommand, - definitionCommand, commandkit.Completion("composed-example"), commandkit.Group("session", "Manage saved sessions.", restore), commandkit.Alias("list collections", "promptkit list collections"), @@ -301,6 +300,7 @@ func newAppWithEngineLoader( commandkit.FlagAliasFor("promptkit show prompt", "--fmt", "--format"), commandkit.FlagAlias("--hlep", "--help"), } + declarations = append(declarations, definitionDeclarations...) return commandkit.New(commandkit.Identity{Name: "composed-example", Version: "1.0"}, declarations...) } diff --git a/integrations/ckprompt/namespaces.go b/integrations/ckprompt/namespaces.go index ca43dcc..9901c9c 100644 --- a/integrations/ckprompt/namespaces.go +++ b/integrations/ckprompt/namespaces.go @@ -17,23 +17,23 @@ type namespaceInput struct { prompt, profile, backend commandkit.Supplied[string] } -func namespaceFlags[I any](destination func(*I) *namespaceInput, kinds ...string) []commandkit.Flag[I] { - flags := make([]commandkit.Flag[I], 0, len(kinds)) +func namespaceFlags[I any](destination func(*I) *namespaceInput, kinds ...string) []requestBinding[I] { + flags := make([]requestBinding[I], 0, len(kinds)) for _, kind := range kinds { - var meta commandkit.FlagMeta + var definition requestFlagDefinition var field func(*namespaceInput) *commandkit.Supplied[string] switch kind { case "prompt": - meta = namespaceFlagDefinitions.prompt.meta + definition = namespaceFlagDefinitions.prompt field = func(input *namespaceInput) *commandkit.Supplied[string] { return &input.prompt } case "profile": - meta = namespaceFlagDefinitions.profile.meta + definition = namespaceFlagDefinitions.profile field = func(input *namespaceInput) *commandkit.Supplied[string] { return &input.profile } case "backend": - meta = namespaceFlagDefinitions.backend.meta + definition = namespaceFlagDefinitions.backend field = func(input *namespaceInput) *commandkit.Supplied[string] { return &input.backend } } - flags = append(flags, commandkit.ValueFlag(meta, (*string)(nil), parseDefinitionString, func(input *I, value commandkit.Supplied[string]) { *field(destination(input)) = value })) + flags = append(flags, requestBinding[I]{commandkit.ValueFlag(definition.meta, (*string)(nil), parseDefinitionString, func(input *I, value commandkit.Supplied[string]) { *field(destination(input)) = value }), definition}) } return flags } diff --git a/integrations/ckprompt/preparation.go b/integrations/ckprompt/preparation.go index a5294f8..a7dda5f 100644 --- a/integrations/ckprompt/preparation.go +++ b/integrations/ckprompt/preparation.go @@ -69,8 +69,8 @@ func WithRequestPreparation[I, S any]( return suite } -func newPreparationCommand[I, S any](suite Suite[I, S]) commandkit.Command { - spec := newConcreteRequestSpec(suite) +func newPreparationCommand[I, S any](suite Suite[I, S], aliases *[]commandkit.Declaration) commandkit.Command { + spec := newConcreteRequestSpec(suite, "promptkit show prepared", aliases) spec.Name = "prepared" spec.Summary = "Show a prepared PromptKit request without calling the model." spec.Description = "Prepares the request using resource I/O and reports rendered application content without calling the model." diff --git a/integrations/ckprompt/request_flags.go b/integrations/ckprompt/request_flags.go index 1c719af..aaadc13 100644 --- a/integrations/ckprompt/request_flags.go +++ b/integrations/ckprompt/request_flags.go @@ -70,7 +70,18 @@ type ExecutionFlags[I any] struct { // All returns all execution controls. func (f ExecutionFlags[I]) All() []commandkit.Flag[I] { - return []commandkit.Flag[I]{f.Endpoint, f.Model, f.Temperature, f.MaxTokens, f.TopP, f.TimeoutSeconds, f.ServiceTier, f.ReasoningEffort, f.APIKeyEnv, f.TransientRetries, f.ExtraParams} + return appendRequestBindings(nil, "", nil, f.bindings()...) +} + +func (f ExecutionFlags[I]) bindings() []requestBinding[I] { + return []requestBinding[I]{ + {f.Endpoint, requestFlags.Endpoint}, {f.Model, requestFlags.Model}, + {f.Temperature, requestFlags.Temperature}, {f.MaxTokens, requestFlags.MaxTokens}, + {f.TopP, requestFlags.TopP}, {f.TimeoutSeconds, requestFlags.TimeoutSeconds}, + {f.ServiceTier, requestFlags.ServiceTier}, {f.ReasoningEffort, requestFlags.ReasoningEffort}, + {f.APIKeyEnv, requestFlags.APIKeyEnv}, {f.TransientRetries, requestFlags.TransientRetries}, + {f.ExtraParams, requestFlags.ExtraParams}, + } } // NewExecutionFlags binds execution controls to a field of the caller's input. diff --git a/integrations/ckprompt/smoke_command.go b/integrations/ckprompt/smoke_command.go index 2e588cf..d55886e 100644 --- a/integrations/ckprompt/smoke_command.go +++ b/integrations/ckprompt/smoke_command.go @@ -21,7 +21,7 @@ type smokeCommandInput[I any] struct { selected I } -func newSmokeTestCommand[I, S any](suite Suite[I, S]) commandkit.Command { +func newSmokeTestCommand[I, S any](suite Suite[I, S], aliases *[]commandkit.Declaration) commandkit.Command { flags := commandkit.CaptureFlags(suite.Selectors, func(input **smokeCommandInput[I]) *commandkit.FlagValues[I] { if input == nil || *input == nil { return nil @@ -41,11 +41,15 @@ func newSmokeTestCommand[I, S any](suite Suite[I, S]) commandkit.Command { resources := NewResourceFlags(func(input **smokeCommandInput[I]) *ResourceInput { return &(*input).resources }) execution := NewExecutionFlags(func(input **smokeCommandInput[I]) *ExecutionInput { return &(*input).execution }) - flags = append(flags, resources.Backend, resources.Prompt, resources.Profile) - flags = append(flags, execution.All()...) + flags = appendRequestBindings(flags, "promptkit test", aliases, + requestBinding[*smokeCommandInput[I]]{resources.Backend, requestFlags.Backend}, + requestBinding[*smokeCommandInput[I]]{resources.Prompt, requestFlags.Prompt}, + requestBinding[*smokeCommandInput[I]]{resources.Profile, requestFlags.Profile}, + ) + flags = appendRequestBindings(flags, "promptkit test", aliases, execution.bindings()...) if suite.namespaces != nil { - flags = append(flags, namespaceFlags(func(input **smokeCommandInput[I]) *namespaceInput { return &(*input).namespaces }, "prompt", "profile", "backend")...) + flags = appendRequestBindings(flags, "promptkit test", aliases, namespaceFlags(func(input **smokeCommandInput[I]) *namespaceInput { return &(*input).namespaces }, "prompt", "profile", "backend")...) } return commandkit.NewCommand(commandkit.CommandSpec[*smokeCommandInput[I]]{ diff --git a/integrations/ckprompt/validation_command.go b/integrations/ckprompt/validation_command.go index 984a764..d141989 100644 --- a/integrations/ckprompt/validation_command.go +++ b/integrations/ckprompt/validation_command.go @@ -23,7 +23,7 @@ type requestValidationReport struct { Valid bool `json:"valid"` } -func newConcreteRequestSpec[I, S any](suite Suite[I, S]) commandkit.CommandSpec[*concreteRequestInput[I]] { +func newConcreteRequestSpec[I, S any](suite Suite[I, S], path string, aliases *[]commandkit.Declaration) commandkit.CommandSpec[*concreteRequestInput[I]] { flags := commandkit.CaptureFlags(suite.Selectors, func(input **concreteRequestInput[I]) *commandkit.FlagValues[I] { if input == nil || *input == nil { return nil @@ -44,11 +44,17 @@ func newConcreteRequestSpec[I, S any](suite Suite[I, S]) commandkit.CommandSpec[ resources := NewResourceFlags(func(input **concreteRequestInput[I]) *ResourceInput { return &(*input).resources }) execution := NewExecutionFlags(func(input **concreteRequestInput[I]) *ExecutionInput { return &(*input).execution }) inputs := NewPromptInputFlags(func(input **concreteRequestInput[I]) *PromptInputs { return &(*input).inputs }) - flags = append(flags, resources.Profile, resources.SessionID, inputs.Variable, inputs.Text, inputs.File) - flags = append(flags, execution.All()...) + flags = appendRequestBindings(flags, path, aliases, + requestBinding[*concreteRequestInput[I]]{resources.Profile, requestFlags.Profile}, + requestBinding[*concreteRequestInput[I]]{resources.SessionID, requestFlags.SessionID}, + requestBinding[*concreteRequestInput[I]]{inputs.Variable, requestFlags.Var}, + requestBinding[*concreteRequestInput[I]]{inputs.Text, requestFlags.InputText}, + requestBinding[*concreteRequestInput[I]]{inputs.File, requestFlags.InputFile}, + ) + flags = appendRequestBindings(flags, path, aliases, execution.bindings()...) if suite.namespaces != nil { - flags = append(flags, namespaceFlags(func(input **concreteRequestInput[I]) *namespaceInput { return &(*input).namespaces }, "prompt", "profile", "backend")...) + flags = appendRequestBindings(flags, path, aliases, namespaceFlags(func(input **concreteRequestInput[I]) *namespaceInput { return &(*input).namespaces }, "prompt", "profile", "backend")...) } return commandkit.CommandSpec[*concreteRequestInput[I]]{ @@ -84,8 +90,8 @@ func newConcreteRequestSpec[I, S any](suite Suite[I, S]) commandkit.CommandSpec[ } } -func newValidationCommand[I, S any](suite Suite[I, S]) commandkit.Command { - spec := newConcreteRequestSpec(suite) +func newValidationCommand[I, S any](suite Suite[I, S], aliases *[]commandkit.Declaration) commandkit.Command { + spec := newConcreteRequestSpec(suite, "promptkit requirements validate", aliases) spec.Name = "validate" spec.Summary = "Validate a concrete PromptKit request without executing it." spec.Run = func(invocation commandkit.Invocation, input *concreteRequestInput[I]) commandkit.Outcome {