2026-10-11 01:46:54 +00:00
|
|
|
package ckprompt
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
2026-10-12 02:17:54 +00:00
|
|
|
"context"
|
2026-10-11 01:46:54 +00:00
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
commandkit "gitea.maximumdirect.net/eric/CommandKit"
|
|
|
|
|
"gitea.maximumdirect.net/eric/CommandKit/integrations/ckconfig"
|
|
|
|
|
promptkit "gitea.maximumdirect.net/eric/promptkit"
|
|
|
|
|
"go.yaml.in/yaml/v4"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type definitionInput[I any] struct {
|
|
|
|
|
selection ckconfig.Selection
|
|
|
|
|
id string
|
|
|
|
|
format string
|
|
|
|
|
captured commandkit.FlagValues[I]
|
|
|
|
|
selected I
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-12 02:17:54 +00:00
|
|
|
type catalogInput[I any] struct {
|
2026-10-17 18:45:39 +00:00
|
|
|
selection ckconfig.Selection
|
|
|
|
|
namespace commandkit.Supplied[string]
|
|
|
|
|
allNamespaces commandkit.Supplied[bool]
|
|
|
|
|
kinds []promptkit.ResourceKind
|
|
|
|
|
filter promptkit.NamespaceFilter
|
|
|
|
|
format string
|
|
|
|
|
captured commandkit.FlagValues[I]
|
|
|
|
|
selected I
|
2026-10-12 02:17:54 +00:00
|
|
|
}
|
|
|
|
|
|
2026-10-12 17:05:33 +00:00
|
|
|
type requirementsInput[I any] struct {
|
|
|
|
|
selection ckconfig.Selection
|
|
|
|
|
inspection promptkit.RequirementInspectionRequest
|
|
|
|
|
format string
|
|
|
|
|
captured commandkit.FlagValues[I]
|
|
|
|
|
selected I
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type variableAssignment struct {
|
|
|
|
|
key string
|
|
|
|
|
value string
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-12 22:16:44 +00:00
|
|
|
type inputAssignment struct {
|
|
|
|
|
name string
|
|
|
|
|
path string
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-17 18:45:39 +00:00
|
|
|
// Command captures the optional PromptKit catalogs, named inspection,
|
2026-10-12 22:25:43 +00:00
|
|
|
// requirements discovery, and concrete request validation callbacks and adds
|
|
|
|
|
// only supplied operations. Discovery and validation share the
|
|
|
|
|
// `requirements` group and remain independently optional. An absent suite
|
|
|
|
|
// claims no namespace; attached construction issues remain reportable. It adds
|
|
|
|
|
// no configuration family; callers compose ConfigScope independently.
|
|
|
|
|
// Construction, installation, help, and version do not run suite factories or
|
|
|
|
|
// providers. Command captures suite callbacks and selector metadata;
|
|
|
|
|
// callback-referenced objects and effects remain caller-owned. Suite documents
|
|
|
|
|
// invocation input and state initialization, zero values, and caller ownership
|
|
|
|
|
// of returned objects.
|
2026-10-12 02:38:27 +00:00
|
|
|
// Catalog commands accept config/overlay selection and suite selectors, and
|
|
|
|
|
// render YAML with `yaml` as the only format and default. Named-show formats are
|
|
|
|
|
// unchanged.
|
2026-10-11 01:46:54 +00:00
|
|
|
func Command[I, S any](suite Suite[I, S]) commandkit.Command {
|
2026-10-17 18:45:39 +00:00
|
|
|
if suite.InspectPrompt == nil && suite.InspectProfile == nil && suite.InspectRequirements == nil && suite.ValidateRequest == nil && suite.ListPrompts == nil && suite.ListProfiles == nil && suite.ListBackends == nil && suite.ListNamespaces == nil && suite.ListCollections == nil {
|
2026-10-11 01:46:54 +00:00
|
|
|
command := commandkit.NoCommand()
|
|
|
|
|
for _, issue := range suite.issues {
|
|
|
|
|
command = commandkit.WithError(command, issue)
|
|
|
|
|
}
|
|
|
|
|
return command
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-11 13:02:54 +00:00
|
|
|
suite.Selectors = append([]commandkit.Flag[I](nil), suite.Selectors...)
|
2026-10-12 17:05:33 +00:00
|
|
|
rootCommands := make([]commandkit.Command, 0, 3)
|
2026-10-11 01:46:54 +00:00
|
|
|
showCommands := make([]commandkit.Command, 0, 2)
|
|
|
|
|
if suite.InspectPrompt != nil {
|
|
|
|
|
showCommands = append(showCommands, newDefinitionCommand(suite, "prompt", true))
|
|
|
|
|
}
|
|
|
|
|
if suite.InspectProfile != nil {
|
|
|
|
|
showCommands = append(showCommands, newDefinitionCommand(suite, "profile", false))
|
|
|
|
|
}
|
2026-10-12 02:17:54 +00:00
|
|
|
if len(showCommands) != 0 {
|
|
|
|
|
rootCommands = append(rootCommands, commandkit.Group("show", "Show a named PromptKit definition.", showCommands...))
|
|
|
|
|
}
|
2026-10-17 18:45:39 +00:00
|
|
|
listCommands := make([]commandkit.Command, 0, 5)
|
2026-10-12 02:17:54 +00:00
|
|
|
if suite.ListPrompts != nil {
|
2026-10-17 18:45:39 +00:00
|
|
|
listCommands = append(listCommands, newCatalogListCommand(suite, "prompts"))
|
2026-10-12 02:17:54 +00:00
|
|
|
}
|
|
|
|
|
if suite.ListProfiles != nil {
|
2026-10-17 18:45:39 +00:00
|
|
|
listCommands = append(listCommands, newCatalogListCommand(suite, "profiles"))
|
|
|
|
|
}
|
|
|
|
|
if suite.ListBackends != nil {
|
|
|
|
|
listCommands = append(listCommands, newCatalogListCommand(suite, "backends"))
|
|
|
|
|
}
|
|
|
|
|
if suite.ListNamespaces != nil {
|
|
|
|
|
listCommands = append(listCommands, newCatalogListCommand(suite, "namespaces"))
|
|
|
|
|
}
|
|
|
|
|
if suite.ListCollections != nil {
|
|
|
|
|
listCommands = append(listCommands, newCatalogListCommand(suite, "collections"))
|
2026-10-12 02:17:54 +00:00
|
|
|
}
|
|
|
|
|
if len(listCommands) != 0 {
|
|
|
|
|
rootCommands = append(rootCommands, commandkit.Group("list", "List PromptKit catalogs.", listCommands...))
|
|
|
|
|
}
|
2026-10-12 22:16:44 +00:00
|
|
|
requirementsCommands := make([]commandkit.Command, 0, 2)
|
2026-10-12 17:05:33 +00:00
|
|
|
if suite.InspectRequirements != nil {
|
2026-10-12 22:16:44 +00:00
|
|
|
requirementsCommands = append(requirementsCommands, newRequirementsCommand(suite))
|
2026-10-12 17:05:33 +00:00
|
|
|
}
|
2026-10-12 22:16:44 +00:00
|
|
|
if suite.ValidateRequest != nil {
|
|
|
|
|
requirementsCommands = append(requirementsCommands, newValidationCommand(suite))
|
|
|
|
|
}
|
|
|
|
|
if len(requirementsCommands) != 0 {
|
|
|
|
|
rootCommands = append(rootCommands, commandkit.Group("requirements", "Inspect and validate prompt requirements.", requirementsCommands...))
|
|
|
|
|
}
|
|
|
|
|
command := commandkit.RootGroup("promptkit", "Inspect, validate, and list PromptKit definitions and requirements.", rootCommands...)
|
2026-10-11 01:46:54 +00:00
|
|
|
for _, issue := range suite.issues {
|
|
|
|
|
command = commandkit.WithError(command, issue)
|
|
|
|
|
}
|
|
|
|
|
return command
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-17 18:45:39 +00:00
|
|
|
func newCatalogListCommand[I, S any](suite Suite[I, S], name string) commandkit.Command {
|
2026-10-12 02:17:54 +00:00
|
|
|
flags := commandkit.CaptureFlags(suite.Selectors, func(input **catalogInput[I]) *commandkit.FlagValues[I] {
|
|
|
|
|
if input == nil || *input == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return &(*input).captured
|
|
|
|
|
})
|
|
|
|
|
flags = append(flags,
|
|
|
|
|
commandkit.ValueFlag(commandkit.FlagMeta{Name: "config", Usage: "Configuration input path."}, (*string)(nil), parseDefinitionString,
|
|
|
|
|
func(input **catalogInput[I], value commandkit.Supplied[string]) { (*input).selection.Config = value }),
|
|
|
|
|
commandkit.ValueFlag(commandkit.FlagMeta{Name: "overlay", Usage: "Configuration overlay path."}, (*string)(nil), parseDefinitionString,
|
|
|
|
|
func(input **catalogInput[I], value commandkit.Supplied[string]) { (*input).selection.Overlay = value }),
|
|
|
|
|
commandkit.ValueFlag(commandkit.FlagMeta{Name: "format", Usage: "Catalog format: yaml."}, stringDefault("yaml"), parseDefinitionString,
|
|
|
|
|
func(input **catalogInput[I], value commandkit.Supplied[string]) { (*input).format = value.Value }),
|
|
|
|
|
)
|
2026-10-17 18:45:39 +00:00
|
|
|
if name == "prompts" || name == "profiles" || name == "backends" {
|
|
|
|
|
flags = append(flags,
|
|
|
|
|
commandkit.ValueFlag(commandkit.FlagMeta{Name: "namespace", Usage: "Select one native namespace."}, (*string)(nil), parseDefinitionString,
|
|
|
|
|
func(input **catalogInput[I], value commandkit.Supplied[string]) { (*input).namespace = value }),
|
|
|
|
|
commandkit.BoolFlag(commandkit.FlagMeta{Name: "all-namespaces", Usage: "Select identities from every namespace."}, false,
|
|
|
|
|
func(input **catalogInput[I], value commandkit.Supplied[bool]) { (*input).allNamespaces = value }),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
if name == "namespaces" {
|
|
|
|
|
flags = append(flags,
|
|
|
|
|
commandkit.RepeatFlag(commandkit.FlagMeta{Name: "kind", Usage: "Resource kind to include."}, []promptkit.ResourceKind(nil),
|
|
|
|
|
func(value string) (promptkit.ResourceKind, error) { return promptkit.ResourceKind(value), nil },
|
|
|
|
|
func(input **catalogInput[I], value commandkit.Supplied[[]promptkit.ResourceKind]) {
|
|
|
|
|
(*input).kinds = value.Value
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
}
|
2026-10-12 02:17:54 +00:00
|
|
|
return commandkit.NewCommand(commandkit.CommandSpec[*catalogInput[I]]{
|
|
|
|
|
Name: name,
|
|
|
|
|
Summary: "List PromptKit " + name + ".",
|
|
|
|
|
NewInput: func() *catalogInput[I] {
|
|
|
|
|
return &catalogInput[I]{}
|
|
|
|
|
},
|
|
|
|
|
Flags: flags,
|
|
|
|
|
CheckInput: func(input *catalogInput[I]) error {
|
|
|
|
|
if input == nil {
|
|
|
|
|
return errors.New("PromptKit catalog input must not be nil")
|
|
|
|
|
}
|
|
|
|
|
if err := checkCatalogFormat(input.format); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-10-17 18:45:39 +00:00
|
|
|
if err := prepareCatalogFilter(input, name); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-10-12 02:17:54 +00:00
|
|
|
selected, err := prepareCatalogInput(suite, input)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
input.selected = selected
|
|
|
|
|
return nil
|
|
|
|
|
},
|
|
|
|
|
Run: func(invocation commandkit.Invocation, input *catalogInput[I]) commandkit.Outcome {
|
2026-10-17 18:45:39 +00:00
|
|
|
switch name {
|
|
|
|
|
case "prompts":
|
2026-10-12 02:17:54 +00:00
|
|
|
return runCatalogList(invocation, suite, input, suite.ListPrompts, encodePromptCatalog)
|
2026-10-17 18:45:39 +00:00
|
|
|
case "profiles":
|
|
|
|
|
return runCatalogList(invocation, suite, input, suite.ListProfiles, encodeProfileCatalog)
|
|
|
|
|
case "backends":
|
|
|
|
|
return runCatalogList(invocation, suite, input, suite.ListBackends, encodeProfileCatalog)
|
|
|
|
|
case "namespaces":
|
|
|
|
|
list := func(ctx context.Context, request CatalogRequest, value I, state S) (commandkit.Acquired[[]string], error) {
|
|
|
|
|
return suite.ListNamespaces(ctx, NamespaceRequest{Selection: request.Selection, Kinds: input.kinds}, value, state)
|
|
|
|
|
}
|
|
|
|
|
return runCatalogList(invocation, suite, input, list, encodeProfileCatalog)
|
|
|
|
|
case "collections":
|
|
|
|
|
list := func(ctx context.Context, request CatalogRequest, value I, state S) (commandkit.Acquired[[]promptkit.CollectionInfo], error) {
|
|
|
|
|
return suite.ListCollections(ctx, request.Selection, value, state)
|
|
|
|
|
}
|
|
|
|
|
return runCatalogList(invocation, suite, input, list, encodeCollectionCatalog)
|
|
|
|
|
default:
|
|
|
|
|
return definitionFailure(fmt.Errorf("unknown PromptKit catalog %q", name), nil)
|
2026-10-12 02:17:54 +00:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-17 18:45:39 +00:00
|
|
|
func prepareCatalogFilter[I any](input *catalogInput[I], name string) error {
|
|
|
|
|
input.filter = promptkit.NamespaceFilter{Mode: promptkit.NamespaceDefault}
|
|
|
|
|
if name != "prompts" && name != "profiles" && name != "backends" {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if input.allNamespaces.Set && input.allNamespaces.Value && input.namespace.Set {
|
|
|
|
|
return errors.New("--namespace cannot be combined with --all-namespaces")
|
|
|
|
|
}
|
|
|
|
|
if input.allNamespaces.Set && input.allNamespaces.Value {
|
|
|
|
|
input.filter.Mode = promptkit.NamespaceAll
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if input.namespace.Set {
|
|
|
|
|
if input.namespace.Value == "" {
|
|
|
|
|
input.filter.Mode = promptkit.NamespaceUnqualified
|
|
|
|
|
} else {
|
|
|
|
|
input.filter.Mode = promptkit.NamespaceNamed
|
|
|
|
|
input.filter.Name = input.namespace.Value
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-12 02:17:54 +00:00
|
|
|
func checkCatalogFormat(format string) error {
|
|
|
|
|
if format != "yaml" {
|
|
|
|
|
return fmt.Errorf("unsupported catalog format %q", format)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-12 17:05:33 +00:00
|
|
|
func checkRequirementsFormat(format string) error {
|
|
|
|
|
if format != "yaml" {
|
|
|
|
|
return fmt.Errorf("unsupported requirements format %q; expected yaml", format)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-12 02:17:54 +00:00
|
|
|
func prepareCatalogInput[I, S any](suite Suite[I, S], input *catalogInput[I]) (I, error) {
|
|
|
|
|
return prepareSuiteInput(suite, input.selection, input.captured)
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-11 01:46:54 +00:00
|
|
|
func newDefinitionCommand[I, S any](suite Suite[I, S], name string, prompt bool) commandkit.Command {
|
|
|
|
|
flags := commandkit.CaptureFlags(suite.Selectors, func(input **definitionInput[I]) *commandkit.FlagValues[I] {
|
|
|
|
|
if input == nil || *input == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return &(*input).captured
|
|
|
|
|
})
|
|
|
|
|
flags = append(flags,
|
|
|
|
|
commandkit.ValueFlag(commandkit.FlagMeta{Name: "config", Usage: "Configuration input path."}, (*string)(nil), parseDefinitionString,
|
|
|
|
|
func(input **definitionInput[I], value commandkit.Supplied[string]) { (*input).selection.Config = value }),
|
|
|
|
|
commandkit.ValueFlag(commandkit.FlagMeta{Name: "overlay", Usage: "Configuration overlay path."}, (*string)(nil), parseDefinitionString,
|
|
|
|
|
func(input **definitionInput[I], value commandkit.Supplied[string]) {
|
|
|
|
|
(*input).selection.Overlay = value
|
|
|
|
|
}),
|
|
|
|
|
commandkit.ValueFlag(commandkit.FlagMeta{Name: "format", Usage: "Report format: text or yaml."}, stringDefault("text"), parseDefinitionString,
|
|
|
|
|
func(input **definitionInput[I], value commandkit.Supplied[string]) { (*input).format = value.Value }),
|
|
|
|
|
)
|
|
|
|
|
spec := commandkit.CommandSpec[*definitionInput[I]]{
|
|
|
|
|
Name: name,
|
|
|
|
|
Summary: "Inspect a named PromptKit " + name + ".",
|
|
|
|
|
NewInput: func() *definitionInput[I] {
|
|
|
|
|
return &definitionInput[I]{}
|
|
|
|
|
},
|
|
|
|
|
Flags: flags,
|
|
|
|
|
Arguments: []commandkit.Argument[*definitionInput[I]]{
|
|
|
|
|
commandkit.ValueArgument(commandkit.ArgumentMeta{Name: "id", Usage: "Prompt or profile ID."}, true, parseDefinitionString,
|
|
|
|
|
func(input **definitionInput[I], value commandkit.Supplied[string]) { (*input).id = value.Value }),
|
|
|
|
|
},
|
|
|
|
|
CheckInput: func(input *definitionInput[I]) error {
|
|
|
|
|
if err := checkDefinitionFormat(input.format); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
selected, err := prepareDefinitionInput(suite, input)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
input.selected = selected
|
|
|
|
|
return nil
|
|
|
|
|
},
|
|
|
|
|
Run: func(invocation commandkit.Invocation, input *definitionInput[I]) commandkit.Outcome {
|
|
|
|
|
return runDefinitionInspection(invocation, suite, input, prompt)
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
return commandkit.NewCommand(spec)
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-12 17:05:33 +00:00
|
|
|
func newRequirementsCommand[I, S any](suite Suite[I, S]) commandkit.Command {
|
|
|
|
|
flags := commandkit.CaptureFlags(suite.Selectors, func(input **requirementsInput[I]) *commandkit.FlagValues[I] {
|
|
|
|
|
if input == nil || *input == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return &(*input).captured
|
|
|
|
|
})
|
|
|
|
|
flags = append(flags,
|
|
|
|
|
commandkit.ValueFlag(commandkit.FlagMeta{Name: "config", Usage: "Configuration input path."}, (*string)(nil), parseDefinitionString,
|
|
|
|
|
func(input **requirementsInput[I], value commandkit.Supplied[string]) {
|
|
|
|
|
(*input).selection.Config = value
|
|
|
|
|
}),
|
|
|
|
|
commandkit.ValueFlag(commandkit.FlagMeta{Name: "overlay", Usage: "Configuration overlay path."}, (*string)(nil), parseDefinitionString,
|
|
|
|
|
func(input **requirementsInput[I], value commandkit.Supplied[string]) {
|
|
|
|
|
(*input).selection.Overlay = value
|
|
|
|
|
}),
|
|
|
|
|
commandkit.ValueFlag(commandkit.FlagMeta{Name: "profile", Usage: "Profile identity for discovery."}, stringDefault(""), parseDefinitionString,
|
|
|
|
|
func(input **requirementsInput[I], value commandkit.Supplied[string]) {
|
|
|
|
|
(*input).inspection.ProfileID = value.Value
|
|
|
|
|
}),
|
|
|
|
|
commandkit.ValueFlag(commandkit.FlagMeta{Name: "session-id", Usage: "Session context for discovery."}, stringDefault(""), parseDefinitionString,
|
|
|
|
|
func(input **requirementsInput[I], value commandkit.Supplied[string]) {
|
|
|
|
|
(*input).inspection.SessionID = value.Value
|
|
|
|
|
}),
|
|
|
|
|
commandkit.ValueFlag(commandkit.FlagMeta{Name: "format", Usage: "Report format: yaml."}, stringDefault("yaml"), parseDefinitionString,
|
|
|
|
|
func(input **requirementsInput[I], value commandkit.Supplied[string]) { (*input).format = value.Value }),
|
|
|
|
|
commandkit.RepeatFlag(commandkit.FlagMeta{Name: "var", Usage: "Caller variable assignment KEY=VALUE."}, []variableAssignment(nil), parseVariableAssignment,
|
|
|
|
|
func(input **requirementsInput[I], value commandkit.Supplied[[]variableAssignment]) {
|
|
|
|
|
variables := make(map[string]string, len(value.Value))
|
|
|
|
|
for _, assignment := range value.Value {
|
|
|
|
|
variables[assignment.key] = assignment.value
|
|
|
|
|
}
|
|
|
|
|
(*input).inspection.Vars = variables
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
spec := commandkit.CommandSpec[*requirementsInput[I]]{
|
|
|
|
|
Name: "show",
|
|
|
|
|
Summary: "Inspect requirements for a named PromptKit prompt.",
|
|
|
|
|
NewInput: func() *requirementsInput[I] {
|
|
|
|
|
return &requirementsInput[I]{}
|
|
|
|
|
},
|
|
|
|
|
Flags: flags,
|
|
|
|
|
Arguments: []commandkit.Argument[*requirementsInput[I]]{
|
|
|
|
|
commandkit.ValueArgument(commandkit.ArgumentMeta{Name: "id", Usage: "Prompt ID."}, true, parseDefinitionString,
|
|
|
|
|
func(input **requirementsInput[I], value commandkit.Supplied[string]) {
|
|
|
|
|
(*input).inspection.PromptID = value.Value
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
CheckInput: func(input *requirementsInput[I]) error {
|
|
|
|
|
if err := checkRequirementsFormat(input.format); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
selected, err := prepareSuiteInput(suite, input.selection, input.captured)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
input.selected = selected
|
|
|
|
|
return nil
|
|
|
|
|
},
|
|
|
|
|
Run: func(invocation commandkit.Invocation, input *requirementsInput[I]) commandkit.Outcome {
|
|
|
|
|
return runRequirementsInspection(invocation, suite, input)
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
return commandkit.NewCommand(spec)
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-11 01:46:54 +00:00
|
|
|
func prepareDefinitionInput[I, S any](suite Suite[I, S], input *definitionInput[I]) (I, error) {
|
2026-10-12 02:17:54 +00:00
|
|
|
return prepareSuiteInput(suite, input.selection, input.captured)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func prepareSuiteInput[I, S any](suite Suite[I, S], selection ckconfig.Selection, captured commandkit.FlagValues[I]) (I, error) {
|
2026-10-11 01:46:54 +00:00
|
|
|
var selected I
|
|
|
|
|
if suite.NewInput != nil {
|
|
|
|
|
selected = suite.NewInput()
|
|
|
|
|
}
|
2026-10-12 02:17:54 +00:00
|
|
|
if err := captured.ApplyTo(&selected, suite.Selectors...); err != nil {
|
2026-10-11 01:46:54 +00:00
|
|
|
return selected, err
|
|
|
|
|
}
|
|
|
|
|
if suite.CheckInput != nil {
|
2026-10-12 02:17:54 +00:00
|
|
|
if err := suite.CheckInput(selection, selected); err != nil {
|
2026-10-11 01:46:54 +00:00
|
|
|
return selected, err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return selected, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-12 02:17:54 +00:00
|
|
|
func runCatalogList[T any, I, S any](
|
|
|
|
|
invocation commandkit.Invocation,
|
|
|
|
|
suite Suite[I, S],
|
|
|
|
|
input *catalogInput[I],
|
2026-10-17 18:44:10 +00:00
|
|
|
list func(context.Context, CatalogRequest, I, S) (commandkit.Acquired[T], error),
|
2026-10-12 02:17:54 +00:00
|
|
|
encode func(T) ([]byte, error),
|
|
|
|
|
) commandkit.Outcome {
|
|
|
|
|
if invocation.Context != nil {
|
|
|
|
|
if err := invocation.Context.Err(); err != nil {
|
|
|
|
|
return definitionFailure(err, nil)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
var state S
|
|
|
|
|
if suite.NewState != nil {
|
|
|
|
|
state = suite.NewState()
|
|
|
|
|
}
|
|
|
|
|
if invocation.Context != nil {
|
|
|
|
|
if err := invocation.Context.Err(); err != nil {
|
|
|
|
|
return definitionFailure(err, nil)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-17 18:45:39 +00:00
|
|
|
acquired, err := list(invocation.Context, CatalogRequest{Selection: input.selection, Filter: input.filter}, input.selected, state)
|
2026-10-12 02:17:54 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return definitionFailure(err, acquired.Diagnostics)
|
|
|
|
|
}
|
|
|
|
|
if invocation.Context != nil {
|
|
|
|
|
if err := invocation.Context.Err(); err != nil {
|
|
|
|
|
return definitionFailure(err, acquired.Diagnostics)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
output, err := encode(acquired.Value)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return definitionFailure(err, acquired.Diagnostics)
|
|
|
|
|
}
|
|
|
|
|
return writePromptReport(invocation, output, acquired.Diagnostics)
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-11 01:46:54 +00:00
|
|
|
func runDefinitionInspection[I, S any](invocation commandkit.Invocation, suite Suite[I, S], input *definitionInput[I], prompt bool) commandkit.Outcome {
|
|
|
|
|
if invocation.Context != nil {
|
|
|
|
|
if err := invocation.Context.Err(); err != nil {
|
|
|
|
|
return definitionFailure(err, nil)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
var state S
|
|
|
|
|
if suite.NewState != nil {
|
|
|
|
|
state = suite.NewState()
|
|
|
|
|
}
|
|
|
|
|
if invocation.Context != nil {
|
|
|
|
|
if err := invocation.Context.Err(); err != nil {
|
|
|
|
|
return definitionFailure(err, nil)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if prompt {
|
2026-10-17 18:34:03 +00:00
|
|
|
request := PromptRequest{Selection: input.selection, ID: input.id}
|
2026-10-11 01:46:54 +00:00
|
|
|
acquired, err := suite.InspectPrompt(invocation.Context, request, input.selected, state)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return definitionFailure(err, acquired.Diagnostics)
|
|
|
|
|
}
|
|
|
|
|
if acquired.Value == nil {
|
|
|
|
|
return definitionFailure(errors.New("PromptKit prompt inspection returned nil"), acquired.Diagnostics)
|
|
|
|
|
}
|
|
|
|
|
if invocation.Context != nil {
|
|
|
|
|
if err := invocation.Context.Err(); err != nil {
|
|
|
|
|
return definitionFailure(err, acquired.Diagnostics)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return renderPromptReport(invocation, promptReportFromInspection(acquired.Value), input.format, acquired.Diagnostics)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
request := ProfileRequest{Selection: input.selection, ID: input.id}
|
|
|
|
|
acquired, err := suite.InspectProfile(invocation.Context, request, input.selected, state)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return definitionFailure(err, acquired.Diagnostics)
|
|
|
|
|
}
|
|
|
|
|
if acquired.Value == nil {
|
|
|
|
|
return definitionFailure(errors.New("PromptKit profile inspection returned nil"), acquired.Diagnostics)
|
|
|
|
|
}
|
|
|
|
|
if invocation.Context != nil {
|
|
|
|
|
if err := invocation.Context.Err(); err != nil {
|
|
|
|
|
return definitionFailure(err, acquired.Diagnostics)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return renderProfileReport(invocation, profileReportFromInspection(acquired.Value), input.format, acquired.Diagnostics)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type promptInputReport struct {
|
|
|
|
|
Name string `yaml:"name"`
|
|
|
|
|
Required bool `yaml:"required"`
|
|
|
|
|
ContentType string `yaml:"content_type"`
|
|
|
|
|
Description string `yaml:"description"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type promptOutputContractReport struct {
|
|
|
|
|
Format string `yaml:"format"`
|
|
|
|
|
ValidationMode string `yaml:"validation_mode"`
|
|
|
|
|
SchemaPath string `yaml:"schema_path"`
|
|
|
|
|
RepairAttempts int `yaml:"repair_attempts"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type promptInspectionReport struct {
|
|
|
|
|
PromptID string `yaml:"prompt_id"`
|
2026-10-17 18:34:03 +00:00
|
|
|
PromptVersion *string `yaml:"version,omitempty"`
|
2026-10-11 01:46:54 +00:00
|
|
|
PromptHash string `yaml:"prompt_hash"`
|
|
|
|
|
DefaultProfileID string `yaml:"default_profile_id"`
|
|
|
|
|
Inputs []promptInputReport `yaml:"inputs"`
|
|
|
|
|
OutputContract promptOutputContractReport `yaml:"output_contract"`
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-12 16:57:23 +00:00
|
|
|
type requirementsInspectionReport struct {
|
|
|
|
|
PromptID string `yaml:"prompt_id"`
|
2026-10-17 18:34:03 +00:00
|
|
|
PromptVersion *string `yaml:"version,omitempty"`
|
2026-10-12 16:57:23 +00:00
|
|
|
ProfileID string `yaml:"profile_id"`
|
|
|
|
|
Complete bool `yaml:"complete"`
|
|
|
|
|
Inputs []requirementInputReport `yaml:"inputs"`
|
|
|
|
|
Variables []requirementVariableReport `yaml:"variables"`
|
|
|
|
|
Issues []requirementIssueReport `yaml:"issues"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type requirementInputReport struct {
|
|
|
|
|
Name string `yaml:"name"`
|
|
|
|
|
Kind string `yaml:"kind"`
|
|
|
|
|
Declared bool `yaml:"declared"`
|
|
|
|
|
ContentType *string `yaml:"content_type"`
|
|
|
|
|
Description *string `yaml:"description"`
|
|
|
|
|
Explanation string `yaml:"explanation"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type requirementVariableReport struct {
|
|
|
|
|
Name string `yaml:"name"`
|
|
|
|
|
Kind string `yaml:"kind"`
|
|
|
|
|
Supplied bool `yaml:"supplied"`
|
|
|
|
|
Explanation string `yaml:"explanation"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type requirementIssueReport struct {
|
|
|
|
|
Code string `yaml:"code"`
|
|
|
|
|
InputName string `yaml:"input_name"`
|
|
|
|
|
VariableName string `yaml:"variable_name"`
|
|
|
|
|
Description string `yaml:"description"`
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-11 01:46:54 +00:00
|
|
|
type modelParamsReport struct {
|
|
|
|
|
BackendID string `yaml:"backend_id"`
|
|
|
|
|
Endpoint string `yaml:"endpoint"`
|
|
|
|
|
Model string `yaml:"model"`
|
|
|
|
|
Temperature float64 `yaml:"temperature"`
|
|
|
|
|
MaxTokens int `yaml:"max_tokens"`
|
|
|
|
|
TopP float64 `yaml:"top_p"`
|
|
|
|
|
TimeoutSeconds int `yaml:"timeout_seconds"`
|
|
|
|
|
ServiceTier string `yaml:"service_tier"`
|
|
|
|
|
ReasoningEffort string `yaml:"reasoning_effort"`
|
|
|
|
|
APIKeyEnv string `yaml:"api_key_env"`
|
|
|
|
|
ExtraParams map[string]any `yaml:"extra_params"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type profileInspectionReport struct {
|
|
|
|
|
ProfileID string `yaml:"profile_id"`
|
|
|
|
|
EffectiveModelParams modelParamsReport `yaml:"effective_model_params"`
|
|
|
|
|
APIKeyRequired bool `yaml:"api_key_required"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func promptReportFromInspection(inspection *promptkit.PromptInspection) promptInspectionReport {
|
|
|
|
|
report := promptInspectionReport{
|
|
|
|
|
PromptID: inspection.PromptID,
|
|
|
|
|
PromptVersion: inspection.PromptVersion,
|
|
|
|
|
PromptHash: inspection.PromptHash,
|
|
|
|
|
DefaultProfileID: inspection.DefaultProfileID,
|
|
|
|
|
Inputs: make([]promptInputReport, 0, len(inspection.Inputs)),
|
|
|
|
|
OutputContract: promptOutputContractReport{
|
|
|
|
|
Format: string(inspection.OutputContract.Format),
|
|
|
|
|
ValidationMode: string(inspection.OutputContract.ValidationMode),
|
|
|
|
|
SchemaPath: inspection.OutputContract.SchemaPath,
|
|
|
|
|
RepairAttempts: inspection.OutputContract.RepairAttempts,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
for _, input := range inspection.Inputs {
|
|
|
|
|
report.Inputs = append(report.Inputs, promptInputReport{Name: input.Name, Required: input.Required, ContentType: input.ContentType, Description: input.Description})
|
|
|
|
|
}
|
|
|
|
|
return report
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-12 16:57:23 +00:00
|
|
|
func requirementsReportFromInspection(inspection *promptkit.RequirementInspection) requirementsInspectionReport {
|
|
|
|
|
report := requirementsInspectionReport{
|
|
|
|
|
PromptID: inspection.PromptID,
|
|
|
|
|
PromptVersion: inspection.PromptVersion,
|
|
|
|
|
ProfileID: inspection.ProfileID,
|
|
|
|
|
Complete: inspection.Complete,
|
|
|
|
|
Inputs: make([]requirementInputReport, 0, len(inspection.Inputs)),
|
|
|
|
|
Variables: make([]requirementVariableReport, 0, len(inspection.Variables)),
|
|
|
|
|
Issues: make([]requirementIssueReport, 0, len(inspection.Issues)),
|
|
|
|
|
}
|
|
|
|
|
for _, input := range inspection.Inputs {
|
|
|
|
|
report.Inputs = append(report.Inputs, requirementInputReport{
|
|
|
|
|
Name: input.Name,
|
|
|
|
|
Kind: string(input.Kind),
|
|
|
|
|
Declared: input.Declared,
|
|
|
|
|
ContentType: input.ContentType,
|
|
|
|
|
Description: input.Description,
|
|
|
|
|
Explanation: input.Explanation,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
for _, variable := range inspection.Variables {
|
|
|
|
|
report.Variables = append(report.Variables, requirementVariableReport{
|
|
|
|
|
Name: variable.Name,
|
|
|
|
|
Kind: string(variable.Kind),
|
|
|
|
|
Supplied: variable.Supplied,
|
|
|
|
|
Explanation: variable.Explanation,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
for _, issue := range inspection.Issues {
|
|
|
|
|
report.Issues = append(report.Issues, requirementIssueReport{
|
|
|
|
|
Code: string(issue.Code),
|
|
|
|
|
InputName: issue.InputName,
|
|
|
|
|
VariableName: issue.VariableName,
|
|
|
|
|
Description: issue.Description,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return report
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-11 01:46:54 +00:00
|
|
|
func profileReportFromInspection(inspection *promptkit.ProfileInspection) profileInspectionReport {
|
|
|
|
|
params := inspection.EffectiveModelParams
|
|
|
|
|
extra := params.ExtraParams
|
|
|
|
|
if extra == nil {
|
|
|
|
|
extra = map[string]any{}
|
|
|
|
|
}
|
|
|
|
|
return profileInspectionReport{
|
|
|
|
|
ProfileID: inspection.ProfileID,
|
|
|
|
|
EffectiveModelParams: modelParamsReport{
|
|
|
|
|
BackendID: params.BackendID, Endpoint: params.Endpoint, Model: params.Model,
|
|
|
|
|
Temperature: params.Temperature, MaxTokens: params.MaxTokens, TopP: params.TopP,
|
|
|
|
|
TimeoutSeconds: params.TimeoutSeconds, ServiceTier: params.ServiceTier,
|
|
|
|
|
ReasoningEffort: params.ReasoningEffort, APIKeyEnv: params.APIKeyEnv, ExtraParams: extra,
|
|
|
|
|
},
|
|
|
|
|
APIKeyRequired: inspection.APIKeyRequired,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-12 17:05:33 +00:00
|
|
|
func runRequirementsInspection[I, S any](invocation commandkit.Invocation, suite Suite[I, S], input *requirementsInput[I]) commandkit.Outcome {
|
|
|
|
|
if invocation.Context != nil {
|
|
|
|
|
if err := invocation.Context.Err(); err != nil {
|
|
|
|
|
return definitionFailure(err, nil)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
var state S
|
|
|
|
|
if suite.NewState != nil {
|
|
|
|
|
state = suite.NewState()
|
|
|
|
|
}
|
|
|
|
|
if invocation.Context != nil {
|
|
|
|
|
if err := invocation.Context.Err(); err != nil {
|
|
|
|
|
return definitionFailure(err, nil)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
request := RequirementsRequest{Selection: input.selection, Inspection: input.inspection}
|
|
|
|
|
acquired, err := suite.InspectRequirements(invocation.Context, request, input.selected, state)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return definitionFailure(err, acquired.Diagnostics)
|
|
|
|
|
}
|
|
|
|
|
if acquired.Value == nil {
|
|
|
|
|
return definitionFailure(errors.New("PromptKit requirements inspection returned nil"), acquired.Diagnostics)
|
|
|
|
|
}
|
|
|
|
|
if invocation.Context != nil {
|
|
|
|
|
if err := invocation.Context.Err(); err != nil {
|
|
|
|
|
return definitionFailure(err, acquired.Diagnostics)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
output, err := encodePromptYAMLBytes(requirementsReportFromInspection(acquired.Value))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return definitionFailure(err, acquired.Diagnostics)
|
|
|
|
|
}
|
|
|
|
|
return writePromptReport(invocation, output, acquired.Diagnostics)
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-11 01:46:54 +00:00
|
|
|
func renderPromptReport(invocation commandkit.Invocation, report promptInspectionReport, format string, diagnostics []commandkit.Diagnostic) commandkit.Outcome {
|
|
|
|
|
var output bytes.Buffer
|
|
|
|
|
if format == "yaml" {
|
|
|
|
|
if err := encodePromptYAML(&output, report); err != nil {
|
|
|
|
|
return definitionFailure(err, diagnostics)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
output.WriteString(formatPromptText(report))
|
|
|
|
|
}
|
|
|
|
|
return writePromptReport(invocation, output.Bytes(), diagnostics)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func renderProfileReport(invocation commandkit.Invocation, report profileInspectionReport, format string, diagnostics []commandkit.Diagnostic) commandkit.Outcome {
|
|
|
|
|
var output bytes.Buffer
|
|
|
|
|
if format == "yaml" {
|
|
|
|
|
if err := encodePromptYAML(&output, report); err != nil {
|
|
|
|
|
return definitionFailure(err, diagnostics)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
text, err := formatProfileText(report)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return definitionFailure(err, diagnostics)
|
|
|
|
|
}
|
|
|
|
|
output.WriteString(text)
|
|
|
|
|
}
|
|
|
|
|
return writePromptReport(invocation, output.Bytes(), diagnostics)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func formatPromptText(report promptInspectionReport) string {
|
|
|
|
|
var output strings.Builder
|
|
|
|
|
fmt.Fprintf(&output, "Prompt ID: %s\n", strconv.Quote(report.PromptID))
|
2026-10-17 18:34:03 +00:00
|
|
|
if report.PromptVersion != nil {
|
|
|
|
|
fmt.Fprintf(&output, "Prompt version: %s\n", strconv.Quote(*report.PromptVersion))
|
|
|
|
|
}
|
2026-10-11 01:46:54 +00:00
|
|
|
fmt.Fprintf(&output, "Prompt hash: %s\n", strconv.Quote(report.PromptHash))
|
|
|
|
|
fmt.Fprintf(&output, "Default profile ID: %s\n", strconv.Quote(report.DefaultProfileID))
|
|
|
|
|
output.WriteString("Inputs:\n")
|
|
|
|
|
for _, input := range report.Inputs {
|
|
|
|
|
fmt.Fprintf(&output, " - name: %s\n required: %t\n content_type: %s\n description: %s\n", strconv.Quote(input.Name), input.Required, strconv.Quote(input.ContentType), strconv.Quote(input.Description))
|
|
|
|
|
}
|
|
|
|
|
if len(report.Inputs) == 0 {
|
|
|
|
|
output.WriteString(" []\n")
|
|
|
|
|
}
|
|
|
|
|
fmt.Fprintf(&output, "Output contract:\n format: %s\n validation_mode: %s\n schema_path: %s\n repair_attempts: %d\n", strconv.Quote(report.OutputContract.Format), strconv.Quote(report.OutputContract.ValidationMode), strconv.Quote(report.OutputContract.SchemaPath), report.OutputContract.RepairAttempts)
|
|
|
|
|
return output.String()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func formatProfileText(report profileInspectionReport) (string, error) {
|
|
|
|
|
params := report.EffectiveModelParams
|
|
|
|
|
var output strings.Builder
|
|
|
|
|
fmt.Fprintf(&output, "Profile ID: %s\n", strconv.Quote(report.ProfileID))
|
|
|
|
|
output.WriteString("Effective model params:\n")
|
|
|
|
|
fmt.Fprintf(&output, " backend_id: %s\n endpoint: %s\n model: %s\n temperature: %s\n max_tokens: %d\n top_p: %s\n timeout_seconds: %d\n service_tier: %s\n reasoning_effort: %s\n api_key_env: %s\n", strconv.Quote(params.BackendID), strconv.Quote(params.Endpoint), strconv.Quote(params.Model), strconv.FormatFloat(params.Temperature, 'g', -1, 64), params.MaxTokens, strconv.FormatFloat(params.TopP, 'g', -1, 64), params.TimeoutSeconds, strconv.Quote(params.ServiceTier), strconv.Quote(params.ReasoningEffort), strconv.Quote(params.APIKeyEnv))
|
|
|
|
|
output.WriteString(" extra_params:\n")
|
|
|
|
|
var nested strings.Builder
|
|
|
|
|
if err := encodePromptYAML(&nested, params.ExtraParams); err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
for _, line := range strings.Split(strings.TrimSuffix(nested.String(), "\n"), "\n") {
|
|
|
|
|
output.WriteString(" ")
|
|
|
|
|
output.WriteString(line)
|
|
|
|
|
output.WriteByte('\n')
|
|
|
|
|
}
|
|
|
|
|
fmt.Fprintf(&output, "API key required: %t\n", report.APIKeyRequired)
|
|
|
|
|
return output.String(), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func encodePromptYAML(writer io.Writer, value any) error {
|
|
|
|
|
encoder := yaml.NewEncoder(writer)
|
|
|
|
|
encoder.SetIndent(2)
|
|
|
|
|
return encoder.Encode(value)
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-12 02:12:47 +00:00
|
|
|
type promptCatalogEntry struct {
|
2026-10-17 18:34:03 +00:00
|
|
|
ID string `yaml:"id"`
|
|
|
|
|
Version *string `yaml:"version,omitempty"`
|
2026-10-12 02:12:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func encodePromptCatalog(prompts []promptkit.PromptIdentity) ([]byte, error) {
|
|
|
|
|
entries := make([]promptCatalogEntry, len(prompts))
|
|
|
|
|
for index, prompt := range prompts {
|
|
|
|
|
entries[index] = promptCatalogEntry{ID: prompt.ID, Version: prompt.Version}
|
|
|
|
|
}
|
|
|
|
|
return encodePromptYAMLBytes(entries)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func encodeProfileCatalog(profiles []string) ([]byte, error) {
|
|
|
|
|
if profiles == nil {
|
|
|
|
|
profiles = []string{}
|
|
|
|
|
}
|
|
|
|
|
return encodePromptYAMLBytes(profiles)
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-17 18:45:39 +00:00
|
|
|
type collectionCatalogEntry struct {
|
|
|
|
|
Namespace string `yaml:"namespace"`
|
|
|
|
|
Version *string `yaml:"version,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func encodeCollectionCatalog(collections []promptkit.CollectionInfo) ([]byte, error) {
|
|
|
|
|
entries := make([]collectionCatalogEntry, len(collections))
|
|
|
|
|
for index, collection := range collections {
|
|
|
|
|
entries[index] = collectionCatalogEntry{Namespace: collection.Namespace, Version: collection.ReleaseVersion}
|
|
|
|
|
}
|
|
|
|
|
return encodePromptYAMLBytes(entries)
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-12 02:12:47 +00:00
|
|
|
func encodePromptYAMLBytes(value any) ([]byte, error) {
|
|
|
|
|
var output bytes.Buffer
|
|
|
|
|
if err := encodePromptYAML(&output, value); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return output.Bytes(), nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-11 01:46:54 +00:00
|
|
|
func writePromptReport(invocation commandkit.Invocation, output []byte, diagnostics []commandkit.Diagnostic) commandkit.Outcome {
|
|
|
|
|
if invocation.Context != nil {
|
|
|
|
|
if err := invocation.Context.Err(); err != nil {
|
|
|
|
|
return definitionFailure(err, diagnostics)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
written, err := invocation.Streams.Out.Write(output)
|
|
|
|
|
if err == nil && written != len(output) {
|
|
|
|
|
err = io.ErrShortWrite
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
outcome := definitionFailure(err, diagnostics)
|
|
|
|
|
outcome.ReportIncomplete = true
|
|
|
|
|
outcome.DeliveryError = err
|
|
|
|
|
return outcome
|
|
|
|
|
}
|
|
|
|
|
outcome := commandkit.Success()
|
|
|
|
|
outcome.Diagnostics = diagnostics
|
|
|
|
|
return outcome
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func definitionFailure(cause error, diagnostics []commandkit.Diagnostic) commandkit.Outcome {
|
|
|
|
|
outcome := commandkit.Failure(cause)
|
|
|
|
|
outcome.Diagnostics = diagnostics
|
|
|
|
|
return outcome
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func checkDefinitionFormat(format string) error {
|
|
|
|
|
if format != "text" && format != "yaml" {
|
|
|
|
|
return fmt.Errorf("unsupported format %q; expected text or yaml", format)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func parseDefinitionString(value string) (string, error) { return value, nil }
|
|
|
|
|
|
2026-10-12 17:05:33 +00:00
|
|
|
func parseVariableAssignment(raw string) (variableAssignment, error) {
|
|
|
|
|
key, value, found := strings.Cut(raw, "=")
|
|
|
|
|
if !found {
|
|
|
|
|
return variableAssignment{}, fmt.Errorf("variable assignment %q must contain =", raw)
|
|
|
|
|
}
|
|
|
|
|
return variableAssignment{key: key, value: value}, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-12 22:16:44 +00:00
|
|
|
func parseInputAssignment(raw string) (inputAssignment, error) {
|
|
|
|
|
name, path, found := strings.Cut(raw, "=")
|
|
|
|
|
if !found {
|
|
|
|
|
return inputAssignment{}, fmt.Errorf("input assignment %q must contain =", raw)
|
|
|
|
|
}
|
|
|
|
|
return inputAssignment{name: name, path: path}, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-11 01:46:54 +00:00
|
|
|
func stringDefault(value string) *string { return &value }
|