main.go

  1package main
  2
  3import (
  4	"encoding/json"
  5	"fmt"
  6	"os"
  7
  8	"github.com/charmbracelet/crush/internal/config"
  9	"github.com/charmbracelet/crush/internal/llm/models"
 10)
 11
 12// JSONSchemaType represents a JSON Schema type
 13type JSONSchemaType struct {
 14	Type                 string           `json:"type,omitempty"`
 15	Description          string           `json:"description,omitempty"`
 16	Properties           map[string]any   `json:"properties,omitempty"`
 17	Required             []string         `json:"required,omitempty"`
 18	AdditionalProperties any              `json:"additionalProperties,omitempty"`
 19	Enum                 []any            `json:"enum,omitempty"`
 20	Items                map[string]any   `json:"items,omitempty"`
 21	OneOf                []map[string]any `json:"oneOf,omitempty"`
 22	AnyOf                []map[string]any `json:"anyOf,omitempty"`
 23	Default              any              `json:"default,omitempty"`
 24}
 25
 26func main() {
 27	schema := generateSchema()
 28
 29	// Pretty print the schema
 30	encoder := json.NewEncoder(os.Stdout)
 31	encoder.SetIndent("", "  ")
 32	if err := encoder.Encode(schema); err != nil {
 33		fmt.Fprintf(os.Stderr, "Error encoding schema: %v\n", err)
 34		os.Exit(1)
 35	}
 36}
 37
 38func generateSchema() map[string]any {
 39	schema := map[string]any{
 40		"$schema":     "http://json-schema.org/draft-07/schema#",
 41		"title":       "Crush Configuration",
 42		"description": "Configuration schema for the Crush application",
 43		"type":        "object",
 44		"properties":  map[string]any{},
 45	}
 46
 47	// Add Data configuration
 48	schema["properties"].(map[string]any)["data"] = map[string]any{
 49		"type":        "object",
 50		"description": "Storage configuration",
 51		"properties": map[string]any{
 52			"directory": map[string]any{
 53				"type":        "string",
 54				"description": "Directory where application data is stored",
 55				"default":     ".crush",
 56			},
 57		},
 58		"required": []string{"directory"},
 59	}
 60
 61	// Add working directory
 62	schema["properties"].(map[string]any)["wd"] = map[string]any{
 63		"type":        "string",
 64		"description": "Working directory for the application",
 65	}
 66
 67	// Add debug flags
 68	schema["properties"].(map[string]any)["debug"] = map[string]any{
 69		"type":        "boolean",
 70		"description": "Enable debug mode",
 71		"default":     false,
 72	}
 73
 74	schema["properties"].(map[string]any)["debugLSP"] = map[string]any{
 75		"type":        "boolean",
 76		"description": "Enable LSP debug mode",
 77		"default":     false,
 78	}
 79
 80	schema["properties"].(map[string]any)["contextPaths"] = map[string]any{
 81		"type":        "array",
 82		"description": "Context paths for the application",
 83		"items": map[string]any{
 84			"type": "string",
 85		},
 86		"default": []string{
 87			".github/copilot-instructions.md",
 88			".cursorrules",
 89			".cursor/rules/",
 90			"CLAUDE.md",
 91			"CLAUDE.local.md",
 92			"GEMINI.md",
 93			"gemini.md",
 94			"crush.md",
 95			"crush.local.md",
 96			"Crush.md",
 97			"Crush.local.md",
 98			"CRUSH.md",
 99			"CRUSH.local.md",
100		},
101	}
102
103	schema["properties"].(map[string]any)["tui"] = map[string]any{
104		"type":        "object",
105		"description": "Terminal User Interface configuration",
106		"properties": map[string]any{
107			"theme": map[string]any{
108				"type":        "string",
109				"description": "TUI theme name",
110				"default":     "crush",
111				"enum": []string{
112					"crush",
113					"catppuccin",
114					"dracula",
115					"flexoki",
116					"gruvbox",
117					"monokai",
118					"onedark",
119					"tokyonight",
120					"tron",
121				},
122			},
123		},
124	}
125
126	// Add MCP servers
127	schema["properties"].(map[string]any)["mcpServers"] = map[string]any{
128		"type":        "object",
129		"description": "Model Control Protocol server configurations",
130		"additionalProperties": map[string]any{
131			"type":        "object",
132			"description": "MCP server configuration",
133			"properties": map[string]any{
134				"command": map[string]any{
135					"type":        "string",
136					"description": "Command to execute for the MCP server",
137				},
138				"env": map[string]any{
139					"type":        "array",
140					"description": "Environment variables for the MCP server",
141					"items": map[string]any{
142						"type": "string",
143					},
144				},
145				"args": map[string]any{
146					"type":        "array",
147					"description": "Command arguments for the MCP server",
148					"items": map[string]any{
149						"type": "string",
150					},
151				},
152				"type": map[string]any{
153					"type":        "string",
154					"description": "Type of MCP server",
155					"enum":        []string{"stdio", "sse"},
156					"default":     "stdio",
157				},
158				"url": map[string]any{
159					"type":        "string",
160					"description": "URL for SSE type MCP servers",
161				},
162				"headers": map[string]any{
163					"type":        "object",
164					"description": "HTTP headers for SSE type MCP servers",
165					"additionalProperties": map[string]any{
166						"type": "string",
167					},
168				},
169			},
170			"required": []string{"command"},
171		},
172	}
173
174	// Add providers
175	providerSchema := map[string]any{
176		"type":        "object",
177		"description": "LLM provider configurations",
178		"additionalProperties": map[string]any{
179			"type":        "object",
180			"description": "Provider configuration",
181			"properties": map[string]any{
182				"apiKey": map[string]any{
183					"type":        "string",
184					"description": "API key for the provider",
185				},
186				"disabled": map[string]any{
187					"type":        "boolean",
188					"description": "Whether the provider is disabled",
189					"default":     false,
190				},
191			},
192		},
193	}
194
195	// Add known providers
196	knownProviders := []string{
197		string(models.ProviderAnthropic),
198		string(models.ProviderOpenAI),
199		string(models.ProviderGemini),
200		string(models.ProviderGROQ),
201		string(models.ProviderOpenRouter),
202		string(models.ProviderBedrock),
203		string(models.ProviderAzure),
204		string(models.ProviderVertexAI),
205	}
206
207	providerSchema["additionalProperties"].(map[string]any)["properties"].(map[string]any)["provider"] = map[string]any{
208		"type":        "string",
209		"description": "Provider type",
210		"enum":        knownProviders,
211	}
212
213	schema["properties"].(map[string]any)["providers"] = providerSchema
214
215	// Add agents
216	agentSchema := map[string]any{
217		"type":        "object",
218		"description": "Agent configurations",
219		"additionalProperties": map[string]any{
220			"type":        "object",
221			"description": "Agent configuration",
222			"properties": map[string]any{
223				"model": map[string]any{
224					"type":        "string",
225					"description": "Model ID for the agent",
226				},
227				"maxTokens": map[string]any{
228					"type":        "integer",
229					"description": "Maximum tokens for the agent",
230					"minimum":     1,
231				},
232				"reasoningEffort": map[string]any{
233					"type":        "string",
234					"description": "Reasoning effort for models that support it (OpenAI, Anthropic)",
235					"enum":        []string{"low", "medium", "high"},
236				},
237			},
238			"required": []string{"model"},
239		},
240	}
241
242	// Add model enum
243	modelEnum := []string{}
244	for modelID := range models.SupportedModels {
245		modelEnum = append(modelEnum, string(modelID))
246	}
247	agentSchema["additionalProperties"].(map[string]any)["properties"].(map[string]any)["model"].(map[string]any)["enum"] = modelEnum
248
249	// Add specific agent properties
250	agentProperties := map[string]any{}
251	knownAgents := []string{
252		string(config.AgentCoder),
253		string(config.AgentTask),
254		string(config.AgentTitle),
255	}
256
257	for _, agentName := range knownAgents {
258		agentProperties[agentName] = map[string]any{
259			"$ref": "#/definitions/agent",
260		}
261	}
262
263	// Create a combined schema that allows both specific agents and additional ones
264	combinedAgentSchema := map[string]any{
265		"type":                 "object",
266		"description":          "Agent configurations",
267		"properties":           agentProperties,
268		"additionalProperties": agentSchema["additionalProperties"],
269	}
270
271	schema["properties"].(map[string]any)["agents"] = combinedAgentSchema
272	schema["definitions"] = map[string]any{
273		"agent": agentSchema["additionalProperties"],
274	}
275
276	// Add LSP configuration
277	schema["properties"].(map[string]any)["lsp"] = map[string]any{
278		"type":        "object",
279		"description": "Language Server Protocol configurations",
280		"additionalProperties": map[string]any{
281			"type":        "object",
282			"description": "LSP configuration for a language",
283			"properties": map[string]any{
284				"disabled": map[string]any{
285					"type":        "boolean",
286					"description": "Whether the LSP is disabled",
287					"default":     false,
288				},
289				"command": map[string]any{
290					"type":        "string",
291					"description": "Command to execute for the LSP server",
292				},
293				"args": map[string]any{
294					"type":        "array",
295					"description": "Command arguments for the LSP server",
296					"items": map[string]any{
297						"type": "string",
298					},
299				},
300				"options": map[string]any{
301					"type":        "object",
302					"description": "Additional options for the LSP server",
303				},
304			},
305			"required": []string{"command"},
306		},
307	}
308
309	return schema
310}