package cli import ( "context" "fmt" "net/http" "reflect" "strings" "gitea.maximumdirect.net/eric/audita/v3/internal/core/config" "gitea.maximumdirect.net/eric/audita/v3/internal/framework/llm" "gitea.maximumdirect.net/eric/audita/v3/internal/prompts" "go.maximumdirect.net/ckconfig" "go.maximumdirect.net/ckprompt" commandkit "go.maximumdirect.net/commandkit" ck "go.maximumdirect.net/configkit" pk "go.maximumdirect.net/promptkit" "go.maximumdirect.net/releasekit/buildinfo" ) type invocationState struct { engines []engineEntry client *http.Client } type engineEntry struct { selection ckconfig.Selection engine *pk.Engine } func loadApplication(ctx context.Context, selection ckconfig.Selection, _ struct{}, _ *invocationState) (*ck.Result[config.Config], error) { if err := ctx.Err(); err != nil { return nil, err } var source ck.Source if selection.Config.Set { if strings.TrimSpace(selection.Config.Value) == "" { return nil, fmt.Errorf("--config requires a nonempty path") } source = ck.File(selection.Config.Value) } else { source = ck.SearchConfig("audita", ck.Searches(ck.WorkingDirectory(), ck.UserConfigDirectory(), ck.UnixSystemDirectories())) } return config.Load(ck.Input{Source: source}, selection.Overlay.Value, selection.Overrides...) } func loadPromptEngine(ctx context.Context, selection ckconfig.Selection, input struct{}, state *invocationState) (*pk.Engine, error) { loaded, err := loadApplication(ctx, selection, input, state) if err != nil { return nil, err } var client *http.Client if state != nil { client = state.client } return llm.NewEngine(ctx, loaded, client) } func capturePromptEngine(ctx context.Context, selection ckconfig.Selection, input struct{}, state *invocationState) (*pk.Engine, error) { if state != nil { for _, entry := range state.engines { if reflect.DeepEqual(entry.selection, selection) { return entry.engine, nil } } } engine, err := loadPromptEngine(ctx, selection, input, state) if err != nil { return nil, err } if state != nil { state.engines = append(state.engines, engineEntry{selection: selection, engine: engine}) } return engine, nil } func buildApplication(extra ...commandkit.Declaration) (*commandkit.App, error) { return buildApplicationWithClient(nil, extra...) } func buildApplicationWithClient(client *http.Client, extra ...commandkit.Declaration) (*commandkit.App, error) { appScope := ckconfig.FromConfigKit("app", "Audita application settings.", loadApplication) appScope.Features.Overrides = true promptScope := ckprompt.ConfigScope(loadPromptEngine) configCommand := ckconfig.Command(ckconfig.Suite[struct{}, *invocationState]{NewState: func() *invocationState { return &invocationState{client: client} }, Scopes: []ckconfig.Scope[struct{}, *invocationState]{appScope, promptScope}}) definitions := ckprompt.WithNamespaces(ckprompt.FromEngine(capturePromptEngine), pk.Namespaces{}) definitions.NewState = func() *invocationState { return &invocationState{client: client} } definitions = ckprompt.WithRequestValidation(definitions, capturePromptEngine, func(_ context.Context, seed ckprompt.ValidationRequest, _ struct{}, _ *invocationState) (pk.RunRequest, error) { return applicationPromptRequest(seed.Request, seed.Namespaces), nil }) definitions = ckprompt.WithRequestPreparation(definitions, capturePromptEngine, func(_ context.Context, seed ckprompt.PreparationRequest, _ struct{}, _ *invocationState) (pk.RunRequest, error) { return applicationPromptRequest(seed.Request, seed.Namespaces), nil }) definitions = ckprompt.WithSmokeTesting(definitions, capturePromptEngine, nil) declarations := []commandkit.Declaration{configCommand, commandkit.Completion("audita"), processCommand()} declarations = append(declarations, ckprompt.Declarations(definitions)...) declarations = append(declarations, extra...) return commandkit.New(commandkit.Identity{Name: "audita", Version: buildinfo.Version()}, declarations...) } // applicationPromptRequest supplies instructions only for Audita's maintained prompts. func applicationPromptRequest(request pk.RunRequest, namespaces *pk.Namespaces) pk.RunRequest { id := request.PromptID if !strings.Contains(id, "/") && namespaces != nil && namespaces.Prompt != "" { id = namespaces.Prompt + "/" + id } if _, ok := prompts.LookupMetadata(id); ok { request.Vars = prompts.WithSharedVariables(request.Vars) } return request }