grokkit/cmd/recipe.go

140 lines
3.4 KiB
Go
Raw Normal View History

package cmd
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/spf13/cobra"
"gmgauthier.com/grokkit/config"
"gmgauthier.com/grokkit/internal/grok"
"gmgauthier.com/grokkit/internal/recipe"
)
var recipeCmd = &cobra.Command{
Use: "recipe",
Short: "Run a recipe (transactional sous-chef mode)",
}
var runCmd = &cobra.Command{
Use: "run [recipe-name|recipe.md]",
Short: "Execute a recipe",
Args: cobra.MinimumNArgs(1),
RunE: runRecipe,
}
var paramFlags []string
func init() {
runCmd.Flags().StringSliceVarP(&paramFlags, "param", "p", nil, "key=value parameters for the recipe (can be repeated)")
recipeCmd.AddCommand(runCmd)
rootCmd.AddCommand(recipeCmd)
}
func runRecipe(cmd *cobra.Command, args []string) error {
nameOrPath := args[0]
recipePath, err := resolveRecipePath(nameOrPath)
if err != nil {
return err
}
// 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])
// Clean bool handling that satisfies staticcheck
switch strings.ToLower(value) {
case "true", "1", "yes", "on":
params[key] = true
case "false", "0", "no", "off":
params[key] = false
default:
params[key] = value
}
}
}
r, err := recipe.Load(recipePath, params)
if err != nil {
return fmt.Errorf("failed to load recipe: %w", err)
}
flagModel, _ := cmd.Flags().GetString("model")
model := config.GetModel("recipe", flagModel)
client := grok.NewClient()
runner := recipe.NewRunner(r, client, model)
return runner.Run()
}
// resolveRecipePath and findProjectRoot stay exactly as you have them
func resolveRecipePath(nameOrPath string) (string, error) {
if strings.Contains(nameOrPath, "/") || strings.HasSuffix(nameOrPath, ".md") {
if _, err := os.Stat(nameOrPath); err == nil {
return nameOrPath, nil
}
return "", fmt.Errorf("recipe file not found: %s", nameOrPath)
}
if !strings.HasSuffix(nameOrPath, ".md") {
nameOrPath += ".md"
}
projectRoot, err := findProjectRoot()
if err == nil {
local := filepath.Join(projectRoot, ".grokkit", "recipes", nameOrPath)
if _, err := os.Stat(local); err == nil {
return local, nil
}
}
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)
var answer string
if _, err := fmt.Scanln(&answer); err != nil {
return "", err
}
if strings.HasPrefix(strings.ToLower(answer), "y") {
return global, nil
}
return "", fmt.Errorf("user declined global recipe")
}
return "", fmt.Errorf("recipe %q not found in project or global store", nameOrPath)
}
func findProjectRoot() (string, error) {
dir, err := os.Getwd()
if err != nil {
return "", err
}
for {
if _, err := os.Stat(filepath.Join(dir, ".git")); err == nil {
return dir, nil
}
if _, err := os.Stat(filepath.Join(dir, ".gitignore")); err == nil {
return dir, nil
}
if _, err := os.Stat(filepath.Join(dir, ".grokkit")); err == nil {
return dir, nil
}
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
return dir, nil
}
parent := filepath.Dir(dir)
if parent == dir {
break
}
dir = parent
}
return "", fmt.Errorf("not in a project")
}