package cli import ( "context" "errors" "flag" "fmt" "io" "io/fs" "path/filepath" "strings" "gitea.maximumdirect.net/eric/promptkit" appconfig "gitea.maximumdirect.net/eric/scriptorium/internal/config" appformat "gitea.maximumdirect.net/eric/scriptorium/internal/format" ) type promptInspectionConfig struct { configPath, promptDir, promptID, promptVersion, outputPath string configExplicit bool outputFormat appformat.OutputFormat } type profileInspectionConfig struct { configPath, profileDir, profileID, outputPath string configExplicit bool outputFormat appformat.OutputFormat } // emptyPromptDefinitionFS satisfies Promptkit's engine-level prompt-source // requirement without exposing the caller's working directory. Profile // inspection never reads this source. type emptyPromptDefinitionFS struct{} func (emptyPromptDefinitionFS) Open(name string) (fs.File, error) { return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist} } func inspectCommand(args []string, stdout, stderr io.Writer) int { if len(args) == 0 { fmt.Fprintln(stderr, "inspect parse error: inspection mode is required") return ExitRuntimeError } if args[0] == "profile" { return inspectProfileCommand(args[1:], stdout, stderr) } if args[0] != "prompt" { fmt.Fprintln(stderr, "inspect parse error: unknown inspection mode") return ExitRuntimeError } cfg, err := parsePromptInspectionArgs(args[1:]) if err != nil { fmt.Fprintf(stderr, "inspect parse error: %v\n", err) return ExitRuntimeError } settings, err := resolveAppSettingsForPromptInspection(cfg) if err != nil { fmt.Fprintf(stderr, "inspect error: %v\n", err) return ExitRuntimeError } engine, err := newEngine(settings) if err != nil { fmt.Fprintf(stderr, "engine error: %v\n", err) return ExitRuntimeError } inspection, err := engine.InspectPrompt(context.Background(), cfg.promptID, cfg.promptVersion) if err != nil { fmt.Fprintf(stderr, "inspect error: %v\n", err) return ExitRuntimeError } data, err := appformat.FormatPromptInspection(inspection, cfg.outputFormat) if err != nil { fmt.Fprintf(stderr, "inspect error: %v\n", err) return ExitRuntimeError } if err := writeOutput(stdout, cfg.outputPath, data); err != nil { fmt.Fprintf(stderr, "output write error: %v\n", err) return ExitRuntimeError } return ExitOK } func inspectProfileCommand(args []string, stdout, stderr io.Writer) int { cfg, err := parseProfileInspectionArgs(args) if err != nil { fmt.Fprintf(stderr, "inspect parse error: %v\n", err) return ExitRuntimeError } settings, err := resolveAppSettingsForProfileInspection(cfg) if err != nil { fmt.Fprintf(stderr, "inspect error: %v\n", err) return ExitRuntimeError } engine, err := newEngine(settings, promptkit.WithPromptFS(emptyPromptDefinitionFS{}, ".")) if err != nil { fmt.Fprintf(stderr, "engine error: %v\n", err) return ExitRuntimeError } inspection, err := engine.InspectProfile(context.Background(), cfg.profileID) if err != nil { fmt.Fprintf(stderr, "inspect error: %v\n", err) return ExitRuntimeError } data, err := appformat.FormatProfileInspection(inspection, cfg.outputFormat) if err != nil { fmt.Fprintf(stderr, "inspect error: %v\n", err) return ExitRuntimeError } if err := writeOutput(stdout, cfg.outputPath, data); err != nil { fmt.Fprintf(stderr, "output write error: %v\n", err) return ExitRuntimeError } return ExitOK } func parseProfileInspectionArgs(args []string) (*profileInspectionConfig, error) { cfg := &profileInspectionConfig{outputFormat: appformat.DefaultOutputFormat} fs := flag.NewFlagSet("inspect profile", flag.ContinueOnError) fs.SetOutput(io.Discard) registerConfigPathFlag(fs, &cfg.configPath) fs.StringVar(&cfg.profileDir, "profile-dir", "", "directory containing execution profiles") fs.StringVar(&cfg.profileID, "profile", "", "profile ID to inspect") rawFormat := "" fs.StringVar(&rawFormat, "format", "", "output format: text or json") fs.StringVar(&cfg.outputPath, "out", "", "optional output file path") if err := fs.Parse(args); err != nil { return nil, err } cfg.configExplicit = flagWasSet(fs, "config") if fs.NArg() > 0 { return nil, fmt.Errorf("unexpected positional args: %v", fs.Args()) } if strings.TrimSpace(cfg.profileID) == "" { return nil, errors.New("--profile is required") } format, err := appformat.ParseOutputFormat(rawFormat) if err != nil { return nil, err } cfg.outputFormat = format if cfg.outputPath != "" { cfg.outputPath = filepath.Clean(cfg.outputPath) } return cfg, nil } func resolveAppSettingsForProfileInspection(cfg *profileInspectionConfig) (engineSettings, error) { settings, err := resolveAppSettingsWithConfigPresence(cfg.configPath, cfg.configExplicit, appconfig.CLIOverrides{ ProfileDir: cfg.profileDir, }) if err != nil { return engineSettings{}, err } return engineSettings{profileDir: settings.ProfileDir, backends: settings.Backends}, nil } func parsePromptInspectionArgs(args []string) (*promptInspectionConfig, error) { cfg := &promptInspectionConfig{outputFormat: appformat.DefaultOutputFormat} fs := flag.NewFlagSet("inspect prompt", flag.ContinueOnError) fs.SetOutput(io.Discard) registerConfigPathFlag(fs, &cfg.configPath) fs.StringVar(&cfg.promptDir, "prompt-dir", "", "directory containing prompt definitions") fs.StringVar(&cfg.promptID, "prompt", "", "prompt ID to inspect") fs.StringVar(&cfg.promptVersion, "prompt-version", "", "optional prompt version") rawFormat := "" fs.StringVar(&rawFormat, "format", "", "output format: text or json") fs.StringVar(&cfg.outputPath, "out", "", "optional output file path") if err := fs.Parse(args); err != nil { return nil, err } cfg.configExplicit = flagWasSet(fs, "config") if fs.NArg() > 0 { return nil, fmt.Errorf("unexpected positional args: %v", fs.Args()) } if strings.TrimSpace(cfg.promptID) == "" { return nil, errors.New("--prompt is required") } format, err := appformat.ParseOutputFormat(rawFormat) if err != nil { return nil, err } cfg.outputFormat = format if cfg.outputPath != "" { cfg.outputPath = filepath.Clean(cfg.outputPath) } return cfg, nil } func resolveAppSettingsForPromptInspection(cfg *promptInspectionConfig) (engineSettings, error) { settings, err := resolveAppSettingsWithConfigPresence(cfg.configPath, cfg.configExplicit, appconfig.CLIOverrides{ PromptDir: cfg.promptDir, }) if err != nil { return engineSettings{}, err } if strings.TrimSpace(settings.PromptDir) == "" { return engineSettings{}, errors.New(errPromptDirRequired) } return engineSettings{promptDir: settings.PromptDir, backends: settings.Backends}, nil }