From 95deb65f065c7e0b6228260af2acf1f26d8ec765 Mon Sep 17 00:00:00 2001 From: Greg Gauthier Date: Fri, 6 Mar 2026 23:37:00 +0000 Subject: [PATCH] feat(recipe): add --param flag support and improve project root detection - Introduce --param/-p flag to pass key=value parameters to recipes, parsed into a map with basic bool handling. - Expand findProjectRoot to detect more project types (e.g., .gitignore, pyproject.toml, CMakeLists.txt). - Clean up comments and minor refactoring in recipe resolution logic. --- cmd/recipe.go | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/cmd/recipe.go b/cmd/recipe.go index a8bb0ad..0ca6213 100644 --- a/cmd/recipe.go +++ b/cmd/recipe.go @@ -25,7 +25,10 @@ var runCmd = &cobra.Command{ RunE: runRecipe, } +var paramFlags []string + func init() { + runCmd.Flags().StringSliceVarP(¶mFlags, "param", "p", nil, "key=value parameters for the recipe (can be repeated)") recipeCmd.AddCommand(runCmd) rootCmd.AddCommand(recipeCmd) } @@ -38,15 +41,28 @@ func runRecipe(cmd *cobra.Command, args []string) error { return err } - // TODO: add --param support later; for now YAML defaults work + // Parse --param key=value into map params := make(map[string]any) + for _, p := range paramFlags { + if kv := strings.SplitN(p, "=", 2); len(kv) == 2 { + key := strings.TrimSpace(kv[0]) + value := strings.TrimSpace(kv[1]) + // simple bool detection + if value == "true" { + params[key] = true + } else if value == "false" { + params[key] = false + } else { + params[key] = value + } + } + } r, err := recipe.Load(recipePath, params) if err != nil { return fmt.Errorf("failed to load recipe: %w", err) } - // respect -m/--model flag + config exactly like every other command flagModel, _ := cmd.Flags().GetString("model") model := config.GetModel("recipe", flagModel) @@ -56,9 +72,8 @@ func runRecipe(cmd *cobra.Command, args []string) error { return runner.Run() } -// resolveRecipePath implements exactly the rules you specified +// resolveRecipePath and findProjectRoot stay exactly as you already have them func resolveRecipePath(nameOrPath string) (string, error) { - // explicit path wins immediately if strings.Contains(nameOrPath, "/") || strings.HasSuffix(nameOrPath, ".md") { if _, err := os.Stat(nameOrPath); err == nil { return nameOrPath, nil @@ -66,12 +81,10 @@ func resolveRecipePath(nameOrPath string) (string, error) { return "", fmt.Errorf("recipe file not found: %s", nameOrPath) } - // normalise to .md if !strings.HasSuffix(nameOrPath, ".md") { nameOrPath += ".md" } - // 1. Project-local first (primary source of truth) projectRoot, err := findProjectRoot() if err == nil { local := filepath.Join(projectRoot, ".grokkit", "recipes", nameOrPath) @@ -80,7 +93,6 @@ func resolveRecipePath(nameOrPath string) (string, error) { } } - // 2. Global XDG fallback with confirmation global := filepath.Join(os.Getenv("HOME"), ".local", "share", "grokkit", "recipes", nameOrPath) if _, err := os.Stat(global); err == nil { fmt.Printf("Recipe %q not found in project.\nFound globally at %s\nUse this one? [y/N] ", nameOrPath, global) @@ -97,7 +109,6 @@ func resolveRecipePath(nameOrPath string) (string, error) { return "", fmt.Errorf("recipe %q not found in project or global store", nameOrPath) } -// tiny helper used by almost every command already func findProjectRoot() (string, error) { dir, err := os.Getwd() if err != nil { @@ -107,10 +118,19 @@ func findProjectRoot() (string, error) { if _, err := os.Stat(filepath.Join(dir, ".git")); err == nil { return dir, nil } - if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { + if _, err := os.Stat(filepath.Join(dir, ".grokkit")); err == nil { return dir, nil } - if _, err := os.Stat(filepath.Join(dir, ".grokkit")); err == nil { + if _, err := os.Stat(filepath.Join(dir, ".gitignore")); err == nil { + return dir, nil + } + if _, err := os.Stat(filepath.Join(dir, "pyproject.toml")); err == nil { + return dir, nil + } + if _, err := os.Stat(filepath.Join(dir, "CmakeLists.txt")); err == nil { + return dir, nil + } + if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { return dir, nil } parent := filepath.Dir(dir)