on main help sign in
feat: add swagger api docs generation
commit6dcaab1195c72bd81581cd23511bac6d86fc377b authorAyman Bagabas <ayman.bagabas@gmail.com> dateTue 17 Mar 2026, 14:23 (7 months ago) parent65974bb
8 files changed+781−68
Taskfile.yaml+13 go.mod+14−2 go.sum+28 internal/proto/requests.go+92 internal/server/config.go+198−59 internal/server/proto.go+423−7 internal/server/server.go+3 main.go+10
Taskfile.yaml+13
@@ -185,6 +185,19 @@ tasks:
185185 - go get charm.land/catwalk
186186 - go mod tidy
187187
188+ swag:
189+ desc: Generate OpenAPI spec from swag annotations
190+ cmds:
191+ - go run github.com/swaggo/swag/cmd/swag@v1.16.6 init --generalInfo main.go --dir . --output docs --parseDependency --parseInternal --parseDepth 5
192+ sources:
193+ - internal/server/*.go
194+ - internal/proto/*.go
195+ - main.go
196+ generates:
197+ - docs/docs.go
198+ - docs/swagger.json
199+ - docs/swagger.yaml
200+
188201 sqlc:
189202 desc: Generate code using SQLC
190203 cmds:
go.mod+14−2
@@ -14,6 +14,7 @@ require (
1414 charm.land/x/vcr v0.1.1
1515 github.com/JohannesKaufmann/html-to-markdown v1.6.0
1616 github.com/MakeNowJust/heredoc v1.0.0
17+ github.com/Microsoft/go-winio v0.6.2
1718 github.com/PuerkitoBio/goquery v1.12.0
1819 github.com/alecthomas/chroma/v2 v2.23.1
1920 github.com/atotto/clipboard v0.1.4
@@ -59,12 +60,15 @@ require (
5960 github.com/sourcegraph/jsonrpc2 v0.2.1
6061 github.com/spf13/cobra v1.10.2
6162 github.com/stretchr/testify v1.11.1
63+ github.com/swaggo/http-swagger/v2 v2.0.2
64+ github.com/swaggo/swag v1.16.6
6265 github.com/tidwall/gjson v1.18.0
6366 github.com/tidwall/sjson v1.2.5
6467 github.com/zeebo/xxh3 v1.1.0
6568 go.uber.org/goleak v1.3.0
6669 golang.org/x/net v0.52.0
6770 golang.org/x/sync v0.20.0
71+ golang.org/x/sys v0.42.0
6872 golang.org/x/text v0.35.0
6973 gopkg.in/natefinch/lumberjack.v2 v2.2.1
7074 gopkg.in/yaml.v3 v3.0.1
@@ -81,7 +85,7 @@ require (
8185 git.sr.ht/~jackmordaunt/go-toast v1.1.2 // indirect
8286 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect
8387 github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
84− github.com/Microsoft/go-winio v0.6.2 // indirect
88+ github.com/KyleBanks/depth v1.2.1 // indirect
8589 github.com/andybalholm/cascadia v1.3.3 // indirect
8690 github.com/aws/aws-sdk-go-v2 v1.41.3 // indirect
8791 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 // indirect
@@ -119,6 +123,10 @@ require (
119123 github.com/go-logr/logr v1.4.3 // indirect
120124 github.com/go-logr/stdr v1.2.2 // indirect
121125 github.com/go-ole/go-ole v1.3.0 // indirect
126+ github.com/go-openapi/jsonpointer v0.19.5 // indirect
127+ github.com/go-openapi/jsonreference v0.20.0 // indirect
128+ github.com/go-openapi/spec v0.20.6 // indirect
129+ github.com/go-openapi/swag v0.19.15 // indirect
122130 github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
123131 github.com/goccy/go-json v0.10.5 // indirect
124132 github.com/goccy/go-yaml v1.19.2 // indirect
@@ -135,6 +143,7 @@ require (
135143 github.com/inconshreveable/mousetrap v1.1.0 // indirect
136144 github.com/jackmordaunt/icns/v3 v3.0.1 // indirect
137145 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
146+ github.com/josharian/intern v1.0.0 // indirect
138147 github.com/kaptinlin/go-i18n v0.2.12 // indirect
139148 github.com/kaptinlin/jsonpointer v0.4.17 // indirect
140149 github.com/kaptinlin/jsonschema v0.7.5 // indirect
@@ -164,6 +173,7 @@ require (
164173 github.com/sergeymakinen/go-ico v1.0.0-beta.0 // indirect
165174 github.com/sethvargo/go-retry v0.3.0 // indirect
166175 github.com/spf13/pflag v1.0.9 // indirect
176+ github.com/swaggo/files/v2 v2.0.0 // indirect
167177 github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect
168178 github.com/tetratelabs/wazero v1.11.0 // indirect
169179 github.com/tidwall/match v1.1.1 // indirect
@@ -186,10 +196,11 @@ require (
186196 golang.org/x/crypto v0.49.0 // indirect
187197 golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
188198 golang.org/x/image v0.36.0 // indirect
199+ golang.org/x/mod v0.33.0 // indirect
189200 golang.org/x/oauth2 v0.36.0 // indirect
190− golang.org/x/sys v0.42.0 // indirect
191201 golang.org/x/term v0.41.0 // indirect
192202 golang.org/x/time v0.14.0 // indirect
203+ golang.org/x/tools v0.42.0 // indirect
193204 google.golang.org/api v0.269.0 // indirect
194205 google.golang.org/genai v1.49.0 // indirect
195206 google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
@@ -198,6 +209,7 @@ require (
198209 gopkg.in/dnaeon/go-vcr.v4 v4.0.6-0.20251110073552-01de4eb40290 // indirect
199210 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
200211 gopkg.in/warnings.v0 v0.1.2 // indirect
212+ gopkg.in/yaml.v2 v2.4.0 // indirect
201213 modernc.org/libc v1.68.0 // indirect
202214 modernc.org/mathutil v1.7.1 // indirect
203215 modernc.org/memory v1.11.0 // indirect
go.sum+28
@@ -36,6 +36,8 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mx
3636github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
3737github.com/JohannesKaufmann/html-to-markdown v1.6.0 h1:04VXMiE50YYfCfLboJCLcgqF5x+rHJnb1ssNmqpLH/k=
3838github.com/JohannesKaufmann/html-to-markdown v1.6.0/go.mod h1:NUI78lGg/a7vpEJTz/0uOcYMaibytE4BUOQS8k78yPQ=
39+github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
40+github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
3941github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
4042github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
4143github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
@@ -139,6 +141,7 @@ github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJ
139141github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
140142github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
141143github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
144+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
142145github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
143146github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
144147github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -188,6 +191,16 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
188191github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
189192github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
190193github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
194+github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
195+github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
196+github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
197+github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
198+github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
199+github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ=
200+github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
201+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
202+github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
203+github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
191204github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
192205github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
193206github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
@@ -241,6 +254,7 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
241254github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
242255github.com/jordanella/go-ansi-paintbrush v0.0.0-20240728195301-b7ad996ecf3d h1:on25kP+Sx7sxUMRQiA8gdcToAGet4DK/EIA30mXre+4=
243256github.com/jordanella/go-ansi-paintbrush v0.0.0-20240728195301-b7ad996ecf3d/go.mod h1:SV0W0APWP9MZ1/gfDQ/NzzTlWdIgYZ/ZbpN4d/UXRYw=
257+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
244258github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
245259github.com/kaptinlin/go-i18n v0.2.12 h1:ywDsvb4KDFddMC2dpI/rrIzGU2mWUSvHmWUm9BMsdl4=
246260github.com/kaptinlin/go-i18n v0.2.12/go.mod h1:pVcu9qsW5pOIOoZFJXesRYmLos1vMQrby70JPAoWmJU=
@@ -267,6 +281,9 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
267281github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
268282github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
269283github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
284+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
285+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
286+github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
270287github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
271288github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
272289github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@@ -299,6 +316,7 @@ github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt
299316github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
300317github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
301318github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
319+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
302320github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
303321github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
304322github.com/openai/openai-go/v3 v3.28.0 h1:2+FfrCVMdGXSQrBv1tLWtokm+BU7+3hJ/8rAHPQ63KM=
@@ -359,11 +377,18 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
359377github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
360378github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
361379github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
380+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
362381github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
363382github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
364383github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
365384github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
366385github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
386+github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw=
387+github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
388+github.com/swaggo/http-swagger/v2 v2.0.2 h1:FKCdLsl+sFCx60KFsyM0rDarwiUSZ8DqbfSyIKC9OBg=
389+github.com/swaggo/http-swagger/v2 v2.0.2/go.mod h1:r7/GBkAWIfK6E/OLnE8fXnviHiDeAHmgIyooa4xm3AQ=
390+github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
391+github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
367392github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG04SN9W+iWHCRyHqlVYILiSXziwk=
368393github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+am0jAwlQLddpoMDM+iewkxxt6nxUQ5nq5o=
369394github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
@@ -532,7 +557,9 @@ google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhH
532557google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
533558google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
534559gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
560+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
535561gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
562+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
536563gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
537564gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
538565gopkg.in/dnaeon/go-vcr.v4 v4.0.6-0.20251110073552-01de4eb40290 h1:g3ah7zaWmw41EtOgBNXpx8zk4HYuH3OMwB+qh1Dt834=
@@ -547,6 +574,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
547574gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
548575gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
549576gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
577+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
550578gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
551579gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
552580modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
internal/proto/requests.gonew file+92
@@ -0,0 +1,92 @@
1+package proto
2+
3+import "github.com/charmbracelet/crush/internal/config"
4+
5+// ConfigSetRequest represents a request to set a config field.
6+type ConfigSetRequest struct {
7+ Scope config.Scope `json:"scope"`
8+ Key string `json:"key"`
9+ Value any `json:"value"`
10+}
11+
12+// ConfigRemoveRequest represents a request to remove a config field.
13+type ConfigRemoveRequest struct {
14+ Scope config.Scope `json:"scope"`
15+ Key string `json:"key"`
16+}
17+
18+// ConfigModelRequest represents a request to update the preferred model.
19+type ConfigModelRequest struct {
20+ Scope config.Scope `json:"scope"`
21+ ModelType config.SelectedModelType `json:"model_type"`
22+ Model config.SelectedModel `json:"model"`
23+}
24+
25+// ConfigCompactRequest represents a request to set compact mode.
26+type ConfigCompactRequest struct {
27+ Scope config.Scope `json:"scope"`
28+ Enabled bool `json:"enabled"`
29+}
30+
31+// ConfigProviderKeyRequest represents a request to set a provider API key.
32+type ConfigProviderKeyRequest struct {
33+ Scope config.Scope `json:"scope"`
34+ ProviderID string `json:"provider_id"`
35+ APIKey any `json:"api_key"`
36+}
37+
38+// ConfigRefreshOAuthRequest represents a request to refresh an OAuth token.
39+type ConfigRefreshOAuthRequest struct {
40+ Scope config.Scope `json:"scope"`
41+ ProviderID string `json:"provider_id"`
42+}
43+
44+// ImportCopilotResponse represents the response from importing Copilot credentials.
45+type ImportCopilotResponse struct {
46+ Token any `json:"token"`
47+ Success bool `json:"success"`
48+}
49+
50+// ProjectNeedsInitResponse represents whether a project needs initialization.
51+type ProjectNeedsInitResponse struct {
52+ NeedsInit bool `json:"needs_init"`
53+}
54+
55+// ProjectInitPromptResponse represents the project initialization prompt.
56+type ProjectInitPromptResponse struct {
57+ Prompt string `json:"prompt"`
58+}
59+
60+// LSPStartRequest represents a request to start an LSP for a path.
61+type LSPStartRequest struct {
62+ Path string `json:"path"`
63+}
64+
65+// FileTrackerReadRequest represents a request to record a file read.
66+type FileTrackerReadRequest struct {
67+ SessionID string `json:"session_id"`
68+ Path string `json:"path"`
69+}
70+
71+// MCPNameRequest represents a request targeting a named MCP server.
72+type MCPNameRequest struct {
73+ Name string `json:"name"`
74+}
75+
76+// MCPReadResourceRequest represents a request to read an MCP resource.
77+type MCPReadResourceRequest struct {
78+ Name string `json:"name"`
79+ URI string `json:"uri"`
80+}
81+
82+// MCPGetPromptRequest represents a request to get an MCP prompt.
83+type MCPGetPromptRequest struct {
84+ ClientID string `json:"client_id"`
85+ PromptID string `json:"prompt_id"`
86+ Args map[string]string `json:"args"`
87+}
88+
89+// MCPGetPromptResponse represents the response from getting an MCP prompt.
90+type MCPGetPromptResponse struct {
91+ Prompt string `json:"prompt"`
92+}
internal/server/config.go+198−59
@@ -4,18 +4,25 @@ import (
44 "encoding/json"
55 "net/http"
66
7− "github.com/charmbracelet/crush/internal/config"
87 "github.com/charmbracelet/crush/internal/proto"
98)
109
10+// handlePostWorkspaceConfigSet sets a configuration field.
11+//
12+// @Summary Set a config field
13+// @Tags config
14+// @Accept json
15+// @Param id path string true "Workspace ID"
16+// @Param request body proto.ConfigSetRequest true "Config set request"
17+// @Success 200
18+// @Failure 400 {object} proto.Error
19+// @Failure 404 {object} proto.Error
20+// @Failure 500 {object} proto.Error
21+// @Router /workspaces/{id}/config/set [post]
1122func (c *controllerV1) handlePostWorkspaceConfigSet(w http.ResponseWriter, r *http.Request) {
1223 id := r.PathValue("id")
1324
14− var req struct {
15− Scope config.Scope `json:"scope"`
16− Key string `json:"key"`
17− Value any `json:"value"`
18− }
25+ var req proto.ConfigSetRequest
1926 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
2027 c.server.logError(r, "Failed to decode request", "error", err)
2128 jsonError(w, http.StatusBadRequest, "failed to decode request")
@@ -29,13 +36,22 @@ func (c *controllerV1) handlePostWorkspaceConfigSet(w http.ResponseWriter, r *ht
2936 w.WriteHeader(http.StatusOK)
3037}
3138
39+// handlePostWorkspaceConfigRemove removes a configuration field.
40+//
41+// @Summary Remove a config field
42+// @Tags config
43+// @Accept json
44+// @Param id path string true "Workspace ID"
45+// @Param request body proto.ConfigRemoveRequest true "Config remove request"
46+// @Success 200
47+// @Failure 400 {object} proto.Error
48+// @Failure 404 {object} proto.Error
49+// @Failure 500 {object} proto.Error
50+// @Router /workspaces/{id}/config/remove [post]
3251func (c *controllerV1) handlePostWorkspaceConfigRemove(w http.ResponseWriter, r *http.Request) {
3352 id := r.PathValue("id")
3453
35− var req struct {
36− Scope config.Scope `json:"scope"`
37− Key string `json:"key"`
38− }
54+ var req proto.ConfigRemoveRequest
3955 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
4056 c.server.logError(r, "Failed to decode request", "error", err)
4157 jsonError(w, http.StatusBadRequest, "failed to decode request")
@@ -49,14 +65,22 @@ func (c *controllerV1) handlePostWorkspaceConfigRemove(w http.ResponseWriter, r
4965 w.WriteHeader(http.StatusOK)
5066}
5167
68+// handlePostWorkspaceConfigModel updates the preferred model.
69+//
70+// @Summary Set the preferred model
71+// @Tags config
72+// @Accept json
73+// @Param id path string true "Workspace ID"
74+// @Param request body proto.ConfigModelRequest true "Config model request"
75+// @Success 200
76+// @Failure 400 {object} proto.Error
77+// @Failure 404 {object} proto.Error
78+// @Failure 500 {object} proto.Error
79+// @Router /workspaces/{id}/config/model [post]
5280func (c *controllerV1) handlePostWorkspaceConfigModel(w http.ResponseWriter, r *http.Request) {
5381 id := r.PathValue("id")
5482
55− var req struct {
56− Scope config.Scope `json:"scope"`
57− ModelType config.SelectedModelType `json:"model_type"`
58− Model config.SelectedModel `json:"model"`
59− }
83+ var req proto.ConfigModelRequest
6084 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
6185 c.server.logError(r, "Failed to decode request", "error", err)
6286 jsonError(w, http.StatusBadRequest, "failed to decode request")
@@ -70,13 +94,22 @@ func (c *controllerV1) handlePostWorkspaceConfigModel(w http.ResponseWriter, r *
7094 w.WriteHeader(http.StatusOK)
7195}
7296
97+// handlePostWorkspaceConfigCompact sets compact mode.
98+//
99+// @Summary Set compact mode
100+// @Tags config
101+// @Accept json
102+// @Param id path string true "Workspace ID"
103+// @Param request body proto.ConfigCompactRequest true "Config compact request"
104+// @Success 200
105+// @Failure 400 {object} proto.Error
106+// @Failure 404 {object} proto.Error
107+// @Failure 500 {object} proto.Error
108+// @Router /workspaces/{id}/config/compact [post]
73109func (c *controllerV1) handlePostWorkspaceConfigCompact(w http.ResponseWriter, r *http.Request) {
74110 id := r.PathValue("id")
75111
76− var req struct {
77− Scope config.Scope `json:"scope"`
78− Enabled bool `json:"enabled"`
79− }
112+ var req proto.ConfigCompactRequest
80113 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
81114 c.server.logError(r, "Failed to decode request", "error", err)
82115 jsonError(w, http.StatusBadRequest, "failed to decode request")
@@ -90,14 +123,22 @@ func (c *controllerV1) handlePostWorkspaceConfigCompact(w http.ResponseWriter, r
90123 w.WriteHeader(http.StatusOK)
91124}
92125
126+// handlePostWorkspaceConfigProviderKey sets a provider API key.
127+//
128+// @Summary Set provider API key
129+// @Tags config
130+// @Accept json
131+// @Param id path string true "Workspace ID"
132+// @Param request body proto.ConfigProviderKeyRequest true "Config provider key request"
133+// @Success 200
134+// @Failure 400 {object} proto.Error
135+// @Failure 404 {object} proto.Error
136+// @Failure 500 {object} proto.Error
137+// @Router /workspaces/{id}/config/provider-key [post]
93138func (c *controllerV1) handlePostWorkspaceConfigProviderKey(w http.ResponseWriter, r *http.Request) {
94139 id := r.PathValue("id")
95140
96− var req struct {
97− Scope config.Scope `json:"scope"`
98− ProviderID string `json:"provider_id"`
99− APIKey any `json:"api_key"`
100− }
141+ var req proto.ConfigProviderKeyRequest
101142 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
102143 c.server.logError(r, "Failed to decode request", "error", err)
103144 jsonError(w, http.StatusBadRequest, "failed to decode request")
@@ -111,6 +152,16 @@ func (c *controllerV1) handlePostWorkspaceConfigProviderKey(w http.ResponseWrite
111152 w.WriteHeader(http.StatusOK)
112153}
113154
155+// handlePostWorkspaceConfigImportCopilot imports Copilot credentials.
156+//
157+// @Summary Import Copilot credentials
158+// @Tags config
159+// @Produce json
160+// @Param id path string true "Workspace ID"
161+// @Success 200 {object} proto.ImportCopilotResponse
162+// @Failure 404 {object} proto.Error
163+// @Failure 500 {object} proto.Error
164+// @Router /workspaces/{id}/config/import-copilot [post]
114165func (c *controllerV1) handlePostWorkspaceConfigImportCopilot(w http.ResponseWriter, r *http.Request) {
115166 id := r.PathValue("id")
116167 token, ok, err := c.backend.ImportCopilot(id)
@@ -118,19 +169,25 @@ func (c *controllerV1) handlePostWorkspaceConfigImportCopilot(w http.ResponseWri
118169 c.handleError(w, r, err)
119170 return
120171 }
121− jsonEncode(w, struct {
122− Token any `json:"token"`
123− Success bool `json:"success"`
124− }{Token: token, Success: ok})
172+ jsonEncode(w, proto.ImportCopilotResponse{Token: token, Success: ok})
125173}
126174
175+// handlePostWorkspaceConfigRefreshOAuth refreshes an OAuth token for a provider.
176+//
177+// @Summary Refresh OAuth token
178+// @Tags config
179+// @Accept json
180+// @Param id path string true "Workspace ID"
181+// @Param request body proto.ConfigRefreshOAuthRequest true "Refresh OAuth request"
182+// @Success 200
183+// @Failure 400 {object} proto.Error
184+// @Failure 404 {object} proto.Error
185+// @Failure 500 {object} proto.Error
186+// @Router /workspaces/{id}/config/refresh-oauth [post]
127187func (c *controllerV1) handlePostWorkspaceConfigRefreshOAuth(w http.ResponseWriter, r *http.Request) {
128188 id := r.PathValue("id")
129189
130− var req struct {
131− Scope config.Scope `json:"scope"`
132− ProviderID string `json:"provider_id"`
133− }
190+ var req proto.ConfigRefreshOAuthRequest
134191 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
135192 c.server.logError(r, "Failed to decode request", "error", err)
136193 jsonError(w, http.StatusBadRequest, "failed to decode request")
@@ -144,6 +201,16 @@ func (c *controllerV1) handlePostWorkspaceConfigRefreshOAuth(w http.ResponseWrit
144201 w.WriteHeader(http.StatusOK)
145202}
146203
204+// handleGetWorkspaceProjectNeedsInit reports whether a project needs initialization.
205+//
206+// @Summary Check if project needs initialization
207+// @Tags project
208+// @Produce json
209+// @Param id path string true "Workspace ID"
210+// @Success 200 {object} proto.ProjectNeedsInitResponse
211+// @Failure 404 {object} proto.Error
212+// @Failure 500 {object} proto.Error
213+// @Router /workspaces/{id}/project/needs-init [get]
147214func (c *controllerV1) handleGetWorkspaceProjectNeedsInit(w http.ResponseWriter, r *http.Request) {
148215 id := r.PathValue("id")
149216 needs, err := c.backend.ProjectNeedsInitialization(id)
@@ -151,11 +218,18 @@ func (c *controllerV1) handleGetWorkspaceProjectNeedsInit(w http.ResponseWriter,
151218 c.handleError(w, r, err)
152219 return
153220 }
154− jsonEncode(w, struct {
155− NeedsInit bool `json:"needs_init"`
156− }{NeedsInit: needs})
221+ jsonEncode(w, proto.ProjectNeedsInitResponse{NeedsInit: needs})
157222}
158223
224+// handlePostWorkspaceProjectInit marks the project as initialized.
225+//
226+// @Summary Mark project as initialized
227+// @Tags project
228+// @Param id path string true "Workspace ID"
229+// @Success 200
230+// @Failure 404 {object} proto.Error
231+// @Failure 500 {object} proto.Error
232+// @Router /workspaces/{id}/project/init [post]
159233func (c *controllerV1) handlePostWorkspaceProjectInit(w http.ResponseWriter, r *http.Request) {
160234 id := r.PathValue("id")
161235 if err := c.backend.MarkProjectInitialized(id); err != nil {
@@ -165,6 +239,16 @@ func (c *controllerV1) handlePostWorkspaceProjectInit(w http.ResponseWriter, r *
165239 w.WriteHeader(http.StatusOK)
166240}
167241
242+// handleGetWorkspaceProjectInitPrompt returns the project initialization prompt.
243+//
244+// @Summary Get project initialization prompt
245+// @Tags project
246+// @Produce json
247+// @Param id path string true "Workspace ID"
248+// @Success 200 {object} proto.ProjectInitPromptResponse
249+// @Failure 404 {object} proto.Error
250+// @Failure 500 {object} proto.Error
251+// @Router /workspaces/{id}/project/init-prompt [get]
168252func (c *controllerV1) handleGetWorkspaceProjectInitPrompt(w http.ResponseWriter, r *http.Request) {
169253 id := r.PathValue("id")
170254 prompt, err := c.backend.InitializePrompt(id)
@@ -172,17 +256,25 @@ func (c *controllerV1) handleGetWorkspaceProjectInitPrompt(w http.ResponseWriter
172256 c.handleError(w, r, err)
173257 return
174258 }
175− jsonEncode(w, struct {
176− Prompt string `json:"prompt"`
177− }{Prompt: prompt})
259+ jsonEncode(w, proto.ProjectInitPromptResponse{Prompt: prompt})
178260}
179261
262+// handlePostWorkspaceMCPRefreshTools refreshes tools for a named MCP server.
263+//
264+// @Summary Refresh MCP tools
265+// @Tags mcp
266+// @Accept json
267+// @Param id path string true "Workspace ID"
268+// @Param request body proto.MCPNameRequest true "MCP name request"
269+// @Success 200
270+// @Failure 400 {object} proto.Error
271+// @Failure 404 {object} proto.Error
272+// @Failure 500 {object} proto.Error
273+// @Router /workspaces/{id}/mcp/refresh-tools [post]
180274func (c *controllerV1) handlePostWorkspaceMCPRefreshTools(w http.ResponseWriter, r *http.Request) {
181275 id := r.PathValue("id")
182276
183− var req struct {
184− Name string `json:"name"`
185− }
277+ var req proto.MCPNameRequest
186278 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
187279 c.server.logError(r, "Failed to decode request", "error", err)
188280 jsonError(w, http.StatusBadRequest, "failed to decode request")
@@ -196,13 +288,23 @@ func (c *controllerV1) handlePostWorkspaceMCPRefreshTools(w http.ResponseWriter,
196288 w.WriteHeader(http.StatusOK)
197289}
198290
291+// handlePostWorkspaceMCPReadResource reads a resource from an MCP server.
292+//
293+// @Summary Read MCP resource
294+// @Tags mcp
295+// @Accept json
296+// @Produce json
297+// @Param id path string true "Workspace ID"
298+// @Param request body proto.MCPReadResourceRequest true "MCP read resource request"
299+// @Success 200 {object} object
300+// @Failure 400 {object} proto.Error
301+// @Failure 404 {object} proto.Error
302+// @Failure 500 {object} proto.Error
303+// @Router /workspaces/{id}/mcp/read-resource [post]
199304func (c *controllerV1) handlePostWorkspaceMCPReadResource(w http.ResponseWriter, r *http.Request) {
200305 id := r.PathValue("id")
201306
202− var req struct {
203− Name string `json:"name"`
204− URI string `json:"uri"`
205− }
307+ var req proto.MCPReadResourceRequest
206308 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
207309 c.server.logError(r, "Failed to decode request", "error", err)
208310 jsonError(w, http.StatusBadRequest, "failed to decode request")
@@ -217,14 +319,23 @@ func (c *controllerV1) handlePostWorkspaceMCPReadResource(w http.ResponseWriter,
217319 jsonEncode(w, contents)
218320}
219321
322+// handlePostWorkspaceMCPGetPrompt retrieves a prompt from an MCP server.
323+//
324+// @Summary Get MCP prompt
325+// @Tags mcp
326+// @Accept json
327+// @Produce json
328+// @Param id path string true "Workspace ID"
329+// @Param request body proto.MCPGetPromptRequest true "MCP get prompt request"
330+// @Success 200 {object} proto.MCPGetPromptResponse
331+// @Failure 400 {object} proto.Error
332+// @Failure 404 {object} proto.Error
333+// @Failure 500 {object} proto.Error
334+// @Router /workspaces/{id}/mcp/get-prompt [post]
220335func (c *controllerV1) handlePostWorkspaceMCPGetPrompt(w http.ResponseWriter, r *http.Request) {
221336 id := r.PathValue("id")
222337
223− var req struct {
224− ClientID string `json:"client_id"`
225− PromptID string `json:"prompt_id"`
226− Args map[string]string `json:"args"`
227− }
338+ var req proto.MCPGetPromptRequest
228339 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
229340 c.server.logError(r, "Failed to decode request", "error", err)
230341 jsonError(w, http.StatusBadRequest, "failed to decode request")
@@ -236,11 +347,19 @@ func (c *controllerV1) handlePostWorkspaceMCPGetPrompt(w http.ResponseWriter, r
236347 c.handleError(w, r, err)
237348 return
238349 }
239− jsonEncode(w, struct {
240− Prompt string `json:"prompt"`
241− }{Prompt: prompt})
350+ jsonEncode(w, proto.MCPGetPromptResponse{Prompt: prompt})
242351}
243352
353+// handleGetWorkspaceMCPStates returns the state of all MCP clients.
354+//
355+// @Summary Get MCP client states
356+// @Tags mcp
357+// @Produce json
358+// @Param id path string true "Workspace ID"
359+// @Success 200 {object} map[string]proto.MCPClientInfo
360+// @Failure 404 {object} proto.Error
361+// @Failure 500 {object} proto.Error
362+// @Router /workspaces/{id}/mcp/states [get]
244363func (c *controllerV1) handleGetWorkspaceMCPStates(w http.ResponseWriter, r *http.Request) {
245364 id := r.PathValue("id")
246365 states := c.backend.MCPGetStates(id)
@@ -259,12 +378,22 @@ func (c *controllerV1) handleGetWorkspaceMCPStates(w http.ResponseWriter, r *htt
259378 jsonEncode(w, result)
260379}
261380
381+// handlePostWorkspaceMCPRefreshPrompts refreshes prompts for a named MCP server.
382+//
383+// @Summary Refresh MCP prompts
384+// @Tags mcp
385+// @Accept json
386+// @Param id path string true "Workspace ID"
387+// @Param request body proto.MCPNameRequest true "MCP name request"
388+// @Success 200
389+// @Failure 400 {object} proto.Error
390+// @Failure 404 {object} proto.Error
391+// @Failure 500 {object} proto.Error
392+// @Router /workspaces/{id}/mcp/refresh-prompts [post]
262393func (c *controllerV1) handlePostWorkspaceMCPRefreshPrompts(w http.ResponseWriter, r *http.Request) {
263394 id := r.PathValue("id")
264395
265− var req struct {
266− Name string `json:"name"`
267− }
396+ var req proto.MCPNameRequest
268397 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
269398 c.server.logError(r, "Failed to decode request", "error", err)
270399 jsonError(w, http.StatusBadRequest, "failed to decode request")
@@ -275,12 +404,22 @@ func (c *controllerV1) handlePostWorkspaceMCPRefreshPrompts(w http.ResponseWrite
275404 w.WriteHeader(http.StatusOK)
276405}
277406
407+// handlePostWorkspaceMCPRefreshResources refreshes resources for a named MCP server.
408+//
409+// @Summary Refresh MCP resources
410+// @Tags mcp
411+// @Accept json
412+// @Param id path string true "Workspace ID"
413+// @Param request body proto.MCPNameRequest true "MCP name request"
414+// @Success 200
415+// @Failure 400 {object} proto.Error
416+// @Failure 404 {object} proto.Error
417+// @Failure 500 {object} proto.Error
418+// @Router /workspaces/{id}/mcp/refresh-resources [post]
278419func (c *controllerV1) handlePostWorkspaceMCPRefreshResources(w http.ResponseWriter, r *http.Request) {
279420 id := r.PathValue("id")
280421
281− var req struct {
282− Name string `json:"name"`
283− }
422+ var req proto.MCPNameRequest
284423 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
285424 c.server.logError(r, "Failed to decode request", "error", err)
286425 jsonError(w, http.StatusBadRequest, "failed to decode request")
internal/server/proto.go+423−7
@@ -16,14 +16,36 @@ type controllerV1 struct {
1616 server *Server
1717}
1818
19+// handleGetHealth checks server health.
20+//
21+// @Summary Health check
22+// @Tags system
23+// @Success 200
24+// @Router /health [get]
1925func (c *controllerV1) handleGetHealth(w http.ResponseWriter, _ *http.Request) {
2026 w.WriteHeader(http.StatusOK)
2127}
2228
29+// handleGetVersion returns server version information.
30+//
31+// @Summary Get server version
32+// @Tags system
33+// @Produce json
34+// @Success 200 {object} proto.VersionInfo
35+// @Router /version [get]
2336func (c *controllerV1) handleGetVersion(w http.ResponseWriter, _ *http.Request) {
2437 jsonEncode(w, c.backend.VersionInfo())
2538}
2639
40+// handlePostControl sends a control command to the server.
41+//
42+// @Summary Send server control command
43+// @Tags system
44+// @Accept json
45+// @Param request body proto.ServerControl true "Control command (e.g. shutdown)"
46+// @Success 200
47+// @Failure 400 {object} proto.Error
48+// @Router /control [post]
2749func (c *controllerV1) handlePostControl(w http.ResponseWriter, r *http.Request) {
2850 var req proto.ServerControl
2951 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@@ -42,14 +64,38 @@ func (c *controllerV1) handlePostControl(w http.ResponseWriter, r *http.Request)
4264 }
4365}
4466
67+// handleGetConfig returns global server configuration.
68+//
69+// @Summary Get server config
70+// @Tags system
71+// @Produce json
72+// @Success 200 {object} object
73+// @Router /config [get]
4574func (c *controllerV1) handleGetConfig(w http.ResponseWriter, _ *http.Request) {
4675 jsonEncode(w, c.backend.Config())
4776}
4877
78+// handleGetWorkspaces lists all workspaces.
79+//
80+// @Summary List workspaces
81+// @Tags workspaces
82+// @Produce json
83+// @Success 200 {array} proto.Workspace
84+// @Router /workspaces [get]
4985func (c *controllerV1) handleGetWorkspaces(w http.ResponseWriter, _ *http.Request) {
5086 jsonEncode(w, c.backend.ListWorkspaces())
5187}
5288
89+// handleGetWorkspace returns a single workspace by ID.
90+//
91+// @Summary Get workspace
92+// @Tags workspaces
93+// @Produce json
94+// @Param id path string true "Workspace ID"
95+// @Success 200 {object} proto.Workspace
96+// @Failure 404 {object} proto.Error
97+// @Failure 500 {object} proto.Error
98+// @Router /workspaces/{id} [get]
5399func (c *controllerV1) handleGetWorkspace(w http.ResponseWriter, r *http.Request) {
54100 id := r.PathValue("id")
55101 ws, err := c.backend.GetWorkspaceProto(id)
@@ -60,6 +106,17 @@ func (c *controllerV1) handleGetWorkspace(w http.ResponseWriter, r *http.Request
60106 jsonEncode(w, ws)
61107}
62108
109+// handlePostWorkspaces creates a new workspace.
110+//
111+// @Summary Create workspace
112+// @Tags workspaces
113+// @Accept json
114+// @Produce json
115+// @Param request body proto.Workspace true "Workspace creation params"
116+// @Success 200 {object} proto.Workspace
117+// @Failure 400 {object} proto.Error
118+// @Failure 500 {object} proto.Error
119+// @Router /workspaces [post]
63120func (c *controllerV1) handlePostWorkspaces(w http.ResponseWriter, r *http.Request) {
64121 var args proto.Workspace
65122 if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
@@ -76,11 +133,29 @@ func (c *controllerV1) handlePostWorkspaces(w http.ResponseWriter, r *http.Reque
76133 jsonEncode(w, result)
77134}
78135
136+// handleDeleteWorkspaces deletes a workspace.
137+//
138+// @Summary Delete workspace
139+// @Tags workspaces
140+// @Param id path string true "Workspace ID"
141+// @Success 200
142+// @Failure 404 {object} proto.Error
143+// @Router /workspaces/{id} [delete]
79144func (c *controllerV1) handleDeleteWorkspaces(w http.ResponseWriter, r *http.Request) {
80145 id := r.PathValue("id")
81146 c.backend.DeleteWorkspace(id)
82147}
83148
149+// handleGetWorkspaceConfig returns workspace configuration.
150+//
151+// @Summary Get workspace config
152+// @Tags workspaces
153+// @Produce json
154+// @Param id path string true "Workspace ID"
155+// @Success 200 {object} object
156+// @Failure 404 {object} proto.Error
157+// @Failure 500 {object} proto.Error
158+// @Router /workspaces/{id}/config [get]
84159func (c *controllerV1) handleGetWorkspaceConfig(w http.ResponseWriter, r *http.Request) {
85160 id := r.PathValue("id")
86161 cfg, err := c.backend.GetWorkspaceConfig(id)
@@ -91,6 +166,16 @@ func (c *controllerV1) handleGetWorkspaceConfig(w http.ResponseWriter, r *http.R
91166 jsonEncode(w, cfg)
92167}
93168
169+// handleGetWorkspaceProviders lists available providers for a workspace.
170+//
171+// @Summary Get workspace providers
172+// @Tags workspaces
173+// @Produce json
174+// @Param id path string true "Workspace ID"
175+// @Success 200 {object} object
176+// @Failure 404 {object} proto.Error
177+// @Failure 500 {object} proto.Error
178+// @Router /workspaces/{id}/providers [get]
94179func (c *controllerV1) handleGetWorkspaceProviders(w http.ResponseWriter, r *http.Request) {
95180 id := r.PathValue("id")
96181 providers, err := c.backend.GetWorkspaceProviders(id)
@@ -101,6 +186,16 @@ func (c *controllerV1) handleGetWorkspaceProviders(w http.ResponseWriter, r *htt
101186 jsonEncode(w, providers)
102187}
103188
189+// handleGetWorkspaceEvents streams workspace events as Server-Sent Events.
190+//
191+// @Summary Stream workspace events (SSE)
192+// @Tags workspaces
193+// @Produce text/event-stream
194+// @Param id path string true "Workspace ID"
195+// @Success 200
196+// @Failure 404 {object} proto.Error
197+// @Failure 500 {object} proto.Error
198+// @Router /workspaces/{id}/events [get]
104199func (c *controllerV1) handleGetWorkspaceEvents(w http.ResponseWriter, r *http.Request) {
105200 flusher := http.NewResponseController(w)
106201 id := r.PathValue("id")
@@ -140,6 +235,16 @@ func (c *controllerV1) handleGetWorkspaceEvents(w http.ResponseWriter, r *http.R
140235 }
141236}
142237
238+// handleGetWorkspaceLSPs lists LSP clients for a workspace.
239+//
240+// @Summary List LSP clients
241+// @Tags lsp
242+// @Produce json
243+// @Param id path string true "Workspace ID"
244+// @Success 200 {object} map[string]proto.LSPClientInfo
245+// @Failure 404 {object} proto.Error
246+// @Failure 500 {object} proto.Error
247+// @Router /workspaces/{id}/lsps [get]
143248func (c *controllerV1) handleGetWorkspaceLSPs(w http.ResponseWriter, r *http.Request) {
144249 id := r.PathValue("id")
145250 states, err := c.backend.GetLSPStates(id)
@@ -160,6 +265,17 @@ func (c *controllerV1) handleGetWorkspaceLSPs(w http.ResponseWriter, r *http.Req
160265 jsonEncode(w, result)
161266}
162267
268+// handleGetWorkspaceLSPDiagnostics returns diagnostics for an LSP client.
269+//
270+// @Summary Get LSP diagnostics
271+// @Tags lsp
272+// @Produce json
273+// @Param id path string true "Workspace ID"
274+// @Param lsp path string true "LSP client name"
275+// @Success 200 {object} object
276+// @Failure 404 {object} proto.Error
277+// @Failure 500 {object} proto.Error
278+// @Router /workspaces/{id}/lsps/{lsp}/diagnostics [get]
163279func (c *controllerV1) handleGetWorkspaceLSPDiagnostics(w http.ResponseWriter, r *http.Request) {
164280 id := r.PathValue("id")
165281 lspName := r.PathValue("lsp")
@@ -171,6 +287,16 @@ func (c *controllerV1) handleGetWorkspaceLSPDiagnostics(w http.ResponseWriter, r
171287 jsonEncode(w, diagnostics)
172288}
173289
290+// handleGetWorkspaceSessions lists sessions for a workspace.
291+//
292+// @Summary List sessions
293+// @Tags sessions
294+// @Produce json
295+// @Param id path string true "Workspace ID"
296+// @Success 200 {array} proto.Session
297+// @Failure 404 {object} proto.Error
298+// @Failure 500 {object} proto.Error
299+// @Router /workspaces/{id}/sessions [get]
174300func (c *controllerV1) handleGetWorkspaceSessions(w http.ResponseWriter, r *http.Request) {
175301 id := r.PathValue("id")
176302 sessions, err := c.backend.ListSessions(r.Context(), id)
@@ -181,6 +307,19 @@ func (c *controllerV1) handleGetWorkspaceSessions(w http.ResponseWriter, r *http
181307 jsonEncode(w, sessions)
182308}
183309
310+// handlePostWorkspaceSessions creates a new session in a workspace.
311+//
312+// @Summary Create session
313+// @Tags sessions
314+// @Accept json
315+// @Produce json
316+// @Param id path string true "Workspace ID"
317+// @Param request body proto.Session true "Session creation params (title)"
318+// @Success 200 {object} proto.Session
319+// @Failure 400 {object} proto.Error
320+// @Failure 404 {object} proto.Error
321+// @Failure 500 {object} proto.Error
322+// @Router /workspaces/{id}/sessions [post]
184323func (c *controllerV1) handlePostWorkspaceSessions(w http.ResponseWriter, r *http.Request) {
185324 id := r.PathValue("id")
186325
@@ -199,6 +338,17 @@ func (c *controllerV1) handlePostWorkspaceSessions(w http.ResponseWriter, r *htt
199338 jsonEncode(w, sess)
200339}
201340
341+// handleGetWorkspaceSession returns a single session.
342+//
343+// @Summary Get session
344+// @Tags sessions
345+// @Produce json
346+// @Param id path string true "Workspace ID"
347+// @Param sid path string true "Session ID"
348+// @Success 200 {object} proto.Session
349+// @Failure 404 {object} proto.Error
350+// @Failure 500 {object} proto.Error
351+// @Router /workspaces/{id}/sessions/{sid} [get]
202352func (c *controllerV1) handleGetWorkspaceSession(w http.ResponseWriter, r *http.Request) {
203353 id := r.PathValue("id")
204354 sid := r.PathValue("sid")
@@ -210,6 +360,17 @@ func (c *controllerV1) handleGetWorkspaceSession(w http.ResponseWriter, r *http.
210360 jsonEncode(w, sess)
211361}
212362
363+// handleGetWorkspaceSessionHistory returns the history for a session.
364+//
365+// @Summary Get session history
366+// @Tags sessions
367+// @Produce json
368+// @Param id path string true "Workspace ID"
369+// @Param sid path string true "Session ID"
370+// @Success 200 {array} proto.File
371+// @Failure 404 {object} proto.Error
372+// @Failure 500 {object} proto.Error
373+// @Router /workspaces/{id}/sessions/{sid}/history [get]
213374func (c *controllerV1) handleGetWorkspaceSessionHistory(w http.ResponseWriter, r *http.Request) {
214375 id := r.PathValue("id")
215376 sid := r.PathValue("sid")
@@ -221,6 +382,17 @@ func (c *controllerV1) handleGetWorkspaceSessionHistory(w http.ResponseWriter, r
221382 jsonEncode(w, history)
222383}
223384
385+// handleGetWorkspaceSessionMessages returns all messages for a session.
386+//
387+// @Summary Get session messages
388+// @Tags sessions
389+// @Produce json
390+// @Param id path string true "Workspace ID"
391+// @Param sid path string true "Session ID"
392+// @Success 200 {array} proto.Message
393+// @Failure 404 {object} proto.Error
394+// @Failure 500 {object} proto.Error
395+// @Router /workspaces/{id}/sessions/{sid}/messages [get]
224396func (c *controllerV1) handleGetWorkspaceSessionMessages(w http.ResponseWriter, r *http.Request) {
225397 id := r.PathValue("id")
226398 sid := r.PathValue("sid")
@@ -232,6 +404,20 @@ func (c *controllerV1) handleGetWorkspaceSessionMessages(w http.ResponseWriter,
232404 jsonEncode(w, messagesToProto(messages))
233405}
234406
407+// handlePutWorkspaceSession updates a session.
408+//
409+// @Summary Update session
410+// @Tags sessions
411+// @Accept json
412+// @Produce json
413+// @Param id path string true "Workspace ID"
414+// @Param sid path string true "Session ID"
415+// @Param request body proto.Session true "Updated session"
416+// @Success 200 {object} proto.Session
417+// @Failure 400 {object} proto.Error
418+// @Failure 404 {object} proto.Error
419+// @Failure 500 {object} proto.Error
420+// @Router /workspaces/{id}/sessions/{sid} [put]
235421func (c *controllerV1) handlePutWorkspaceSession(w http.ResponseWriter, r *http.Request) {
236422 id := r.PathValue("id")
237423
@@ -250,6 +436,16 @@ func (c *controllerV1) handlePutWorkspaceSession(w http.ResponseWriter, r *http.
250436 jsonEncode(w, saved)
251437}
252438
439+// handleDeleteWorkspaceSession deletes a session.
440+//
441+// @Summary Delete session
442+// @Tags sessions
443+// @Param id path string true "Workspace ID"
444+// @Param sid path string true "Session ID"
445+// @Success 200
446+// @Failure 404 {object} proto.Error
447+// @Failure 500 {object} proto.Error
448+// @Router /workspaces/{id}/sessions/{sid} [delete]
253449func (c *controllerV1) handleDeleteWorkspaceSession(w http.ResponseWriter, r *http.Request) {
254450 id := r.PathValue("id")
255451 sid := r.PathValue("sid")
@@ -260,6 +456,17 @@ func (c *controllerV1) handleDeleteWorkspaceSession(w http.ResponseWriter, r *ht
260456 w.WriteHeader(http.StatusOK)
261457}
262458
459+// handleGetWorkspaceSessionUserMessages returns user messages for a session.
460+//
461+// @Summary Get user messages for session
462+// @Tags sessions
463+// @Produce json
464+// @Param id path string true "Workspace ID"
465+// @Param sid path string true "Session ID"
466+// @Success 200 {array} proto.Message
467+// @Failure 404 {object} proto.Error
468+// @Failure 500 {object} proto.Error
469+// @Router /workspaces/{id}/sessions/{sid}/messages/user [get]
263470func (c *controllerV1) handleGetWorkspaceSessionUserMessages(w http.ResponseWriter, r *http.Request) {
264471 id := r.PathValue("id")
265472 sid := r.PathValue("sid")
@@ -271,6 +478,16 @@ func (c *controllerV1) handleGetWorkspaceSessionUserMessages(w http.ResponseWrit
271478 jsonEncode(w, messagesToProto(messages))
272479}
273480
481+// handleGetWorkspaceAllUserMessages returns all user messages across sessions.
482+//
483+// @Summary Get all user messages for workspace
484+// @Tags workspaces
485+// @Produce json
486+// @Param id path string true "Workspace ID"
487+// @Success 200 {array} proto.Message
488+// @Failure 404 {object} proto.Error
489+// @Failure 500 {object} proto.Error
490+// @Router /workspaces/{id}/messages/user [get]
274491func (c *controllerV1) handleGetWorkspaceAllUserMessages(w http.ResponseWriter, r *http.Request) {
275492 id := r.PathValue("id")
276493 messages, err := c.backend.ListAllUserMessages(r.Context(), id)
@@ -281,6 +498,17 @@ func (c *controllerV1) handleGetWorkspaceAllUserMessages(w http.ResponseWriter,
281498 jsonEncode(w, messagesToProto(messages))
282499}
283500
501+// handleGetWorkspaceSessionFileTrackerFiles lists files read in a session.
502+//
503+// @Summary List tracked files for session
504+// @Tags filetracker
505+// @Produce json
506+// @Param id path string true "Workspace ID"
507+// @Param sid path string true "Session ID"
508+// @Success 200 {array} string
509+// @Failure 404 {object} proto.Error
510+// @Failure 500 {object} proto.Error
511+// @Router /workspaces/{id}/sessions/{sid}/filetracker/files [get]
284512func (c *controllerV1) handleGetWorkspaceSessionFileTrackerFiles(w http.ResponseWriter, r *http.Request) {
285513 id := r.PathValue("id")
286514 sid := r.PathValue("sid")
@@ -292,13 +520,22 @@ func (c *controllerV1) handleGetWorkspaceSessionFileTrackerFiles(w http.Response
292520 jsonEncode(w, files)
293521}
294522
523+// handlePostWorkspaceFileTrackerRead records a file read event.
524+//
525+// @Summary Record file read
526+// @Tags filetracker
527+// @Accept json
528+// @Param id path string true "Workspace ID"
529+// @Param request body proto.FileTrackerReadRequest true "File tracker read request"
530+// @Success 200
531+// @Failure 400 {object} proto.Error
532+// @Failure 404 {object} proto.Error
533+// @Failure 500 {object} proto.Error
534+// @Router /workspaces/{id}/filetracker/read [post]
295535func (c *controllerV1) handlePostWorkspaceFileTrackerRead(w http.ResponseWriter, r *http.Request) {
296536 id := r.PathValue("id")
297537
298− var req struct {
299− SessionID string `json:"session_id"`
300− Path string `json:"path"`
301− }
538+ var req proto.FileTrackerReadRequest
302539 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
303540 c.server.logError(r, "Failed to decode request", "error", err)
304541 jsonError(w, http.StatusBadRequest, "failed to decode request")
@@ -312,6 +549,18 @@ func (c *controllerV1) handlePostWorkspaceFileTrackerRead(w http.ResponseWriter,
312549 w.WriteHeader(http.StatusOK)
313550}
314551
552+// handleGetWorkspaceFileTrackerLastRead returns the last read time for a file.
553+//
554+// @Summary Get last read time for file
555+// @Tags filetracker
556+// @Produce json
557+// @Param id path string true "Workspace ID"
558+// @Param session_id query string false "Session ID"
559+// @Param path query string true "File path"
560+// @Success 200 {object} object
561+// @Failure 404 {object} proto.Error
562+// @Failure 500 {object} proto.Error
563+// @Router /workspaces/{id}/filetracker/lastread [get]
315564func (c *controllerV1) handleGetWorkspaceFileTrackerLastRead(w http.ResponseWriter, r *http.Request) {
316565 id := r.PathValue("id")
317566 sid := r.URL.Query().Get("session_id")
@@ -325,12 +574,22 @@ func (c *controllerV1) handleGetWorkspaceFileTrackerLastRead(w http.ResponseWrit
325574 jsonEncode(w, t)
326575}
327576
577+// handlePostWorkspaceLSPStart starts an LSP server for a path.
578+//
579+// @Summary Start LSP server
580+// @Tags lsp
581+// @Accept json
582+// @Param id path string true "Workspace ID"
583+// @Param request body proto.LSPStartRequest true "LSP start request"
584+// @Success 200
585+// @Failure 400 {object} proto.Error
586+// @Failure 404 {object} proto.Error
587+// @Failure 500 {object} proto.Error
588+// @Router /workspaces/{id}/lsps/start [post]
328589func (c *controllerV1) handlePostWorkspaceLSPStart(w http.ResponseWriter, r *http.Request) {
329590 id := r.PathValue("id")
330591
331− var req struct {
332− Path string `json:"path"`
333− }
592+ var req proto.LSPStartRequest
334593 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
335594 c.server.logError(r, "Failed to decode request", "error", err)
336595 jsonError(w, http.StatusBadRequest, "failed to decode request")
@@ -344,6 +603,15 @@ func (c *controllerV1) handlePostWorkspaceLSPStart(w http.ResponseWriter, r *htt
344603 w.WriteHeader(http.StatusOK)
345604}
346605
606+// handlePostWorkspaceLSPStopAll stops all LSP servers.
607+//
608+// @Summary Stop all LSP servers
609+// @Tags lsp
610+// @Param id path string true "Workspace ID"
611+// @Success 200
612+// @Failure 404 {object} proto.Error
613+// @Failure 500 {object} proto.Error
614+// @Router /workspaces/{id}/lsps/stop [post]
347615func (c *controllerV1) handlePostWorkspaceLSPStopAll(w http.ResponseWriter, r *http.Request) {
348616 id := r.PathValue("id")
349617 if err := c.backend.LSPStopAll(r.Context(), id); err != nil {
@@ -353,6 +621,16 @@ func (c *controllerV1) handlePostWorkspaceLSPStopAll(w http.ResponseWriter, r *h
353621 w.WriteHeader(http.StatusOK)
354622}
355623
624+// handleGetWorkspaceAgent returns agent info for a workspace.
625+//
626+// @Summary Get agent info
627+// @Tags agent
628+// @Produce json
629+// @Param id path string true "Workspace ID"
630+// @Success 200 {object} proto.AgentInfo
631+// @Failure 404 {object} proto.Error
632+// @Failure 500 {object} proto.Error
633+// @Router /workspaces/{id}/agent [get]
356634func (c *controllerV1) handleGetWorkspaceAgent(w http.ResponseWriter, r *http.Request) {
357635 id := r.PathValue("id")
358636 info, err := c.backend.GetAgentInfo(id)
@@ -363,6 +641,18 @@ func (c *controllerV1) handleGetWorkspaceAgent(w http.ResponseWriter, r *http.Re
363641 jsonEncode(w, info)
364642}
365643
644+// handlePostWorkspaceAgent sends a message to the agent.
645+//
646+// @Summary Send message to agent
647+// @Tags agent
648+// @Accept json
649+// @Param id path string true "Workspace ID"
650+// @Param request body proto.AgentMessage true "Agent message"
651+// @Success 200
652+// @Failure 400 {object} proto.Error
653+// @Failure 404 {object} proto.Error
654+// @Failure 500 {object} proto.Error
655+// @Router /workspaces/{id}/agent [post]
366656func (c *controllerV1) handlePostWorkspaceAgent(w http.ResponseWriter, r *http.Request) {
367657 id := r.PathValue("id")
368658
@@ -380,6 +670,15 @@ func (c *controllerV1) handlePostWorkspaceAgent(w http.ResponseWriter, r *http.R
380670 w.WriteHeader(http.StatusOK)
381671}
382672
673+// handlePostWorkspaceAgentInit initializes the agent for a workspace.
674+//
675+// @Summary Initialize agent
676+// @Tags agent
677+// @Param id path string true "Workspace ID"
678+// @Success 200
679+// @Failure 404 {object} proto.Error
680+// @Failure 500 {object} proto.Error
681+// @Router /workspaces/{id}/agent/init [post]
383682func (c *controllerV1) handlePostWorkspaceAgentInit(w http.ResponseWriter, r *http.Request) {
384683 id := r.PathValue("id")
385684 if err := c.backend.InitAgent(r.Context(), id); err != nil {
@@ -389,6 +688,15 @@ func (c *controllerV1) handlePostWorkspaceAgentInit(w http.ResponseWriter, r *ht
389688 w.WriteHeader(http.StatusOK)
390689}
391690
691+// handlePostWorkspaceAgentUpdate updates the agent for a workspace.
692+//
693+// @Summary Update agent
694+// @Tags agent
695+// @Param id path string true "Workspace ID"
696+// @Success 200
697+// @Failure 404 {object} proto.Error
698+// @Failure 500 {object} proto.Error
699+// @Router /workspaces/{id}/agent/update [post]
392700func (c *controllerV1) handlePostWorkspaceAgentUpdate(w http.ResponseWriter, r *http.Request) {
393701 id := r.PathValue("id")
394702 if err := c.backend.UpdateAgent(r.Context(), id); err != nil {
@@ -398,6 +706,17 @@ func (c *controllerV1) handlePostWorkspaceAgentUpdate(w http.ResponseWriter, r *
398706 w.WriteHeader(http.StatusOK)
399707}
400708
709+// handleGetWorkspaceAgentSession returns a specific agent session.
710+//
711+// @Summary Get agent session
712+// @Tags agent
713+// @Produce json
714+// @Param id path string true "Workspace ID"
715+// @Param sid path string true "Session ID"
716+// @Success 200 {object} proto.AgentSession
717+// @Failure 404 {object} proto.Error
718+// @Failure 500 {object} proto.Error
719+// @Router /workspaces/{id}/agent/sessions/{sid} [get]
401720func (c *controllerV1) handleGetWorkspaceAgentSession(w http.ResponseWriter, r *http.Request) {
402721 id := r.PathValue("id")
403722 sid := r.PathValue("sid")
@@ -409,6 +728,16 @@ func (c *controllerV1) handleGetWorkspaceAgentSession(w http.ResponseWriter, r *
409728 jsonEncode(w, agentSession)
410729}
411730
731+// handlePostWorkspaceAgentSessionCancel cancels a running agent session.
732+//
733+// @Summary Cancel agent session
734+// @Tags agent
735+// @Param id path string true "Workspace ID"
736+// @Param sid path string true "Session ID"
737+// @Success 200
738+// @Failure 404 {object} proto.Error
739+// @Failure 500 {object} proto.Error
740+// @Router /workspaces/{id}/agent/sessions/{sid}/cancel [post]
412741func (c *controllerV1) handlePostWorkspaceAgentSessionCancel(w http.ResponseWriter, r *http.Request) {
413742 id := r.PathValue("id")
414743 sid := r.PathValue("sid")
@@ -419,6 +748,17 @@ func (c *controllerV1) handlePostWorkspaceAgentSessionCancel(w http.ResponseWrit
419748 w.WriteHeader(http.StatusOK)
420749}
421750
751+// handleGetWorkspaceAgentSessionPromptQueued returns whether a queued prompt exists.
752+//
753+// @Summary Get queued prompt status
754+// @Tags agent
755+// @Produce json
756+// @Param id path string true "Workspace ID"
757+// @Param sid path string true "Session ID"
758+// @Success 200 {object} object
759+// @Failure 404 {object} proto.Error
760+// @Failure 500 {object} proto.Error
761+// @Router /workspaces/{id}/agent/sessions/{sid}/prompts/queued [get]
422762func (c *controllerV1) handleGetWorkspaceAgentSessionPromptQueued(w http.ResponseWriter, r *http.Request) {
423763 id := r.PathValue("id")
424764 sid := r.PathValue("sid")
@@ -430,6 +770,16 @@ func (c *controllerV1) handleGetWorkspaceAgentSessionPromptQueued(w http.Respons
430770 jsonEncode(w, queued)
431771}
432772
773+// handlePostWorkspaceAgentSessionPromptClear clears the prompt queue for a session.
774+//
775+// @Summary Clear prompt queue
776+// @Tags agent
777+// @Param id path string true "Workspace ID"
778+// @Param sid path string true "Session ID"
779+// @Success 200
780+// @Failure 404 {object} proto.Error
781+// @Failure 500 {object} proto.Error
782+// @Router /workspaces/{id}/agent/sessions/{sid}/prompts/clear [post]
433783func (c *controllerV1) handlePostWorkspaceAgentSessionPromptClear(w http.ResponseWriter, r *http.Request) {
434784 id := r.PathValue("id")
435785 sid := r.PathValue("sid")
@@ -440,6 +790,16 @@ func (c *controllerV1) handlePostWorkspaceAgentSessionPromptClear(w http.Respons
440790 w.WriteHeader(http.StatusOK)
441791}
442792
793+// handlePostWorkspaceAgentSessionSummarize summarizes a session.
794+//
795+// @Summary Summarize session
796+// @Tags agent
797+// @Param id path string true "Workspace ID"
798+// @Param sid path string true "Session ID"
799+// @Success 200
800+// @Failure 404 {object} proto.Error
801+// @Failure 500 {object} proto.Error
802+// @Router /workspaces/{id}/agent/sessions/{sid}/summarize [post]
443803func (c *controllerV1) handlePostWorkspaceAgentSessionSummarize(w http.ResponseWriter, r *http.Request) {
444804 id := r.PathValue("id")
445805 sid := r.PathValue("sid")
@@ -450,6 +810,17 @@ func (c *controllerV1) handlePostWorkspaceAgentSessionSummarize(w http.ResponseW
450810 w.WriteHeader(http.StatusOK)
451811}
452812
813+// handleGetWorkspaceAgentSessionPromptList returns the list of queued prompts.
814+//
815+// @Summary List queued prompts
816+// @Tags agent
817+// @Produce json
818+// @Param id path string true "Workspace ID"
819+// @Param sid path string true "Session ID"
820+// @Success 200 {array} string
821+// @Failure 404 {object} proto.Error
822+// @Failure 500 {object} proto.Error
823+// @Router /workspaces/{id}/agent/sessions/{sid}/prompts/list [get]
453824func (c *controllerV1) handleGetWorkspaceAgentSessionPromptList(w http.ResponseWriter, r *http.Request) {
454825 id := r.PathValue("id")
455826 sid := r.PathValue("sid")
@@ -461,6 +832,17 @@ func (c *controllerV1) handleGetWorkspaceAgentSessionPromptList(w http.ResponseW
461832 jsonEncode(w, prompts)
462833}
463834
835+// handleGetWorkspaceAgentDefaultSmallModel returns the default small model for a provider.
836+//
837+// @Summary Get default small model
838+// @Tags agent
839+// @Produce json
840+// @Param id path string true "Workspace ID"
841+// @Param provider_id query string false "Provider ID"
842+// @Success 200 {object} object
843+// @Failure 404 {object} proto.Error
844+// @Failure 500 {object} proto.Error
845+// @Router /workspaces/{id}/agent/default-small-model [get]
464846func (c *controllerV1) handleGetWorkspaceAgentDefaultSmallModel(w http.ResponseWriter, r *http.Request) {
465847 id := r.PathValue("id")
466848 providerID := r.URL.Query().Get("provider_id")
@@ -472,6 +854,18 @@ func (c *controllerV1) handleGetWorkspaceAgentDefaultSmallModel(w http.ResponseW
472854 jsonEncode(w, model)
473855}
474856
857+// handlePostWorkspacePermissionsGrant grants a permission request.
858+//
859+// @Summary Grant permission
860+// @Tags permissions
861+// @Accept json
862+// @Param id path string true "Workspace ID"
863+// @Param request body proto.PermissionGrant true "Permission grant"
864+// @Success 200
865+// @Failure 400 {object} proto.Error
866+// @Failure 404 {object} proto.Error
867+// @Failure 500 {object} proto.Error
868+// @Router /workspaces/{id}/permissions/grant [post]
475869func (c *controllerV1) handlePostWorkspacePermissionsGrant(w http.ResponseWriter, r *http.Request) {
476870 id := r.PathValue("id")
477871
@@ -489,6 +883,18 @@ func (c *controllerV1) handlePostWorkspacePermissionsGrant(w http.ResponseWriter
489883 w.WriteHeader(http.StatusOK)
490884}
491885
886+// handlePostWorkspacePermissionsSkip sets whether to skip permission prompts.
887+//
888+// @Summary Set skip permissions
889+// @Tags permissions
890+// @Accept json
891+// @Param id path string true "Workspace ID"
892+// @Param request body proto.PermissionSkipRequest true "Permission skip request"
893+// @Success 200
894+// @Failure 400 {object} proto.Error
895+// @Failure 404 {object} proto.Error
896+// @Failure 500 {object} proto.Error
897+// @Router /workspaces/{id}/permissions/skip [post]
492898func (c *controllerV1) handlePostWorkspacePermissionsSkip(w http.ResponseWriter, r *http.Request) {
493899 id := r.PathValue("id")
494900
@@ -505,6 +911,16 @@ func (c *controllerV1) handlePostWorkspacePermissionsSkip(w http.ResponseWriter,
505911 }
506912}
507913
914+// handleGetWorkspacePermissionsSkip returns whether permission prompts are skipped.
915+//
916+// @Summary Get skip permissions status
917+// @Tags permissions
918+// @Produce json
919+// @Param id path string true "Workspace ID"
920+// @Success 200 {object} proto.PermissionSkipRequest
921+// @Failure 404 {object} proto.Error
922+// @Failure 500 {object} proto.Error
923+// @Router /workspaces/{id}/permissions/skip [get]
508924func (c *controllerV1) handleGetWorkspacePermissionsSkip(w http.ResponseWriter, r *http.Request) {
509925 id := r.PathValue("id")
510926 skip, err := c.backend.GetPermissionsSkip(id)
internal/server/server.go+3
@@ -11,8 +11,10 @@ import (
1111 "runtime"
1212 "strings"
1313
14+ _ "github.com/charmbracelet/crush/docs"
1415 "github.com/charmbracelet/crush/internal/backend"
1516 "github.com/charmbracelet/crush/internal/config"
17+ httpswagger "github.com/swaggo/http-swagger/v2"
1618)
1719
1820// ErrServerClosed is returned when the server is closed.
@@ -161,6 +163,7 @@ func NewServer(cfg *config.ConfigStore, network, address string) *Server {
161163 mux.HandleFunc("GET /v1/workspaces/{id}/mcp/states", c.handleGetWorkspaceMCPStates)
162164 mux.HandleFunc("POST /v1/workspaces/{id}/mcp/refresh-prompts", c.handlePostWorkspaceMCPRefreshPrompts)
163165 mux.HandleFunc("POST /v1/workspaces/{id}/mcp/refresh-resources", c.handlePostWorkspaceMCPRefreshResources)
166+ mux.Handle("/v1/docs/", httpswagger.WrapHandler)
164167 s.h = &http.Server{
165168 Protocols: &p,
166169 Handler: s.loggingHandler(mux),
main.go+10
@@ -1,3 +1,13 @@
1+// Package main is the entry point for the Crush CLI.
2+//
3+// @title Crush API
4+// @version 1.0
5+// @description Crush is a terminal-based AI coding assistant. This API is served over a Unix socket (or Windows named pipe) and provides programmatic access to workspaces, sessions, agents, LSP, MCP, and more.
6+// @contact.name Charm
7+// @contact.url https://charm.sh
8+// @license.name MIT
9+// @license.url https://github.com/charmbracelet/crush/blob/main/LICENSE
10+// @BasePath /v1
111package main
212
313import (