schema.go

 1package cmd
 2
 3import (
 4	"encoding/json"
 5	"fmt"
 6	"reflect"
 7
 8	"github.com/charmbracelet/crush/internal/config"
 9	"github.com/invopop/jsonschema"
10	"github.com/spf13/cobra"
11)
12
13var schemaCmd = &cobra.Command{
14	Use:    "schema",
15	Short:  "Generate JSON schema for configuration",
16	Long:   "Generate JSON schema for the crush configuration file",
17	Hidden: true,
18	RunE: func(cmd *cobra.Command, args []string) error {
19		reflector := jsonschema.Reflector{
20			// Custom type mapper to handle csync.Map
21			Mapper: func(t reflect.Type) *jsonschema.Schema {
22				// Handle csync.Map[string, ProviderConfig] specifically
23				if t.String() == "csync.Map[string,github.com/charmbracelet/crush/internal/config.ProviderConfig]" {
24					return &jsonschema.Schema{
25						Type:        "object",
26						Description: "AI provider configurations",
27						AdditionalProperties: &jsonschema.Schema{
28							Ref: "#/$defs/ProviderConfig",
29						},
30					}
31				}
32				return nil
33			},
34		}
35		
36		// First reflect the config to get the main schema
37		schema := reflector.Reflect(&config.Config{})
38		
39		// Now manually add the ProviderConfig definition that might be missing
40		providerConfigSchema := reflector.ReflectFromType(reflect.TypeOf(config.ProviderConfig{}))
41		if schema.Definitions == nil {
42			schema.Definitions = make(map[string]*jsonschema.Schema)
43		}
44		
45		// Extract the actual definition from the nested schema
46		if providerConfigSchema.Definitions != nil && providerConfigSchema.Definitions["ProviderConfig"] != nil {
47			schema.Definitions["ProviderConfig"] = providerConfigSchema.Definitions["ProviderConfig"]
48			// Also add any other definitions from the provider config schema
49			for k, v := range providerConfigSchema.Definitions {
50				if k != "ProviderConfig" {
51					schema.Definitions[k] = v
52				}
53			}
54		} else {
55			// Fallback: use the schema itself if it's not nested
56			schema.Definitions["ProviderConfig"] = providerConfigSchema
57		}
58
59		schemaJSON, err := json.MarshalIndent(schema, "", "  ")
60		if err != nil {
61			return fmt.Errorf("failed to marshal schema: %w", err)
62		}
63
64		fmt.Println(string(schemaJSON))
65		return nil
66	},
67}
68
69func init() {
70	rootCmd.AddCommand(schemaCmd)
71}