Detailed changes
@@ -999,6 +999,14 @@
"created_at": "2025-12-27T06:01:58Z",
"repoId": 987670088,
"pullRequestNo": 1723
+ },
+ {
+ "name": "nikolayk812",
+ "id": 23297850,
+ "comment_id": 3704102069,
+ "created_at": "2026-01-01T21:00:07Z",
+ "repoId": 987670088,
+ "pullRequestNo": 1748
}
]
}
@@ -152,7 +152,14 @@ aurs:
conflicts:
- crush
package: |-
- cd "${srcdir}/crush_${pkgver}_Linux_${CARCH}"
+ case "$CARCH" in
+ aarch64)
+ cd "${srcdir}/crush_${pkgver}_Linux_arm64"
+ ;;
+ *)
+ cd "${srcdir}/crush_${pkgver}_Linux_${CARCH}"
+ ;;
+ esac
# bin
install -Dm755 "./crush" "${pkgdir}/usr/bin/crush"
# license
@@ -5,7 +5,7 @@ go 1.25.5
require (
charm.land/bubbles/v2 v2.0.0-rc.1
charm.land/bubbletea/v2 v2.0.0-rc.2.0.20251216153312-819e2e89c62e
- charm.land/fantasy v0.5.5
+ charm.land/fantasy v0.6.0
charm.land/glamour/v2 v2.0.0-20251110203732-69649f93d3b1
charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251205162909-7869489d8971
charm.land/log/v2 v2.0.0-20251110204020-529bb77f35da
@@ -18,7 +18,7 @@ require (
github.com/aymanbagabas/go-udiff v0.3.1
github.com/bmatcuk/doublestar/v4 v4.9.1
github.com/charlievieth/fastwalk v1.0.14
- github.com/charmbracelet/catwalk v0.11.2
+ github.com/charmbracelet/catwalk v0.12.0
github.com/charmbracelet/colorprofile v0.4.1
github.com/charmbracelet/fang v0.4.4
github.com/charmbracelet/ultraviolet v0.0.0-20251212194010-b927aa605560
@@ -37,14 +37,14 @@ require (
github.com/invopop/jsonschema v0.13.0
github.com/joho/godotenv v1.5.1
github.com/lucasb-eyer/go-colorful v1.3.0
- github.com/modelcontextprotocol/go-sdk v1.1.0
+ github.com/modelcontextprotocol/go-sdk v1.2.0
github.com/muesli/termenv v0.16.0
github.com/ncruces/go-sqlite3 v0.30.4
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/nxadm/tail v1.4.11
github.com/openai/openai-go/v2 v2.7.1
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
- github.com/posthog/posthog-go v1.6.13
+ github.com/posthog/posthog-go v1.8.2
github.com/pressly/goose/v3 v3.26.0
github.com/qjebbs/go-jsons v1.0.0-alpha.4
github.com/rivo/uniseg v0.4.7
@@ -69,26 +69,27 @@ require (
require (
cloud.google.com/go v0.116.0 // indirect
- cloud.google.com/go/auth v0.17.0 // indirect
+ cloud.google.com/go/auth v0.18.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
- cloud.google.com/go/compute/metadata v0.8.0 // indirect
+ cloud.google.com/go/compute/metadata v0.9.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/RealAlexandreAI/json-repair v0.0.14 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/aws/aws-sdk-go-v2 v1.41.0 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect
- github.com/aws/aws-sdk-go-v2/config v1.27.27 // indirect
- github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect
- github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect
- github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect
- github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect
- github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
- github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect
- github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect
- github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect
- github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect
- github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect
+ github.com/aws/aws-sdk-go-v2/config v1.32.6 // indirect
+ github.com/aws/aws-sdk-go-v2/credentials v1.19.6 // indirect
+ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
+ github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect
+ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect
+ github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect
+ github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 // indirect
+ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect
+ github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect
github.com/aws/smithy-go v1.24.0 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
@@ -115,7 +116,7 @@ require (
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/jsonschema-go v0.3.0 // indirect
github.com/google/s2a-go v0.1.9 // indirect
- github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
+ github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
@@ -170,11 +171,11 @@ require (
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/term v0.38.0 // indirect
- golang.org/x/time v0.12.0 // indirect
+ golang.org/x/time v0.14.0 // indirect
google.golang.org/api v0.239.0 // indirect
- google.golang.org/genai v1.39.0 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
- google.golang.org/grpc v1.74.2 // indirect
+ google.golang.org/genai v1.40.0 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect
+ google.golang.org/grpc v1.76.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/dnaeon/go-vcr.v4 v4.0.6-0.20251110073552-01de4eb40290 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
@@ -2,8 +2,8 @@ charm.land/bubbles/v2 v2.0.0-rc.1 h1:EiIFVAc3Zi/yY86td+79mPhHR7AqZ1OxF+6ztpOCRaM
charm.land/bubbles/v2 v2.0.0-rc.1/go.mod h1:5AbN6cEd/47gkEf8TgiQ2O3RZ5QxMS14l9W+7F9fPC4=
charm.land/bubbletea/v2 v2.0.0-rc.2.0.20251216153312-819e2e89c62e h1:tXwTmgGpwZT7ParKF5xbEQBVjM2e1uKhKi/GpfU3mYQ=
charm.land/bubbletea/v2 v2.0.0-rc.2.0.20251216153312-819e2e89c62e/go.mod h1:pDM18flq3Z4njKZPA3zCvyVSSIJbMcoqlE82BdGUtL8=
-charm.land/fantasy v0.5.5 h1:Dw/NBLH9HLX/ouCz604RXGD7BYzr0lT56/B4ylMGZjg=
-charm.land/fantasy v0.5.5/go.mod h1:QyJLJGissYdBifvitgAxFcYhNACSr0G1faC75CIESUk=
+charm.land/fantasy v0.6.0 h1:0PZfZ/w6c70UdlumGGFW6s9zTV6f4xAV/bXo6vGuZsc=
+charm.land/fantasy v0.6.0/go.mod h1:hUyklhBbCtnVeMAWGXHbMD4A+5B8dHbYHGZDfOYpzzw=
charm.land/glamour/v2 v2.0.0-20251110203732-69649f93d3b1 h1:9q4+yyU7105T3OrOx0csMyKnw89yMSijJ+rVld/Z2ek=
charm.land/glamour/v2 v2.0.0-20251110203732-69649f93d3b1/go.mod h1:J3kVhY6oHXZq5f+8vC3hmDO95fEvbqj3z7xDwxrfzU8=
charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251205162909-7869489d8971 h1:xZFcNsJMiIDbFtWRyDmkKNk1sjojfaom4Zoe0cyH/8c=
@@ -14,12 +14,12 @@ charm.land/x/vcr v0.1.1 h1:PXCFMUG0rPtyk35rhfzYCJEduOzWXCIbrXTFq4OF/9Q=
charm.land/x/vcr v0.1.1/go.mod h1:eByq2gqzWvcct/8XE2XO5KznoWEBiXH56+y2gphbltM=
cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
-cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=
-cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=
+cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0=
+cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
-cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA=
-cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw=
+cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
+cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
@@ -52,28 +52,30 @@ github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgP
github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM=
-github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90=
-github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg=
-github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI=
-github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8=
-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII=
-github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM=
-github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw=
-github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE=
-github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ=
+github.com/aws/aws-sdk-go-v2/config v1.32.6 h1:hFLBGUKjmLAekvi1evLi5hVvFQtSo3GYwi+Bx4lpJf8=
+github.com/aws/aws-sdk-go-v2/config v1.32.6/go.mod h1:lcUL/gcd8WyjCrMnxez5OXkO3/rwcNmvfno62tnXNcI=
+github.com/aws/aws-sdk-go-v2/credentials v1.19.6 h1:F9vWao2TwjV2MyiyVS+duza0NIRtAslgLUM0vTA1ZaE=
+github.com/aws/aws-sdk-go-v2/credentials v1.19.6/go.mod h1:SgHzKjEVsdQr6Opor0ihgWtkWdfRAIwxYzSJ8O85VHY=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM=
+github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ=
+github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU=
+github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 h1:aM/Q24rIlS3bRAhTyFurowU8A0SMyGDtEOY/l/s/1Uw=
+github.com/aws/aws-sdk-go-v2/service/sso v1.30.8/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0=
+github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70=
+github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk=
github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
@@ -92,8 +94,8 @@ github.com/charlievieth/fastwalk v1.0.14 h1:3Eh5uaFGwHZd8EGwTjJnSpBkfwfsak9h6ICg
github.com/charlievieth/fastwalk v1.0.14/go.mod h1:diVcUreiU1aQ4/Wu3NbxxH4/KYdKpLDojrQ1Bb2KgNY=
github.com/charmbracelet/anthropic-sdk-go v0.0.0-20251024181547-21d6f3d9a904 h1:rwLdEpG9wE6kL69KkEKDiWprO8pQOZHZXeod6+9K+mw=
github.com/charmbracelet/anthropic-sdk-go v0.0.0-20251024181547-21d6f3d9a904/go.mod h1:8TIYxZxsuCqqeJ0lga/b91tBwrbjoHDC66Sq5t8N2R4=
-github.com/charmbracelet/catwalk v0.11.2 h1:m+eE7yv/uIrKW95FpFeGDMFrAugotylX89XzpkZwlLk=
-github.com/charmbracelet/catwalk v0.11.2/go.mod h1:qg+Yl9oaZTkTvRscqbxfttzOFQ4v0pOT5XwC7b5O0NQ=
+github.com/charmbracelet/catwalk v0.12.0 h1:CCxbZpgMPyZNtnaRGvL//BgPkvOWOYVFhRf925Dfrdg=
+github.com/charmbracelet/catwalk v0.12.0/go.mod h1:qg+Yl9oaZTkTvRscqbxfttzOFQ4v0pOT5XwC7b5O0NQ=
github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
github.com/charmbracelet/fang v0.4.4 h1:G4qKxF6or/eTPgmAolwPuRNyuci3hTUGGX1rj1YkHJY=
@@ -128,6 +130,8 @@ github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfa
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
+github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls=
+github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
@@ -144,6 +148,11 @@ github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZ
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
+github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
+github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
+github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
+github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
+github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
@@ -177,8 +186,8 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
-github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
+github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=
+github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
@@ -234,8 +243,8 @@ github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwX
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/modelcontextprotocol/go-sdk v1.1.0 h1:Qjayg53dnKC4UZ+792W21e4BpwEZBzwgRW6LrjLWSwA=
-github.com/modelcontextprotocol/go-sdk v1.1.0/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10=
+github.com/modelcontextprotocol/go-sdk v1.2.0 h1:Y23co09300CEk8iZ/tMxIX1dVmKZkzoSBZOpJwUnc/s=
+github.com/modelcontextprotocol/go-sdk v1.2.0/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/mango v0.1.0 h1:DZQK45d2gGbql1arsYA4vfg4d7I9Hfx5rX/GCmzsAvI=
@@ -267,10 +276,12 @@ github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFu
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
+github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/posthog/posthog-go v1.6.13 h1:4t9j0VOIJBgITm4v5rLsLy3IKUkU9dn2VusMNzZXScw=
-github.com/posthog/posthog-go v1.6.13/go.mod h1:LcC1Nu4AgvV22EndTtrMXTy+7RGVC0MhChSw7Qk5XkY=
+github.com/posthog/posthog-go v1.8.2 h1:v/ajsM8lq+2Z3OlQbTVWqiHI+hyh9Cd4uiQt1wFlehE=
+github.com/posthog/posthog-go v1.8.2/go.mod h1:ueZiJCmHezyDHI/swIR1RmOfktLehnahJnFxEvQ9mnQ=
github.com/pressly/goose/v3 v3.26.0 h1:KJakav68jdH0WDvoAcj8+n61WqOIaPGgH0bJWS6jpmM=
github.com/pressly/goose/v3 v3.26.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY=
github.com/qjebbs/go-jsons v1.0.0-alpha.4 h1:Qsb4ohRUHQODIUAsJKdKJ/SIDbsO7oGOzsfy+h1yQZs=
@@ -352,10 +363,10 @@ go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
-go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
-go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
-go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
-go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
+go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
+go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
+go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
+go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
@@ -449,8 +460,8 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
-golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
-golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
+golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
+golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
@@ -460,14 +471,16 @@ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxb
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
+gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.239.0 h1:2hZKUnFZEy81eugPs4e2XzIJ5SOwQg0G82bpXD65Puo=
google.golang.org/api v0.239.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
-google.golang.org/genai v1.39.0 h1:80I1sYFGROliWNxEgPWDklNYVO8xq/bNvw70BFh6XmA=
-google.golang.org/genai v1.39.0/go.mod h1:A3kkl0nyBjyFlNjgxIwKq70julKbIxpSxqKO5gw/gmk=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
-google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
-google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
+google.golang.org/genai v1.40.0 h1:kYxyQSH+vsib8dvsgyLJzsVEIv5k3ZmHJyVqdvGncmc=
+google.golang.org/genai v1.40.0/go.mod h1:A3kkl0nyBjyFlNjgxIwKq70julKbIxpSxqKO5gw/gmk=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
+google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
+google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -16,6 +16,7 @@ import (
"fmt"
"log/slog"
"os"
+ "regexp"
"strconv"
"strings"
"sync"
@@ -39,12 +40,17 @@ import (
"github.com/charmbracelet/crush/internal/stringext"
)
+const defaultSessionName = "Untitled Session"
+
//go:embed templates/title.md
var titlePrompt []byte
//go:embed templates/summary.md
var summaryPrompt []byte
+// Used to remove <think> tags from generated titles.
+var thinkTagRegex = regexp.MustCompile(`<think>.*?</think>`)
+
type SessionAgentCall struct {
SessionID string
Prompt string
@@ -466,7 +472,7 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (*fantasy
currentAssistant.AddFinish(
message.FinishReasonError,
"Copilot model not enabled",
- fmt.Sprintf("%q is not enabled in Copilot. Go to the following page to enable it. Then, wait a minute before trying again. %s", a.largeModel.CatwalkCfg.Name, link),
+ fmt.Sprintf("%q is not enabled in Copilot. Go to the following page to enable it. Then, wait 5 minutes before trying again. %s", a.largeModel.CatwalkCfg.Name, link),
)
} else {
currentAssistant.AddFinish(message.FinishReasonError, cmp.Or(stringext.Capitalize(providerErr.Title), defaultTitle), providerErr.Message)
@@ -725,49 +731,87 @@ func (a *sessionAgent) getSessionMessages(ctx context.Context, session session.S
return msgs, nil
}
-func (a *sessionAgent) generateTitle(ctx context.Context, sessionID string, prompt string) {
- if prompt == "" {
+// generateTitle generates a session titled based on the initial prompt.
+func (a *sessionAgent) generateTitle(ctx context.Context, sessionID string, userPrompt string) {
+ if userPrompt == "" {
return
}
- var maxOutput int64 = 40
+ var maxOutputTokens int64 = 40
if a.smallModel.CatwalkCfg.CanReason {
- maxOutput = a.smallModel.CatwalkCfg.DefaultMaxTokens
+ maxOutputTokens = a.smallModel.CatwalkCfg.DefaultMaxTokens
}
- agent := fantasy.NewAgent(a.smallModel.Model,
- fantasy.WithSystemPrompt(string(titlePrompt)+"\n /no_think"),
- fantasy.WithMaxOutputTokens(maxOutput),
- )
+ newAgent := func(m fantasy.LanguageModel, p []byte, tok int64) fantasy.Agent {
+ return fantasy.NewAgent(m,
+ fantasy.WithSystemPrompt(string(p)+"\n /no_think"),
+ fantasy.WithMaxOutputTokens(tok),
+ )
+ }
- resp, err := agent.Stream(ctx, fantasy.AgentStreamCall{
- Prompt: fmt.Sprintf("Generate a concise title for the following content:\n\n%s\n <think>\n\n</think>", prompt),
- PrepareStep: func(callContext context.Context, options fantasy.PrepareStepFunctionOptions) (_ context.Context, prepared fantasy.PrepareStepResult, err error) {
- prepared.Messages = options.Messages
+ streamCall := fantasy.AgentStreamCall{
+ Prompt: fmt.Sprintf("Generate a concise title for the following content:\n\n%s\n <think>\n\n</think>", userPrompt),
+ PrepareStep: func(callCtx context.Context, opts fantasy.PrepareStepFunctionOptions) (_ context.Context, prepared fantasy.PrepareStepResult, err error) {
+ prepared.Messages = opts.Messages
if a.systemPromptPrefix != "" {
- prepared.Messages = append([]fantasy.Message{fantasy.NewSystemMessage(a.systemPromptPrefix)}, prepared.Messages...)
+ prepared.Messages = append([]fantasy.Message{
+ fantasy.NewSystemMessage(a.systemPromptPrefix),
+ }, prepared.Messages...)
}
- return callContext, prepared, nil
+ return callCtx, prepared, nil
},
- })
- if err != nil {
- slog.Error("error generating title", "err", err)
- return
}
- title := resp.Response.Content.Text()
+ // Use the small model to generate the title.
+ model := &a.smallModel
+ agent := newAgent(model.Model, titlePrompt, maxOutputTokens)
+ resp, err := agent.Stream(ctx, streamCall)
+ if err == nil {
+ // We successfully generated a title with the small model.
+ slog.Info("generated title with small model")
+ } else {
+ // It didn't work. Let's try with the big model.
+ slog.Error("error generating title with small model; trying big model", "err", err)
+ model = &a.largeModel
+ agent = newAgent(model.Model, titlePrompt, maxOutputTokens)
+ resp, err = agent.Stream(ctx, streamCall)
+ if err == nil {
+ slog.Info("generated title with large model")
+ } else {
+ // Welp, the large model didn't work either. Use the default
+ // session name and return.
+ slog.Error("error generating title with large model", "err", err)
+ saveErr := a.sessions.UpdateTitleAndUsage(ctx, sessionID, defaultSessionName, 0, 0, 0)
+ if saveErr != nil {
+ slog.Error("failed to save session title and usage", "error", saveErr)
+ }
+ return
+ }
+ }
+
+ if resp == nil {
+ // Actually, we didn't get a response so we can't. Use the default
+ // session name and return.
+ slog.Error("response is nil; can't generate title")
+ saveErr := a.sessions.UpdateTitleAndUsage(ctx, sessionID, defaultSessionName, 0, 0, 0)
+ if saveErr != nil {
+ slog.Error("failed to save session title and usage", "error", saveErr)
+ }
+ return
+ }
- title = strings.ReplaceAll(title, "\n", " ")
+ // Clean up title.
+ var title string
+ title = strings.ReplaceAll(resp.Response.Content.Text(), "\n", " ")
+ slog.Info("generated title", "title", title)
// Remove thinking tags if present.
- if idx := strings.Index(title, "</think>"); idx > 0 {
- title = title[idx+len("</think>"):]
- }
+ title = thinkTagRegex.ReplaceAllString(title, "")
title = strings.TrimSpace(title)
if title == "" {
- slog.Warn("failed to generate title", "warn", "empty title")
- return
+ slog.Warn("empty title; using fallback")
+ title = defaultSessionName
}
// Calculate usage and cost.
@@ -783,7 +827,7 @@ func (a *sessionAgent) generateTitle(ctx context.Context, sessionID string, prom
}
}
- modelConfig := a.smallModel.CatwalkCfg
+ modelConfig := model.CatwalkCfg
cost := modelConfig.CostPer1MInCached/1e6*float64(resp.TotalUsage.CacheCreationTokens) +
modelConfig.CostPer1MOutCached/1e6*float64(resp.TotalUsage.CacheReadTokens) +
modelConfig.CostPer1MIn/1e6*float64(resp.TotalUsage.InputTokens) +
@@ -805,7 +849,7 @@ func (a *sessionAgent) generateTitle(ctx context.Context, sessionID string, prom
// concurrent session updates.
saveErr := a.sessions.UpdateTitleAndUsage(ctx, sessionID, title, promptTokens, completionTokens, cost)
if saveErr != nil {
- slog.Error("failed to save session title & usage", "error", saveErr)
+ slog.Error("failed to save session title and usage", "error", saveErr)
return
}
}
@@ -947,6 +991,7 @@ func (a *sessionAgent) promptPrefix() string {
return a.systemPromptPrefix
}
+// XXX: this should be generalized to cover other subscription plans, like Copilot.
func (a *sessionAgent) isClaudeCode() bool {
cfg := config.Get()
pc, ok := cfg.Providers.Get(a.largeModel.ModelCfg.Provider)
@@ -143,7 +143,7 @@ func (c *coordinator) agenticFetchTool(_ context.Context, client *http.Client) (
return fantasy.ToolResponse{}, fmt.Errorf("error creating prompt: %s", err)
}
- _, small, err := c.buildAgentModels(ctx)
+ _, small, err := c.buildAgentModels(ctx, true)
if err != nil {
return fantasy.ToolResponse{}, fmt.Errorf("error building models: %s", err)
}
@@ -26,6 +26,7 @@ import (
"github.com/charmbracelet/crush/internal/log"
"github.com/charmbracelet/crush/internal/lsp"
"github.com/charmbracelet/crush/internal/message"
+ "github.com/charmbracelet/crush/internal/oauth/copilot"
"github.com/charmbracelet/crush/internal/permission"
"github.com/charmbracelet/crush/internal/session"
"golang.org/x/sync/errgroup"
@@ -316,7 +317,7 @@ func mergeCallOptions(model Model, cfg config.ProviderConfig) (fantasy.ProviderO
}
func (c *coordinator) buildAgent(ctx context.Context, prompt *prompt.Prompt, agent config.Agent, isSubAgent bool) (SessionAgent, error) {
- large, small, err := c.buildAgentModels(ctx)
+ large, small, err := c.buildAgentModels(ctx, isSubAgent)
if err != nil {
return nil, err
}
@@ -440,7 +441,7 @@ func (c *coordinator) buildTools(ctx context.Context, agent config.Agent) ([]fan
}
// TODO: when we support multiple agents we need to change this so that we pass in the agent specific model config
-func (c *coordinator) buildAgentModels(ctx context.Context) (Model, Model, error) {
+func (c *coordinator) buildAgentModels(ctx context.Context, isSubAgent bool) (Model, Model, error) {
largeModelCfg, ok := c.cfg.Models[config.SelectedModelTypeLarge]
if !ok {
return Model{}, Model{}, errors.New("large model not selected")
@@ -455,7 +456,7 @@ func (c *coordinator) buildAgentModels(ctx context.Context) (Model, Model, error
return Model{}, Model{}, errors.New("large model provider not configured")
}
- largeProvider, err := c.buildProvider(largeProviderCfg, largeModelCfg)
+ largeProvider, err := c.buildProvider(largeProviderCfg, largeModelCfg, isSubAgent)
if err != nil {
return Model{}, Model{}, err
}
@@ -465,7 +466,7 @@ func (c *coordinator) buildAgentModels(ctx context.Context) (Model, Model, error
return Model{}, Model{}, errors.New("large model provider not configured")
}
- smallProvider, err := c.buildProvider(smallProviderCfg, largeModelCfg)
+ smallProvider, err := c.buildProvider(smallProviderCfg, largeModelCfg, true)
if err != nil {
return Model{}, Model{}, err
}
@@ -582,15 +583,24 @@ func (c *coordinator) buildOpenrouterProvider(_, apiKey string, headers map[stri
return openrouter.New(opts...)
}
-func (c *coordinator) buildOpenaiCompatProvider(baseURL, apiKey string, headers map[string]string, extraBody map[string]any) (fantasy.Provider, error) {
+func (c *coordinator) buildOpenaiCompatProvider(baseURL, apiKey string, headers map[string]string, extraBody map[string]any, providerID string, isSubAgent bool) (fantasy.Provider, error) {
opts := []openaicompat.Option{
openaicompat.WithBaseURL(baseURL),
openaicompat.WithAPIKey(apiKey),
}
- if c.cfg.Options.Debug {
- httpClient := log.NewHTTPClient()
+
+ // Set HTTP client based on provider and debug mode.
+ var httpClient *http.Client
+ if providerID == string(catwalk.InferenceProviderCopilot) {
+ opts = append(opts, openaicompat.WithUseResponsesAPI())
+ httpClient = copilot.NewClient(isSubAgent, c.cfg.Options.Debug)
+ } else if c.cfg.Options.Debug {
+ httpClient = log.NewHTTPClient()
+ }
+ if httpClient != nil {
opts = append(opts, openaicompat.WithHTTPClient(httpClient))
}
+
if len(headers) > 0 {
opts = append(opts, openaicompat.WithHeaders(headers))
}
@@ -705,7 +715,7 @@ func (c *coordinator) isAnthropicThinking(model config.SelectedModel) bool {
return false
}
-func (c *coordinator) buildProvider(providerCfg config.ProviderConfig, model config.SelectedModel) (fantasy.Provider, error) {
+func (c *coordinator) buildProvider(providerCfg config.ProviderConfig, model config.SelectedModel, isSubAgent bool) (fantasy.Provider, error) {
headers := maps.Clone(providerCfg.ExtraHeaders)
if headers == nil {
headers = make(map[string]string)
@@ -745,7 +755,7 @@ func (c *coordinator) buildProvider(providerCfg config.ProviderConfig, model con
}
providerCfg.ExtraBody["tool_stream"] = true
}
- return c.buildOpenaiCompatProvider(baseURL, apiKey, headers, providerCfg.ExtraBody)
+ return c.buildOpenaiCompatProvider(baseURL, apiKey, headers, providerCfg.ExtraBody, providerCfg.ID, isSubAgent)
case hyper.Name:
return c.buildHyperProvider(baseURL, apiKey)
default:
@@ -790,7 +800,7 @@ func (c *coordinator) Model() Model {
func (c *coordinator) UpdateModels(ctx context.Context) error {
// build the models again so we make sure we get the latest config
- large, small, err := c.buildAgentModels(ctx)
+ large, small, err := c.buildAgentModels(ctx, false)
if err != nil {
return err
}
@@ -53,10 +53,8 @@ func init() {
var rootCmd = &cobra.Command{
Use: "crush",
- Short: "Terminal-based AI assistant for software development",
- Long: `Crush is a powerful terminal-based AI assistant that helps with software development tasks.
-It provides an interactive chat interface with AI capabilities, code analysis, and LSP integration
-to assist developers in writing, debugging, and understanding code directly from the terminal.`,
+ Short: "An AI assistant for software development",
+ Long: "An AI assistant for software development and similar tasks with direct access to the terminal",
Example: `
# Run in interactive mode
crush
@@ -31,7 +31,7 @@ var (
)
func SetInteractive(interactive bool) {
- baseProps = baseProps.Set("interactive", interactive)
+ baseProps = baseProps.Set("Interactive", interactive)
}
func Init() {
@@ -0,0 +1,78 @@
+// Package copilot provides GitHub Copilot integration.
+package copilot
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "log/slog"
+ "net/http"
+ "regexp"
+
+ "github.com/charmbracelet/crush/internal/log"
+)
+
+// NewClient creates a new HTTP client with a custom transport that adds the
+// X-Initiator header based on message history in the request body.
+func NewClient(isSubAgent, debug bool) *http.Client {
+ return &http.Client{
+ Transport: &initiatorTransport{debug: debug, isSubAgent: isSubAgent},
+ }
+}
+
+type initiatorTransport struct {
+ debug bool
+ isSubAgent bool
+}
+
+func (t *initiatorTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+ const (
+ xInitiatorHeader = "X-Initiator"
+ userInitiator = "user"
+ agentInitiator = "agent"
+ )
+
+ if req == nil {
+ return nil, fmt.Errorf("HTTP request is nil")
+ }
+ if req.Body == http.NoBody {
+ // No body to inspect; default to user.
+ req.Header.Set(xInitiatorHeader, userInitiator)
+ slog.Debug("Setting X-Initiator header to user (no request body)")
+ return t.roundTrip(req)
+ }
+
+ // Clone request to avoid modifying the original.
+ req = req.Clone(req.Context())
+
+ // Read the original body into bytes so we can examine it.
+ bodyBytes, err := io.ReadAll(req.Body)
+ if err != nil {
+ return nil, fmt.Errorf("failed to read request body: %w", err)
+ }
+ defer req.Body.Close()
+
+ // Restore the original body using the preserved bytes.
+ req.Body = io.NopCloser(bytes.NewReader(bodyBytes))
+
+ // Check for assistant messages using regex to handle whitespace
+ // variations in the JSON while avoiding full unmarshalling overhead.
+ initiator := userInitiator
+ assistantRolePattern := regexp.MustCompile(`"role"\s*:\s*"assistant"`)
+ if assistantRolePattern.Match(bodyBytes) || t.isSubAgent {
+ slog.Debug("Setting X-Initiator header to agent (found assistant messages in history)")
+ initiator = agentInitiator
+ } else {
+ slog.Debug("Setting X-Initiator header to user (no assistant messages)")
+ }
+ req.Header.Set(xInitiatorHeader, initiator)
+
+ return t.roundTrip(req)
+}
+
+func (t *initiatorTransport) roundTrip(req *http.Request) (*http.Response, error) {
+ if t.debug {
+ return log.NewHTTPClient().Transport.RoundTrip(req)
+ }
+ return http.DefaultTransport.RoundTrip(req)
+}
@@ -10,7 +10,9 @@ import (
"path/filepath"
"regexp"
"strings"
+ "sync"
+ "github.com/charlievieth/fastwalk"
"gopkg.in/yaml.v3"
)
@@ -110,17 +112,32 @@ func splitFrontmatter(content string) (frontmatter, body string, err error) {
// Discover finds all valid skills in the given paths.
func Discover(paths []string) []*Skill {
var skills []*Skill
+ var mu sync.Mutex
seen := make(map[string]bool)
for _, base := range paths {
- filepath.WalkDir(base, func(path string, d os.DirEntry, err error) error {
+ // We use fastwalk with Follow: true instead of filepath.WalkDir because
+ // WalkDir doesn't follow symlinked directories at any depthβonly entry
+ // points. This ensures skills in symlinked subdirectories are discovered.
+ // fastwalk is concurrent, so we protect shared state (seen, skills) with mu.
+ conf := fastwalk.Config{
+ Follow: true,
+ ToSlash: fastwalk.DefaultToSlash(),
+ }
+ fastwalk.Walk(&conf, base, func(path string, d os.DirEntry, err error) error {
if err != nil {
return nil
}
- if d.IsDir() || d.Name() != SkillFileName || seen[path] {
+ if d.IsDir() || d.Name() != SkillFileName {
+ return nil
+ }
+ mu.Lock()
+ if seen[path] {
+ mu.Unlock()
return nil
}
seen[path] = true
+ mu.Unlock()
skill, err := Parse(path)
if err != nil {
slog.Warn("Failed to parse skill file", "path", path, "error", err)
@@ -131,7 +148,9 @@ func Discover(paths []string) []*Skill {
return nil
}
slog.Info("Successfully loaded skill", "name", skill.Name, "path", path)
+ mu.Lock()
skills = append(skills, skill)
+ mu.Unlock()
return nil
})
}
@@ -961,7 +961,10 @@ func (p *chatPage) sendMessage(text string, attachments []message.Attachment) te
session := p.session
var cmds []tea.Cmd
if p.session.ID == "" {
- newSession, err := p.app.Sessions.Create(context.Background(), "New Session")
+ // XXX: The second argument here is the session name, which we leave
+ // blank as it will be auto-generated. Ideally, we remove the need for
+ // that argument entirely.
+ newSession, err := p.app.Sessions.Create(context.Background(), "")
if err != nil {
return util.ReportError(err)
}