on main help sign in
Merge pull request #326 from charmbracelet/schema
9b1fa90Kujtim Hoxha
.gitattributes+1
@@ -1 +1,2 @@
1*.golden linguist-generated=true -text
1*.golden linguist-generated=true -text
2+.github/crush-schema.json linguist-generated=true
.github/workflows/schema-update.ymlnew file+26
@@ -0,0 +1,26 @@
1+name: Update Schema
2+
3+on:
4+ push:
5+ branches: [main]
6+ paths:
7+ - "internal/config/**"
8+
9+jobs:
10+ update-schema:
11+ runs-on: ubuntu-latest
12+ steps:
13+ - uses: actions/checkout@v4
14+ with:
15+ token: ${{ secrets.GITHUB_TOKEN }}
16+ - uses: actions/setup-go@v5
17+ with:
18+ go-version-file: go.mod
19+ - run: go run . schema > ./schema.json
20+ - uses: stefanzweifel/git-auto-commit-action@778341af668090896ca464160c2def5d1d1a3eb0 # v5
21+ with:
22+ commit_message: "chore: auto-update generated files"
23+ branch: main
24+ commit_user_name: actions-user
25+ commit_user_email: actions@github.com
26+ commit_author: actions-user <actions@github.com>
README.md+6
@@ -139,6 +139,7 @@ like you would. LSPs can be added manually like so:
139
139
140```json
140```json
141{
141{
142+ "$schema": "https://charm.land/crush.json",
142 "lsp": {
143 "lsp": {
143 "go": {
144 "go": {
144 "command": "gopls"
145 "command": "gopls"
@@ -163,6 +164,7 @@ using `$(echo $VAR)` syntax.
163
164
164```json
165```json
165{
166{
167+ "$schema": "https://charm.land/crush.json",
166 "mcp": {
168 "mcp": {
167 "filesystem": {
169 "filesystem": {
168 "type": "stdio",
170 "type": "stdio",
@@ -198,6 +200,7 @@ permissions. Use this with care.
198
200
199```json
201```json
200{
202{
203+ "$schema": "https://charm.land/crush.json",
201 "permissions": {
204 "permissions": {
202 "allowed_tools": [
205 "allowed_tools": [
203 "view",
206 "view",
@@ -225,6 +228,7 @@ API. Don't forget to set `DEEPSEEK_API_KEY` in your environment.
225
228
226```json
229```json
227{
230{
231+ "$schema": "https://charm.land/crush.json",
228 "providers": {
232 "providers": {
229 "deepseek": {
233 "deepseek": {
230 "type": "openai",
234 "type": "openai",
@@ -253,6 +257,7 @@ Custom Anthropic-compatible providers follow this format:
253
257
254```json
258```json
255{
259{
260+ "$schema": "https://charm.land/crush.json",
256 "providers": {
261 "providers": {
257 "custom-anthropic": {
262 "custom-anthropic": {
258 "type": "anthropic",
263 "type": "anthropic",
@@ -303,6 +308,7 @@ config:
303
308
304```json
309```json
305{
310{
311+ "$schema": "https://charm.land/crush.json",
306 "options": {
312 "options": {
307 "debug": true,
313 "debug": true,
308 "debug_lsp": true
314 "debug_lsp": true
crush.json+1
@@ -1,4 +1,5 @@
1{
1{
2+ "$schema": "https://charm.land/crush.json",
2 "lsp": {
3 "lsp": {
3 "Go": {
4 "Go": {
4 "command": "gopls"
5 "command": "gopls"
go.mod+8−1
@@ -25,6 +25,7 @@ require (
25 github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec
25 github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec
26 github.com/fsnotify/fsnotify v1.9.0
26 github.com/fsnotify/fsnotify v1.9.0
27 github.com/google/uuid v1.6.0
27 github.com/google/uuid v1.6.0
28+ github.com/invopop/jsonschema v0.13.0
28 github.com/joho/godotenv v1.5.1
29 github.com/joho/godotenv v1.5.1
29 github.com/mark3labs/mcp-go v0.34.0
30 github.com/mark3labs/mcp-go v0.34.0
30 github.com/muesli/termenv v0.16.0
31 github.com/muesli/termenv v0.16.0
@@ -47,7 +48,13 @@ require (
47 mvdan.cc/sh/v3 v3.12.1-0.20250726150758-e256f53bade8
48 mvdan.cc/sh/v3 v3.12.1-0.20250726150758-e256f53bade8
48)
49)
49
50
51+require (
52+ github.com/bahlo/generic-list-go v0.2.0 // indirect
53+ github.com/buger/jsonparser v1.1.1 // indirect
54+ github.com/mailru/easyjson v0.7.7 // indirect
55+ github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
50−require golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
56+ golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
57+)
51
58
52require (
59require (
53 cloud.google.com/go v0.116.0 // indirect
60 cloud.google.com/go v0.116.0 // indirect
go.sum+11
@@ -64,8 +64,12 @@ github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3v
64github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
64github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
65github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
65github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
66github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
66github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
67+github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
68+github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
67github.com/bmatcuk/doublestar/v4 v4.9.0 h1:DBvuZxjdKkRP/dr4GVV4w2fnmrk5Hxc90T51LZjv0JA=
69github.com/bmatcuk/doublestar/v4 v4.9.0 h1:DBvuZxjdKkRP/dr4GVV4w2fnmrk5Hxc90T51LZjv0JA=
68github.com/bmatcuk/doublestar/v4 v4.9.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
70github.com/bmatcuk/doublestar/v4 v4.9.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
71+github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
72+github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
69github.com/charlievieth/fastwalk v1.0.11 h1:5sLT/q9+d9xMdpKExawLppqvXFZCVKf6JHnr2u/ufj8=
73github.com/charlievieth/fastwalk v1.0.11 h1:5sLT/q9+d9xMdpKExawLppqvXFZCVKf6JHnr2u/ufj8=
70github.com/charlievieth/fastwalk v1.0.11/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI=
74github.com/charlievieth/fastwalk v1.0.11/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI=
71github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250716191546-1e2ffbbcf5c5 h1:GTcMIfDQJKyNKS+xVt7GkNIwz+tBuQtIuiP50WpzNgs=
75github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250716191546-1e2ffbbcf5c5 h1:GTcMIfDQJKyNKS+xVt7GkNIwz+tBuQtIuiP50WpzNgs=
@@ -154,8 +158,11 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq
154github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
158github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
155github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
159github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
156github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
160github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
161+github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
162+github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
157github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
163github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
158github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
164github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
165+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
159github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
166github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
160github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
167github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
161github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
168github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -169,6 +176,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
169github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
176github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
170github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
177github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
171github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
178github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
179+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
180+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
172github.com/mark3labs/mcp-go v0.34.0 h1:eWy7WBGvhk6EyAAyVzivTCprE52iXJwNtvHV6Cv3bR0=
181github.com/mark3labs/mcp-go v0.34.0 h1:eWy7WBGvhk6EyAAyVzivTCprE52iXJwNtvHV6Cv3bR0=
173github.com/mark3labs/mcp-go v0.34.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
182github.com/mark3labs/mcp-go v0.34.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
174github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
183github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@@ -263,6 +272,8 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
263github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
272github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
264github.com/u-root/u-root v0.14.1-0.20250724181933-b01901710169 h1:f4cp2yGKkMuGpCwAyNEjzcw8szgVXmemK/wfOu4l5gc=
273github.com/u-root/u-root v0.14.1-0.20250724181933-b01901710169 h1:f4cp2yGKkMuGpCwAyNEjzcw8szgVXmemK/wfOu4l5gc=
265github.com/u-root/u-root v0.14.1-0.20250724181933-b01901710169/go.mod h1:/0Qr7qJeDwWxoKku2xKQ4Szc+SwBE3g9VE8jNiamsmc=
274github.com/u-root/u-root v0.14.1-0.20250724181933-b01901710169/go.mod h1:/0Qr7qJeDwWxoKku2xKQ4Szc+SwBE3g9VE8jNiamsmc=
275+github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
276+github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
266github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
277github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
267github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
278github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
268github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
279github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
internal/cmd/schema.gonew file+30
@@ -0,0 +1,30 @@
1+package cmd
2+
3+import (
4+ "encoding/json"
5+ "fmt"
6+
7+ "github.com/charmbracelet/crush/internal/config"
8+ "github.com/invopop/jsonschema"
9+ "github.com/spf13/cobra"
10+)
11+
12+var schemaCmd = &cobra.Command{
13+ Use: "schema",
14+ Short: "Generate JSON schema for configuration",
15+ Long: "Generate JSON schema for the crush configuration file",
16+ Hidden: true,
17+ RunE: func(cmd *cobra.Command, args []string) error {
18+ reflector := new(jsonschema.Reflector)
19+ bts, err := json.MarshalIndent(reflector.Reflect(&config.Config{}), "", " ")
20+ if err != nil {
21+ return fmt.Errorf("failed to marshal schema: %w", err)
22+ }
23+ fmt.Println(string(bts))
24+ return nil
25+ },
26+}
27+
28+func init() {
29+ rootCmd.AddCommand(schemaCmd)
30+}
internal/config/config.go+42−42
@@ -49,48 +49,48 @@ const (
49type SelectedModel struct {
49type SelectedModel struct {
50 // The model id as used by the provider API.
50 // The model id as used by the provider API.
51 // Required.
51 // Required.
52− Model string `json:"model"`
52+ Model string `json:"model" jsonschema:"required,description=The model ID as used by the provider API,example=gpt-4o"`
53 // The model provider, same as the key/id used in the providers config.
53 // The model provider, same as the key/id used in the providers config.
54 // Required.
54 // Required.
55− Provider string `json:"provider"`
55+ Provider string `json:"provider" jsonschema:"required,description=The model provider ID that matches a key in the providers config,example=openai"`
56
56
57 // Only used by models that use the openai provider and need this set.
57 // Only used by models that use the openai provider and need this set.
58− ReasoningEffort string `json:"reasoning_effort,omitempty"`
58+ ReasoningEffort string `json:"reasoning_effort,omitempty" jsonschema:"description=Reasoning effort level for OpenAI models that support it,enum=low,enum=medium,enum=high"`
59
59
60 // Overrides the default model configuration.
60 // Overrides the default model configuration.
61− MaxTokens int64 `json:"max_tokens,omitempty"`
61+ MaxTokens int64 `json:"max_tokens,omitempty" jsonschema:"description=Maximum number of tokens for model responses,minimum=1,maximum=200000,example=4096"`
62
62
63 // Used by anthropic models that can reason to indicate if the model should think.
63 // Used by anthropic models that can reason to indicate if the model should think.
64− Think bool `json:"think,omitempty"`
64+ Think bool `json:"think,omitempty" jsonschema:"description=Enable thinking mode for Anthropic models that support reasoning"`
65}
65}
66
66
67type ProviderConfig struct {
67type ProviderConfig struct {
68 // The provider's id.
68 // The provider's id.
69− ID string `json:"id,omitempty"`
69+ ID string `json:"id,omitempty" jsonschema:"description=Unique identifier for the provider,example=openai"`
70 // The provider's name, used for display purposes.
70 // The provider's name, used for display purposes.
71− Name string `json:"name,omitempty"`
71+ Name string `json:"name,omitempty" jsonschema:"description=Human-readable name for the provider,example=OpenAI"`
72 // The provider's API endpoint.
72 // The provider's API endpoint.
73− BaseURL string `json:"base_url,omitempty"`
73+ BaseURL string `json:"base_url,omitempty" jsonschema:"description=Base URL for the provider's API,format=uri,example=https://api.openai.com/v1"`
74 // The provider type, e.g. "openai", "anthropic", etc. if empty it defaults to openai.
74 // The provider type, e.g. "openai", "anthropic", etc. if empty it defaults to openai.
75− Type catwalk.Type `json:"type,omitempty"`
75+ Type catwalk.Type `json:"type,omitempty" jsonschema:"description=Provider type that determines the API format,enum=openai,enum=anthropic,enum=gemini,enum=azure,enum=vertexai,default=openai"`
76 // The provider's API key.
76 // The provider's API key.
77− APIKey string `json:"api_key,omitempty"`
77+ APIKey string `json:"api_key,omitempty" jsonschema:"description=API key for authentication with the provider,example=$OPENAI_API_KEY"`
78 // Marks the provider as disabled.
78 // Marks the provider as disabled.
79− Disable bool `json:"disable,omitempty"`
79+ Disable bool `json:"disable,omitempty" jsonschema:"description=Whether this provider is disabled,default=false"`
80
80
81 // Custom system prompt prefix.
81 // Custom system prompt prefix.
82− SystemPromptPrefix string `json:"system_prompt_prefix,omitempty"`
82+ SystemPromptPrefix string `json:"system_prompt_prefix,omitempty" jsonschema:"description=Custom prefix to add to system prompts for this provider"`
83
83
84 // Extra headers to send with each request to the provider.
84 // Extra headers to send with each request to the provider.
85− ExtraHeaders map[string]string `json:"extra_headers,omitempty"`
85+ ExtraHeaders map[string]string `json:"extra_headers,omitempty" jsonschema:"description=Additional HTTP headers to send with requests"`
86 // Extra body
86 // Extra body
87− ExtraBody map[string]any `json:"extra_body,omitempty"`
87+ ExtraBody map[string]any `json:"extra_body,omitempty" jsonschema:"description=Additional fields to include in request bodies"`
88
88
89 // Used to pass extra parameters to the provider.
89 // Used to pass extra parameters to the provider.
90 ExtraParams map[string]string `json:"-"`
90 ExtraParams map[string]string `json:"-"`
91
91
92 // The provider models
92 // The provider models
93− Models []catwalk.Model `json:"models,omitempty"`
93+ Models []catwalk.Model `json:"models,omitempty" jsonschema:"description=List of models available from this provider"`
94}
94}
95
95
96type MCPType string
96type MCPType string
@@ -102,41 +102,41 @@ const (
102)
102)
103
103
104type MCPConfig struct {
104type MCPConfig struct {
105− Command string `json:"command,omitempty" `
105+ Command string `json:"command,omitempty" jsonschema:"description=Command to execute for stdio MCP servers,example=npx"`
106− Env map[string]string `json:"env,omitempty"`
106+ Env map[string]string `json:"env,omitempty" jsonschema:"description=Environment variables to set for the MCP server"`
107− Args []string `json:"args,omitempty"`
107+ Args []string `json:"args,omitempty" jsonschema:"description=Arguments to pass to the MCP server command"`
108− Type MCPType `json:"type"`
108+ Type MCPType `json:"type" jsonschema:"required,description=Type of MCP connection,enum=stdio,enum=sse,enum=http,default=stdio"`
109− URL string `json:"url,omitempty"`
109+ URL string `json:"url,omitempty" jsonschema:"description=URL for HTTP or SSE MCP servers,format=uri,example=http://localhost:3000/mcp"`
110− Disabled bool `json:"disabled,omitempty"`
110+ Disabled bool `json:"disabled,omitempty" jsonschema:"description=Whether this MCP server is disabled,default=false"`
111
111
112 // TODO: maybe make it possible to get the value from the env
112 // TODO: maybe make it possible to get the value from the env
113− Headers map[string]string `json:"headers,omitempty"`
113+ Headers map[string]string `json:"headers,omitempty" jsonschema:"description=HTTP headers for HTTP/SSE MCP servers"`
114}
114}
115
115
116type LSPConfig struct {
116type LSPConfig struct {
117− Disabled bool `json:"enabled,omitempty"`
117+ Disabled bool `json:"enabled,omitempty" jsonschema:"description=Whether this LSP server is disabled,default=false"`
118− Command string `json:"command"`
118+ Command string `json:"command" jsonschema:"required,description=Command to execute for the LSP server,example=gopls"`
119− Args []string `json:"args,omitempty"`
119+ Args []string `json:"args,omitempty" jsonschema:"description=Arguments to pass to the LSP server command"`
120− Options any `json:"options,omitempty"`
120+ Options any `json:"options,omitempty" jsonschema:"description=LSP server-specific configuration options"`
121}
121}
122
122
123type TUIOptions struct {
123type TUIOptions struct {
124− CompactMode bool `json:"compact_mode,omitempty"`
124+ CompactMode bool `json:"compact_mode,omitempty" jsonschema:"description=Enable compact mode for the TUI interface,default=false"`
125 // Here we can add themes later or any TUI related options
125 // Here we can add themes later or any TUI related options
126}
126}
127
127
128type Permissions struct {
128type Permissions struct {
129− AllowedTools []string `json:"allowed_tools,omitempty"` // Tools that don't require permission prompts
129+ AllowedTools []string `json:"allowed_tools,omitempty" jsonschema:"description=List of tools that don't require permission prompts,example=bash,example=view"` // Tools that don't require permission prompts
130− SkipRequests bool `json:"-"` // Automatically accept all permissions (YOLO mode)
130+ SkipRequests bool `json:"-"` // Automatically accept all permissions (YOLO mode)
131}
131}
132
132
133type Options struct {
133type Options struct {
134− ContextPaths []string `json:"context_paths,omitempty"`
134+ ContextPaths []string `json:"context_paths,omitempty" jsonschema:"description=Paths to files containing context information for the AI,example=.cursorrules,example=CRUSH.md"`
135− TUI *TUIOptions `json:"tui,omitempty"`
135+ TUI *TUIOptions `json:"tui,omitempty" jsonschema:"description=Terminal user interface options"`
136− Debug bool `json:"debug,omitempty"`
136+ Debug bool `json:"debug,omitempty" jsonschema:"description=Enable debug logging,default=false"`
137− DebugLSP bool `json:"debug_lsp,omitempty"`
137+ DebugLSP bool `json:"debug_lsp,omitempty" jsonschema:"description=Enable debug logging for LSP servers,default=false"`
138− DisableAutoSummarize bool `json:"disable_auto_summarize,omitempty"`
138+ DisableAutoSummarize bool `json:"disable_auto_summarize,omitempty" jsonschema:"description=Disable automatic conversation summarization,default=false"`
139− DataDirectory string `json:"data_directory,omitempty"` // Relative to the cwd
139+ DataDirectory string `json:"data_directory,omitempty" jsonschema:"description=Directory for storing application data (relative to working directory),default=.crush,example=.crush"` // Relative to the cwd
140}
140}
141
141
142type MCPs map[string]MCPConfig
142type MCPs map[string]MCPConfig
@@ -219,7 +219,7 @@ type Agent struct {
219 // This is the id of the system prompt used by the agent
219 // This is the id of the system prompt used by the agent
220 Disabled bool `json:"disabled,omitempty"`
220 Disabled bool `json:"disabled,omitempty"`
221
221
222− Model SelectedModelType `json:"model"`
222+ Model SelectedModelType `json:"model" jsonschema:"required,description=The model type to use for this agent,enum=large,enum=small,default=large"`
223
223
224 // The available tools for the agent
224 // The available tools for the agent
225 // if this is nil, all tools are available
225 // if this is nil, all tools are available
@@ -242,18 +242,18 @@ type Agent struct {
242// Config holds the configuration for crush.
242// Config holds the configuration for crush.
243type Config struct {
243type Config struct {
244 // We currently only support large/small as values here.
244 // We currently only support large/small as values here.
245− Models map[SelectedModelType]SelectedModel `json:"models,omitempty"`
245+ Models map[SelectedModelType]SelectedModel `json:"models,omitempty" jsonschema:"description=Model configurations for different model types,example={\"large\":{\"model\":\"gpt-4o\",\"provider\":\"openai\"}}"`
246
246
247 // The providers that are configured
247 // The providers that are configured
248− Providers *csync.Map[string, ProviderConfig] `json:"providers,omitempty"`
248+ Providers *csync.Map[string, ProviderConfig] `json:"providers,omitempty" jsonschema:"description=AI provider configurations"`
249
249
250− MCP MCPs `json:"mcp,omitempty"`
250+ MCP MCPs `json:"mcp,omitempty" jsonschema:"description=Model Context Protocol server configurations"`
251
251
252− LSP LSPs `json:"lsp,omitempty"`
252+ LSP LSPs `json:"lsp,omitempty" jsonschema:"description=Language Server Protocol configurations"`
253
253
254− Options *Options `json:"options,omitempty"`
254+ Options *Options `json:"options,omitempty" jsonschema:"description=General application options"`
255
255
256− Permissions *Permissions `json:"permissions,omitempty"`
256+ Permissions *Permissions `json:"permissions,omitempty" jsonschema:"description=Permission settings for tool usage"`
257
257
258 // Internal
258 // Internal
259 workingDir string `json:"-"`
259 workingDir string `json:"-"`
internal/csync/maps.go+5
@@ -96,6 +96,11 @@ var (
96 _ json.Marshaler = &Map[string, any]{}
96 _ json.Marshaler = &Map[string, any]{}
97)
97)
98
98
99+func (Map[K, V]) JSONSchemaAlias() any { //nolint
100+ m := map[K]V{}
101+ return m
102+}
103+
99// UnmarshalJSON implements json.Unmarshaler.
104// UnmarshalJSON implements json.Unmarshaler.
100func (m *Map[K, V]) UnmarshalJSON(data []byte) error {
105func (m *Map[K, V]) UnmarshalJSON(data []byte) error {
101 m.mu.Lock()
106 m.mu.Lock()
schema.jsonnew file+393
@@ -0,0 +1,393 @@
1+{
2+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3+ "$id": "https://github.com/charmbracelet/crush/internal/config/config",
4+ "$ref": "#/$defs/Config",
5+ "$defs": {
6+ "Config": {
7+ "properties": {
8+ "models": {
9+ "additionalProperties": {
10+ "$ref": "#/$defs/SelectedModel"
11+ },
12+ "type": "object",
13+ "description": "Model configurations for different model types"
14+ },
15+ "providers": {
16+ "additionalProperties": {
17+ "$ref": "#/$defs/ProviderConfig"
18+ },
19+ "type": "object",
20+ "description": "AI provider configurations"
21+ },
22+ "mcp": {
23+ "$ref": "#/$defs/MCPs",
24+ "description": "Model Context Protocol server configurations"
25+ },
26+ "lsp": {
27+ "$ref": "#/$defs/LSPs",
28+ "description": "Language Server Protocol configurations"
29+ },
30+ "options": {
31+ "$ref": "#/$defs/Options",
32+ "description": "General application options"
33+ },
34+ "permissions": {
35+ "$ref": "#/$defs/Permissions",
36+ "description": "Permission settings for tool usage"
37+ }
38+ },
39+ "additionalProperties": false,
40+ "type": "object"
41+ },
42+ "LSPConfig": {
43+ "properties": {
44+ "enabled": {
45+ "type": "boolean",
46+ "description": "Whether this LSP server is disabled",
47+ "default": false
48+ },
49+ "command": {
50+ "type": "string",
51+ "description": "Command to execute for the LSP server",
52+ "examples": [
53+ "gopls"
54+ ]
55+ },
56+ "args": {
57+ "items": {
58+ "type": "string"
59+ },
60+ "type": "array",
61+ "description": "Arguments to pass to the LSP server command"
62+ },
63+ "options": {
64+ "description": "LSP server-specific configuration options"
65+ }
66+ },
67+ "additionalProperties": false,
68+ "type": "object",
69+ "required": [
70+ "command"
71+ ]
72+ },
73+ "LSPs": {
74+ "additionalProperties": {
75+ "$ref": "#/$defs/LSPConfig"
76+ },
77+ "type": "object"
78+ },
79+ "MCPConfig": {
80+ "properties": {
81+ "command": {
82+ "type": "string",
83+ "description": "Command to execute for stdio MCP servers",
84+ "examples": [
85+ "npx"
86+ ]
87+ },
88+ "env": {
89+ "additionalProperties": {
90+ "type": "string"
91+ },
92+ "type": "object",
93+ "description": "Environment variables to set for the MCP server"
94+ },
95+ "args": {
96+ "items": {
97+ "type": "string"
98+ },
99+ "type": "array",
100+ "description": "Arguments to pass to the MCP server command"
101+ },
102+ "type": {
103+ "type": "string",
104+ "enum": [
105+ "stdio",
106+ "sse",
107+ "http"
108+ ],
109+ "description": "Type of MCP connection",
110+ "default": "stdio"
111+ },
112+ "url": {
113+ "type": "string",
114+ "format": "uri",
115+ "description": "URL for HTTP or SSE MCP servers",
116+ "examples": [
117+ "http://localhost:3000/mcp"
118+ ]
119+ },
120+ "disabled": {
121+ "type": "boolean",
122+ "description": "Whether this MCP server is disabled",
123+ "default": false
124+ },
125+ "headers": {
126+ "additionalProperties": {
127+ "type": "string"
128+ },
129+ "type": "object",
130+ "description": "HTTP headers for HTTP/SSE MCP servers"
131+ }
132+ },
133+ "additionalProperties": false,
134+ "type": "object",
135+ "required": [
136+ "type"
137+ ]
138+ },
139+ "MCPs": {
140+ "additionalProperties": {
141+ "$ref": "#/$defs/MCPConfig"
142+ },
143+ "type": "object"
144+ },
145+ "Model": {
146+ "properties": {
147+ "id": {
148+ "type": "string"
149+ },
150+ "name": {
151+ "type": "string"
152+ },
153+ "cost_per_1m_in": {
154+ "type": "number"
155+ },
156+ "cost_per_1m_out": {
157+ "type": "number"
158+ },
159+ "cost_per_1m_in_cached": {
160+ "type": "number"
161+ },
162+ "cost_per_1m_out_cached": {
163+ "type": "number"
164+ },
165+ "context_window": {
166+ "type": "integer"
167+ },
168+ "default_max_tokens": {
169+ "type": "integer"
170+ },
171+ "can_reason": {
172+ "type": "boolean"
173+ },
174+ "has_reasoning_efforts": {
175+ "type": "boolean"
176+ },
177+ "default_reasoning_effort": {
178+ "type": "string"
179+ },
180+ "supports_attachments": {
181+ "type": "boolean"
182+ }
183+ },
184+ "additionalProperties": false,
185+ "type": "object",
186+ "required": [
187+ "id",
188+ "name",
189+ "cost_per_1m_in",
190+ "cost_per_1m_out",
191+ "cost_per_1m_in_cached",
192+ "cost_per_1m_out_cached",
193+ "context_window",
194+ "default_max_tokens",
195+ "can_reason",
196+ "has_reasoning_efforts",
197+ "supports_attachments"
198+ ]
199+ },
200+ "Options": {
201+ "properties": {
202+ "context_paths": {
203+ "items": {
204+ "type": "string",
205+ "examples": [
206+ ".cursorrules",
207+ "CRUSH.md"
208+ ]
209+ },
210+ "type": "array",
211+ "description": "Paths to files containing context information for the AI"
212+ },
213+ "tui": {
214+ "$ref": "#/$defs/TUIOptions",
215+ "description": "Terminal user interface options"
216+ },
217+ "debug": {
218+ "type": "boolean",
219+ "description": "Enable debug logging",
220+ "default": false
221+ },
222+ "debug_lsp": {
223+ "type": "boolean",
224+ "description": "Enable debug logging for LSP servers",
225+ "default": false
226+ },
227+ "disable_auto_summarize": {
228+ "type": "boolean",
229+ "description": "Disable automatic conversation summarization",
230+ "default": false
231+ },
232+ "data_directory": {
233+ "type": "string",
234+ "description": "Directory for storing application data (relative to working directory)",
235+ "default": ".crush",
236+ "examples": [
237+ ".crush"
238+ ]
239+ }
240+ },
241+ "additionalProperties": false,
242+ "type": "object"
243+ },
244+ "Permissions": {
245+ "properties": {
246+ "allowed_tools": {
247+ "items": {
248+ "type": "string",
249+ "examples": [
250+ "bash",
251+ "view"
252+ ]
253+ },
254+ "type": "array",
255+ "description": "List of tools that don't require permission prompts"
256+ }
257+ },
258+ "additionalProperties": false,
259+ "type": "object"
260+ },
261+ "ProviderConfig": {
262+ "properties": {
263+ "id": {
264+ "type": "string",
265+ "description": "Unique identifier for the provider",
266+ "examples": [
267+ "openai"
268+ ]
269+ },
270+ "name": {
271+ "type": "string",
272+ "description": "Human-readable name for the provider",
273+ "examples": [
274+ "OpenAI"
275+ ]
276+ },
277+ "base_url": {
278+ "type": "string",
279+ "format": "uri",
280+ "description": "Base URL for the provider's API",
281+ "examples": [
282+ "https://api.openai.com/v1"
283+ ]
284+ },
285+ "type": {
286+ "type": "string",
287+ "enum": [
288+ "openai",
289+ "anthropic",
290+ "gemini",
291+ "azure",
292+ "vertexai"
293+ ],
294+ "description": "Provider type that determines the API format",
295+ "default": "openai"
296+ },
297+ "api_key": {
298+ "type": "string",
299+ "description": "API key for authentication with the provider",
300+ "examples": [
301+ "$OPENAI_API_KEY"
302+ ]
303+ },
304+ "disable": {
305+ "type": "boolean",
306+ "description": "Whether this provider is disabled",
307+ "default": false
308+ },
309+ "system_prompt_prefix": {
310+ "type": "string",
311+ "description": "Custom prefix to add to system prompts for this provider"
312+ },
313+ "extra_headers": {
314+ "additionalProperties": {
315+ "type": "string"
316+ },
317+ "type": "object",
318+ "description": "Additional HTTP headers to send with requests"
319+ },
320+ "extra_body": {
321+ "type": "object",
322+ "description": "Additional fields to include in request bodies"
323+ },
324+ "models": {
325+ "items": {
326+ "$ref": "#/$defs/Model"
327+ },
328+ "type": "array",
329+ "description": "List of models available from this provider"
330+ }
331+ },
332+ "additionalProperties": false,
333+ "type": "object"
334+ },
335+ "SelectedModel": {
336+ "properties": {
337+ "model": {
338+ "type": "string",
339+ "description": "The model ID as used by the provider API",
340+ "examples": [
341+ "gpt-4o"
342+ ]
343+ },
344+ "provider": {
345+ "type": "string",
346+ "description": "The model provider ID that matches a key in the providers config",
347+ "examples": [
348+ "openai"
349+ ]
350+ },
351+ "reasoning_effort": {
352+ "type": "string",
353+ "enum": [
354+ "low",
355+ "medium",
356+ "high"
357+ ],
358+ "description": "Reasoning effort level for OpenAI models that support it"
359+ },
360+ "max_tokens": {
361+ "type": "integer",
362+ "maximum": 200000,
363+ "minimum": 1,
364+ "description": "Maximum number of tokens for model responses",
365+ "examples": [
366+ 4096
367+ ]
368+ },
369+ "think": {
370+ "type": "boolean",
371+ "description": "Enable thinking mode for Anthropic models that support reasoning"
372+ }
373+ },
374+ "additionalProperties": false,
375+ "type": "object",
376+ "required": [
377+ "model",
378+ "provider"
379+ ]
380+ },
381+ "TUIOptions": {
382+ "properties": {
383+ "compact_mode": {
384+ "type": "boolean",
385+ "description": "Enable compact mode for the TUI interface",
386+ "default": false
387+ }
388+ },
389+ "additionalProperties": false,
390+ "type": "object"
391+ }
392+ }
393+}