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-17 18:55:41 +00:00
// requirements discovery, concrete request validation, and smoke-test
// callbacks and adds only supplied operations. Discovery and validation share the
2026-10-12 22:25:43 +00:00
// `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:55:41 +00:00
if suite . InspectPrompt == nil && suite . InspectProfile == nil && suite . InspectRequirements == nil && suite . ValidateRequest == nil && suite . SmokeTest == 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 ... ) )
}
2026-10-17 18:55:41 +00:00
if suite . SmokeTest != nil {
rootCommands = append ( rootCommands , newSmokeTestCommand ( suite ) )
}
command := commandkit . RootGroup ( "promptkit" , "Inspect, list, validate requests, and run PromptKit smoke tests." , 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-26 03:30:19 +00:00
return runCatalogList ( invocation , suite , input , name , suite . ListPrompts , encodePromptCatalog )
2026-10-17 18:45:39 +00:00
case "profiles" :
2026-10-26 03:30:19 +00:00
return runCatalogList ( invocation , suite , input , name , suite . ListProfiles , encodeProfileCatalog )
2026-10-17 18:45:39 +00:00
case "backends" :
2026-10-26 03:30:19 +00:00
return runCatalogList ( invocation , suite , input , name , suite . ListBackends , encodeProfileCatalog )
2026-10-17 18:45:39 +00:00
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 )
}
2026-10-26 03:30:19 +00:00
return runCatalogList ( invocation , suite , input , name , list , encodeProfileCatalog )
2026-10-17 18:45:39 +00:00
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 )
}
2026-10-26 03:30:19 +00:00
return runCatalogList ( invocation , suite , input , name , list , encodeCollectionCatalog )
2026-10-17 18:45:39 +00:00
default :
2026-10-26 03:30:19 +00:00
return definitionFailure ( "Unable to list the selected PromptKit catalog." , commandkit . Explanation { } , fmt . Errorf ( "unknown PromptKit catalog %q" , name ) )
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-26 03:30:19 +00:00
catalog string ,
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 {
2026-10-26 03:30:19 +00:00
return definitionFailure ( "PromptKit catalog listing was canceled." , commandkit . Explanation { } , err )
2026-10-12 02:17:54 +00:00
}
}
var state S
if suite . NewState != nil {
state = suite . NewState ( )
}
if invocation . Context != nil {
if err := invocation . Context . Err ( ) ; err != nil {
2026-10-26 03:30:19 +00:00
return definitionFailure ( "PromptKit catalog listing was canceled." , commandkit . Explanation { } , err )
2026-10-12 02:17:54 +00:00
}
}
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-26 03:30:19 +00:00
queuePromptDiagnostics ( invocation . Reporter ( ) , acquired . Diagnostics )
2026-10-12 02:17:54 +00:00
if err != nil {
2026-10-26 03:30:19 +00:00
return definitionFailure ( fmt . Sprintf ( "Unable to list PromptKit %s." , catalog ) , acquired . Explanation , err )
2026-10-12 02:17:54 +00:00
}
if invocation . Context != nil {
if err := invocation . Context . Err ( ) ; err != nil {
2026-10-26 03:30:19 +00:00
return definitionFailure ( "PromptKit catalog listing was canceled." , acquired . Explanation , err )
2026-10-12 02:17:54 +00:00
}
}
2026-10-26 03:30:19 +00:00
_ = invocation . Reporter ( ) . RenderOutput ( func ( writer io . Writer ) error {
output , err := encode ( acquired . Value )
if err != nil {
return err
}
_ , err = writer . Write ( output )
return err
} )
return commandkit . Success ( )
2026-10-12 02:17:54 +00:00
}
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 {
2026-10-26 03:30:19 +00:00
return definitionFailure ( "PromptKit inspection was canceled." , commandkit . Explanation { } , err )
2026-10-11 01:46:54 +00:00
}
}
var state S
if suite . NewState != nil {
state = suite . NewState ( )
}
if invocation . Context != nil {
if err := invocation . Context . Err ( ) ; err != nil {
2026-10-26 03:30:19 +00:00
return definitionFailure ( "PromptKit inspection was canceled." , commandkit . Explanation { } , err )
2026-10-11 01:46:54 +00:00
}
}
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 )
2026-10-26 03:30:19 +00:00
queuePromptDiagnostics ( invocation . Reporter ( ) , acquired . Diagnostics )
2026-10-11 01:46:54 +00:00
if err != nil {
2026-10-26 03:30:19 +00:00
return definitionFailure ( fmt . Sprintf ( "Unable to inspect PromptKit prompt %q." , input . id ) , acquired . Explanation , err )
2026-10-11 01:46:54 +00:00
}
if acquired . Value == nil {
2026-10-26 03:30:19 +00:00
return definitionFailure ( "PromptKit prompt inspection returned no result." , acquired . Explanation , nil )
2026-10-11 01:46:54 +00:00
}
if invocation . Context != nil {
if err := invocation . Context . Err ( ) ; err != nil {
2026-10-26 03:30:19 +00:00
return definitionFailure ( fmt . Sprintf ( "Inspection of PromptKit prompt %q was canceled." , input . id ) , acquired . Explanation , err )
2026-10-11 01:46:54 +00:00
}
}
2026-10-26 03:30:19 +00:00
return renderPromptReport ( invocation , acquired . Value , input . format )
2026-10-11 01:46:54 +00:00
}
request := ProfileRequest { Selection : input . selection , ID : input . id }
acquired , err := suite . InspectProfile ( invocation . Context , request , input . selected , state )
2026-10-26 03:30:19 +00:00
queuePromptDiagnostics ( invocation . Reporter ( ) , acquired . Diagnostics )
2026-10-11 01:46:54 +00:00
if err != nil {
2026-10-26 03:30:19 +00:00
return definitionFailure ( fmt . Sprintf ( "Unable to inspect PromptKit profile %q." , input . id ) , acquired . Explanation , err )
2026-10-11 01:46:54 +00:00
}
if acquired . Value == nil {
2026-10-26 03:30:19 +00:00
return definitionFailure ( "PromptKit profile inspection returned no result." , acquired . Explanation , nil )
2026-10-11 01:46:54 +00:00
}
if invocation . Context != nil {
if err := invocation . Context . Err ( ) ; err != nil {
2026-10-26 03:30:19 +00:00
return definitionFailure ( fmt . Sprintf ( "Inspection of PromptKit profile %q was canceled." , input . id ) , acquired . Explanation , err )
2026-10-11 01:46:54 +00:00
}
}
2026-10-26 03:30:19 +00:00
return renderProfileReport ( invocation , acquired . Value , input . format )
2026-10-11 01:46:54 +00:00
}
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 {
2026-10-26 03:30:19 +00:00
return definitionFailure ( "PromptKit requirements inspection was canceled." , commandkit . Explanation { } , err )
2026-10-12 17:05:33 +00:00
}
}
var state S
if suite . NewState != nil {
state = suite . NewState ( )
}
if invocation . Context != nil {
if err := invocation . Context . Err ( ) ; err != nil {
2026-10-26 03:30:19 +00:00
return definitionFailure ( "PromptKit requirements inspection was canceled." , commandkit . Explanation { } , err )
2026-10-12 17:05:33 +00:00
}
}
request := RequirementsRequest { Selection : input . selection , Inspection : input . inspection }
acquired , err := suite . InspectRequirements ( invocation . Context , request , input . selected , state )
2026-10-26 03:30:19 +00:00
queuePromptDiagnostics ( invocation . Reporter ( ) , acquired . Diagnostics )
2026-10-12 17:05:33 +00:00
if err != nil {
2026-10-26 03:30:19 +00:00
return definitionFailure ( fmt . Sprintf ( "Unable to inspect requirements for PromptKit prompt %q." , input . inspection . PromptID ) , acquired . Explanation , err )
2026-10-12 17:05:33 +00:00
}
if acquired . Value == nil {
2026-10-26 03:30:19 +00:00
return definitionFailure ( "PromptKit requirements inspection returned no result." , acquired . Explanation , nil )
2026-10-12 17:05:33 +00:00
}
if invocation . Context != nil {
if err := invocation . Context . Err ( ) ; err != nil {
2026-10-26 03:30:19 +00:00
return definitionFailure ( fmt . Sprintf ( "Requirements inspection for PromptKit prompt %q was canceled." , input . inspection . PromptID ) , acquired . Explanation , err )
2026-10-12 17:05:33 +00:00
}
}
2026-10-26 03:30:19 +00:00
_ = invocation . Reporter ( ) . RenderOutput ( func ( writer io . Writer ) error {
return encodePromptYAML ( writer , requirementsReportFromInspection ( acquired . Value ) )
} )
return commandkit . Success ( )
2026-10-12 17:05:33 +00:00
}
2026-10-26 03:30:19 +00:00
func renderPromptReport ( invocation commandkit . Invocation , inspection * promptkit . PromptInspection , format string ) commandkit . Outcome {
_ = invocation . Reporter ( ) . RenderOutput ( func ( writer io . Writer ) error {
report := promptReportFromInspection ( inspection )
if format == "yaml" {
return encodePromptYAML ( writer , report )
2026-10-11 01:46:54 +00:00
}
2026-10-26 03:30:19 +00:00
_ , err := io . WriteString ( writer , formatPromptText ( report ) )
return err
} )
return commandkit . Success ( )
2026-10-11 01:46:54 +00:00
}
2026-10-26 03:30:19 +00:00
func renderProfileReport ( invocation commandkit . Invocation , inspection * promptkit . ProfileInspection , format string ) commandkit . Outcome {
_ = invocation . Reporter ( ) . RenderOutput ( func ( writer io . Writer ) error {
report := profileReportFromInspection ( inspection )
if format == "yaml" {
return encodePromptYAML ( writer , report )
2026-10-11 01:46:54 +00:00
}
text , err := formatProfileText ( report )
if err != nil {
2026-10-26 03:30:19 +00:00
return err
2026-10-11 01:46:54 +00:00
}
2026-10-26 03:30:19 +00:00
_ , err = io . WriteString ( writer , text )
return err
} )
return commandkit . Success ( )
2026-10-11 01:46:54 +00:00
}
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-26 03:30:19 +00:00
func queuePromptDiagnostics ( reporter * commandkit . Reporter , diagnostics [ ] commandkit . Diagnostic ) {
deferred := reporter . AtEnd ( )
for _ , diagnostic := range diagnostics {
if diagnostic . Write == nil {
continue
}
switch diagnostic . Severity {
case commandkit . SeverityInfo :
_ = deferred . Info ( "" , diagnostic . Write )
case commandkit . SeverityDebug :
_ = deferred . Debug ( "" , diagnostic . Write )
case commandkit . SeverityProgress :
_ = reporter . Progress ( "" , diagnostic . Write )
default :
_ = deferred . Warn ( "" , diagnostic . Write )
2026-10-11 01:46:54 +00:00
}
}
}
2026-10-26 03:30:19 +00:00
func definitionFailure ( message string , explanation commandkit . Explanation , cause error ) commandkit . Outcome {
outcome := commandkit . Failure ( message )
if explanation . Message != "" || len ( explanation . Details ) > 0 {
outcome = outcome . WithExplanation ( explanation . Message , explanation . Details ... )
}
if cause != nil {
outcome = outcome . WithCause ( cause )
}
2026-10-11 01:46:54 +00:00
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 }