Detailed changes
@@ -1263,6 +1263,30 @@
"created_at": "2026-02-16T09:16:57Z",
"repoId": 987670088,
"pullRequestNo": 2236
+ },
+ {
+ "name": "julienrbrt",
+ "id": 29894366,
+ "comment_id": 3920039276,
+ "created_at": "2026-02-18T10:37:53Z",
+ "repoId": 987670088,
+ "pullRequestNo": 2246
+ },
+ {
+ "name": "erikstmartin",
+ "id": 50041,
+ "comment_id": 3937646631,
+ "created_at": "2026-02-20T23:35:55Z",
+ "repoId": 987670088,
+ "pullRequestNo": 2274
+ },
+ {
+ "name": "Jaylonnet",
+ "id": 101283545,
+ "comment_id": 3944717192,
+ "created_at": "2026-02-23T13:12:50Z",
+ "repoId": 987670088,
+ "pullRequestNo": 2293
}
]
}
@@ -57,6 +57,8 @@
- "/vertex/i"
"provider: kimi":
- "/kimi/i"
+"provider: minimax":
+ - "/minimax/i"
"provider: ollama":
- "/llama/i"
"provider: openai chatgpt":
@@ -29,7 +29,7 @@ jobs:
path-to-signatures: ".github/cla-signatures.json"
path-to-document: "https://github.com/charmbracelet/crush/blob/main/CLA.md"
branch: "main"
- allowlist: charmcli,charmcrush,dependabot[bot]
+ allowlist: charmcli,charmcrush,dependabot[bot],Copilot
custom-pr-sign-comment: "I have read the Contributor License Agreement (CLA) and hereby sign the CLA."
custom-notsigned-prcomment: "Thank you for your submission. We really appreciate it! Like many open-source projects, we ask that you sign our [Contributor License Agreement](https://github.com/charmbracelet/crush/blob/main/CLA.md) before we can accept your contribution. You can sign the CLA by just posting a Pull Request comment same as the below format."
lock-pullrequest-aftermerge: false
@@ -15,7 +15,7 @@ jobs:
goreleaser:
uses: charmbracelet/meta/.github/workflows/goreleaser.yml@main
with:
- go_version: "1.25"
+ go_version: "1.26"
macos_sign_entitlements: "./.github/entitlements.plist"
# XXX: remove it after goreleaser 2.14.
goreleaser_version: nightly
@@ -30,11 +30,11 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- - uses: github/codeql-action/init@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
+ - uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
with:
languages: ${{ matrix.language }}
- - uses: github/codeql-action/autobuild@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
- - uses: github/codeql-action/analyze@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
+ - uses: github/codeql-action/autobuild@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
+ - uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
grype:
runs-on: ubuntu-latest
@@ -52,7 +52,7 @@ jobs:
path: "."
fail-build: true
severity-cutoff: critical
- - uses: github/codeql-action/upload-sarif@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
+ - uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
with:
sarif_file: ${{ steps.scan.outputs.sarif }}
@@ -67,13 +67,13 @@ jobs:
persist-credentials: false
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
with:
- go-version: 1.26.0-rc.1 # change to "stable" once Go 1.26 is released
+ go-version: 1.26.0
- name: Install govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest
- name: Run govulncheck
run: |
govulncheck -C . -format sarif ./... > results.sarif
- - uses: github/codeql-action/upload-sarif@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
+ - uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
with:
sarif_file: results.sarif
@@ -86,7 +86,7 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- - uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2
+ - uses: actions/dependency-review-action@05fe4576374b728f0c523d6a13d64c25081e0803 # v4.8.3
with:
fail-on-severity: critical
- allow-licenses: BSD-2-Clause, BSD-3-Clause, MIT, Apache-2.0, MPL-2.0, ISC, LicenseRef-scancode-google-patent-license-golang
+ allow-licenses: BSD-2-Clause, BSD-3-Clause, MIT, MIT-0, Apache-2.0, MPL-2.0, ISC, LicenseRef-scancode-google-patent-license-golang, Unlicense
@@ -25,7 +25,7 @@ jobs:
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
with:
go-version-file: go.mod
- - uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
+ - uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0
with:
version: "nightly"
distribution: goreleaser-pro
@@ -1,14 +1,14 @@
module github.com/charmbracelet/crush
-go 1.25.5
+go 1.26.0
require (
- charm.land/bubbles/v2 v2.0.0-rc.1.0.20260109112849-ae99f46cec66
- charm.land/bubbletea/v2 v2.0.0-rc.2.0.20260209074636-30878e43d7b0
- charm.land/catwalk v0.19.2
- charm.land/fantasy v0.8.1
+ charm.land/bubbles/v2 v2.0.0
+ charm.land/bubbletea/v2 v2.0.0
+ charm.land/catwalk v0.21.1
+ charm.land/fantasy v0.9.0
charm.land/glamour/v2 v2.0.0-20260123212943-6014aa153a9b
- charm.land/lipgloss/v2 v2.0.0-beta.3.0.20260212100304-e18737634dea
+ charm.land/lipgloss/v2 v2.0.0
charm.land/log/v2 v2.0.0-20251110204020-529bb77f35da
charm.land/x/vcr v0.1.1
github.com/JohannesKaufmann/html-to-markdown v1.6.0
@@ -16,8 +16,8 @@ require (
github.com/PuerkitoBio/goquery v1.11.0
github.com/alecthomas/chroma/v2 v2.23.1
github.com/atotto/clipboard v0.1.4
- github.com/aymanbagabas/go-nativeclipboard v0.1.2
- github.com/aymanbagabas/go-udiff v0.3.1
+ github.com/aymanbagabas/go-nativeclipboard v0.1.3
+ github.com/aymanbagabas/go-udiff v0.4.0
github.com/bmatcuk/doublestar/v4 v4.10.0
github.com/charlievieth/fastwalk v1.0.14
github.com/charmbracelet/colorprofile v0.4.2
@@ -31,10 +31,10 @@ require (
github.com/charmbracelet/x/exp/ordered v0.1.0
github.com/charmbracelet/x/exp/slice v0.0.0-20260209194814-eeb2896ac759
github.com/charmbracelet/x/exp/strings v0.1.0
- github.com/charmbracelet/x/powernap v0.0.0-20260209132835-6b065b8ba62c
+ github.com/charmbracelet/x/powernap v0.1.0
github.com/charmbracelet/x/term v0.2.2
- github.com/clipperhouse/displaywidth v0.10.0
- github.com/clipperhouse/uax29/v2 v2.6.0
+ github.com/clipperhouse/displaywidth v0.11.0
+ github.com/clipperhouse/uax29/v2 v2.7.0
github.com/denisbrodbeck/machineid v1.0.1
github.com/disintegration/imaging v1.6.2
github.com/dustin/go-humanize v1.0.1
@@ -45,13 +45,13 @@ require (
github.com/jordanella/go-ansi-paintbrush v0.0.0-20240728195301-b7ad996ecf3d
github.com/lucasb-eyer/go-colorful v1.3.0
github.com/mattn/go-isatty v0.0.20
- github.com/modelcontextprotocol/go-sdk v1.2.0
+ github.com/modelcontextprotocol/go-sdk v1.3.1
github.com/ncruces/go-sqlite3 v0.30.5
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.10.0
- github.com/pressly/goose/v3 v3.26.0
+ github.com/pressly/goose/v3 v3.27.0
github.com/qjebbs/go-jsons v1.0.0-alpha.4
github.com/rivo/uniseg v0.4.7
github.com/sahilm/fuzzy v0.1.1
@@ -62,28 +62,28 @@ require (
github.com/tidwall/sjson v1.2.5
github.com/zeebo/xxh3 v1.1.0
go.uber.org/goleak v1.3.0
- golang.org/x/net v0.49.0
+ golang.org/x/net v0.50.0
golang.org/x/sync v0.19.0
- golang.org/x/text v0.33.0
+ golang.org/x/text v0.34.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
- modernc.org/sqlite v1.44.3
+ modernc.org/sqlite v1.46.1
mvdan.cc/sh/moreinterp v0.0.0-20250902163504-3cf4fd5717a5
mvdan.cc/sh/v3 v3.12.1-0.20250902163504-3cf4fd5717a5
)
require (
- cloud.google.com/go v0.116.0 // indirect
- cloud.google.com/go/auth v0.18.1 // indirect
+ cloud.google.com/go v0.123.0 // indirect
+ cloud.google.com/go/auth v0.18.2 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // 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/andybalholm/cascadia v1.3.3 // indirect
github.com/aws/aws-sdk-go-v2 v1.41.1 // indirect
- github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect
- github.com/aws/aws-sdk-go-v2/config v1.32.7 // indirect
- github.com/aws/aws-sdk-go-v2/credentials v1.19.7 // indirect
+ github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 // indirect
+ github.com/aws/aws-sdk-go-v2/config v1.32.9 // indirect
+ github.com/aws/aws-sdk-go-v2/credentials v1.19.9 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 // indirect
@@ -91,8 +91,8 @@ require (
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.17 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 // indirect
- github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 // indirect
- github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13 // indirect
+ github.com/aws/aws-sdk-go-v2/service/sso v1.30.10 // indirect
+ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.14 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 // indirect
github.com/aws/smithy-go v1.24.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
@@ -103,10 +103,9 @@ require (
github.com/charmbracelet/x/json v0.2.0 // indirect
github.com/charmbracelet/x/termios v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.2.2 // indirect
- github.com/clipperhouse/stringish v0.1.1 // indirect
- github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
- github.com/ebitengine/purego v0.10.0-alpha.3.0.20260102153238-200df6041cff // indirect
+ github.com/ebitengine/purego v0.10.0-alpha.4 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
@@ -118,26 +117,27 @@ require (
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
+ github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
- github.com/google/jsonschema-go v0.3.0 // indirect
+ github.com/google/jsonschema-go v0.4.2 // indirect
github.com/google/s2a-go v0.1.9 // indirect
- github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
- github.com/googleapis/gax-go/v2 v2.15.0 // indirect
+ github.com/googleapis/enterprise-certificate-proxy v0.3.12 // indirect
+ github.com/googleapis/gax-go/v2 v2.17.0 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
- github.com/kaptinlin/go-i18n v0.2.3 // indirect
- github.com/kaptinlin/jsonpointer v0.4.9 // indirect
- github.com/kaptinlin/jsonschema v0.6.10 // indirect
- github.com/kaptinlin/messageformat-go v0.4.9 // indirect
- github.com/klauspost/compress v1.18.0 // indirect
+ github.com/kaptinlin/go-i18n v0.2.9 // indirect
+ github.com/kaptinlin/jsonpointer v0.4.16 // indirect
+ github.com/kaptinlin/jsonschema v0.7.2 // indirect
+ github.com/kaptinlin/messageformat-go v0.4.18 // indirect
+ github.com/klauspost/compress v1.18.4 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
- github.com/mattn/go-runewidth v0.0.19 // indirect
+ github.com/mattn/go-runewidth v0.0.20 // indirect
github.com/mfridman/interpolate v0.0.2 // indirect
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
@@ -148,9 +148,11 @@ require (
github.com/muesli/roff v0.1.0 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/ncruces/julianday v1.0.0 // indirect
- github.com/pierrec/lz4/v4 v4.1.22 // indirect
- github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/pierrec/lz4/v4 v4.1.25 // indirect
+ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
+ github.com/segmentio/asm v1.2.1 // indirect
+ github.com/segmentio/encoding v0.5.3 // indirect
github.com/sethvargo/go-retry v0.3.0 // indirect
github.com/spf13/pflag v1.0.9 // indirect
github.com/tetratelabs/wazero v1.11.0 // indirect
@@ -164,30 +166,29 @@ require (
github.com/yuin/goldmark v1.7.8 // indirect
github.com/yuin/goldmark-emoji v1.0.5 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
- go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
- go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
- go.opentelemetry.io/otel v1.39.0 // indirect
- go.opentelemetry.io/otel/metric v1.39.0 // indirect
- go.opentelemetry.io/otel/trace v1.39.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect
+ go.opentelemetry.io/otel v1.40.0 // indirect
+ go.opentelemetry.io/otel/metric v1.40.0 // indirect
+ go.opentelemetry.io/otel/trace v1.40.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect
- golang.org/x/crypto v0.47.0 // indirect
- golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
- golang.org/x/image v0.34.0 // indirect
- golang.org/x/mod v0.32.0 // indirect
+ golang.org/x/crypto v0.48.0 // indirect
+ golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
+ golang.org/x/image v0.36.0 // indirect
golang.org/x/oauth2 v0.35.0 // indirect
golang.org/x/sys v0.41.0 // indirect
- golang.org/x/term v0.39.0 // indirect
+ golang.org/x/term v0.40.0 // indirect
golang.org/x/time v0.14.0 // indirect
- google.golang.org/api v0.239.0 // indirect
- google.golang.org/genai v1.45.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
+ google.golang.org/api v0.266.0 // indirect
+ google.golang.org/genai v1.47.0 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect
+ google.golang.org/grpc v1.79.1 // indirect
+ google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/dnaeon/go-vcr.v4 v4.0.6-0.20251110073552-01de4eb40290 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
- modernc.org/libc v1.67.6 // indirect
+ modernc.org/libc v1.68.0 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
)
@@ -1,23 +1,23 @@
-charm.land/bubbles/v2 v2.0.0-rc.1.0.20260109112849-ae99f46cec66 h1:2BdJynsAW+8rv9xq6ZS+x0mtacfxpxjIK1KUIeTqBOs=
-charm.land/bubbles/v2 v2.0.0-rc.1.0.20260109112849-ae99f46cec66/go.mod h1:5AbN6cEd/47gkEf8TgiQ2O3RZ5QxMS14l9W+7F9fPC4=
-charm.land/bubbletea/v2 v2.0.0-rc.2.0.20260209074636-30878e43d7b0 h1:HAbpM9TPjZM18D677ww3VnkKXdd2hyMQtHUsVV0HcPQ=
-charm.land/bubbletea/v2 v2.0.0-rc.2.0.20260209074636-30878e43d7b0/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ=
-charm.land/catwalk v0.19.2 h1:exy+egllV6VEuR0e5eGkefnL6xnlszNxy9FpH2vjss4=
-charm.land/catwalk v0.19.2/go.mod h1:rFC/V96rIHX7VES215c/qzI1EW/Moo1ggs1Q6seTy5s=
-charm.land/fantasy v0.8.1 h1:2zQovDrd26g+/X2+/6bMomTX54HmZWNNHWeorDO0WRo=
-charm.land/fantasy v0.8.1/go.mod h1:KJ8vjy9FH7G2aeR/fL+os2uFHkQ4js2+UJVbsUKCXYM=
+charm.land/bubbles/v2 v2.0.0 h1:tE3eK/pHjmtrDiRdoC9uGNLgpopOd8fjhEe31B/ai5s=
+charm.land/bubbles/v2 v2.0.0/go.mod h1:rCHoleP2XhU8um45NTuOWBPNVHxnkXKTiZqcclL/qOI=
+charm.land/bubbletea/v2 v2.0.0 h1:p0d6CtWyJXJ9GfzMpUUqbP/XUUhhlk06+vCKWmox1wQ=
+charm.land/bubbletea/v2 v2.0.0/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ=
+charm.land/catwalk v0.21.1 h1:CO6GDgfl6u0Gx6v3vC64B8DEnX+PhjDxX7IrVyu3Feg=
+charm.land/catwalk v0.21.1/go.mod h1:rFC/V96rIHX7VES215c/qzI1EW/Moo1ggs1Q6seTy5s=
+charm.land/fantasy v0.9.0 h1:2KzDYZC3IDb6T8KhWn4akqDHoU5Evr+VwL2xbaWtXmM=
+charm.land/fantasy v0.9.0/go.mod h1:vpR/vcgCtKZ5SWHNbW/5c1b+DMDNNO15j+t/evoQb/4=
charm.land/glamour/v2 v2.0.0-20260123212943-6014aa153a9b h1:A6IUUyChZDWP16RUdRJCfmYISAKWQGyIcfhZJUCViQ0=
charm.land/glamour/v2 v2.0.0-20260123212943-6014aa153a9b/go.mod h1:J3kVhY6oHXZq5f+8vC3hmDO95fEvbqj3z7xDwxrfzU8=
-charm.land/lipgloss/v2 v2.0.0-beta.3.0.20260212100304-e18737634dea h1:XBmpGhIKPN8o9VjuXg+X5WXFsEqUs/YtPx0Q0zzmTTA=
-charm.land/lipgloss/v2 v2.0.0-beta.3.0.20260212100304-e18737634dea/go.mod h1:xylWHUuJWcFJqoGrKdZP8Z0y3THC6xqrnfl1IYDviTE=
+charm.land/lipgloss/v2 v2.0.0 h1:sd8N/B3x892oiOjFfBQdXBQp3cAkvjGaU5TvVZC3ivo=
+charm.land/lipgloss/v2 v2.0.0/go.mod h1:w6SnmsBFBmEFBodiEDurGS/sdUY/u1+v72DqUzc6J14=
charm.land/log/v2 v2.0.0-20251110204020-529bb77f35da h1:vZa/Ow0uLclpfaDY0ubjzE+B0eLQqi2zanmpeALanow=
charm.land/log/v2 v2.0.0-20251110204020-529bb77f35da/go.mod h1:Tj12StbPc4GwksDF6XwhC9wdXouinIVxRGKKmmmzdSU=
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.18.1 h1:IwTEx92GFUo2pJ6Qea0EU3zYvKnTAeRCODxfA/G5UWs=
-cloud.google.com/go/auth v0.18.1/go.mod h1:GfTYoS9G3CWpRA3Va9doKN9mjPGRS+v41jmZAhBzbrA=
+cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
+cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
+cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
+cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M=
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.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
@@ -50,12 +50,12 @@ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU=
github.com/aws/aws-sdk-go-v2 v1.41.1/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.32.7 h1:vxUyWGUwmkQ2g19n7JY/9YL8MfAIl7bTesIUykECXmY=
-github.com/aws/aws-sdk-go-v2/config v1.32.7/go.mod h1:2/Qm5vKUU/r7Y+zUk/Ptt2MDAEKAfUtKc1+3U1Mo3oY=
-github.com/aws/aws-sdk-go-v2/credentials v1.19.7 h1:tHK47VqqtJxOymRrNtUXN5SP/zUTvZKeLx4tH6PGQc8=
-github.com/aws/aws-sdk-go-v2/credentials v1.19.7/go.mod h1:qOZk8sPDrxhf+4Wf4oT2urYJrYt3RejHSzgAquYeppw=
+github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU=
+github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4=
+github.com/aws/aws-sdk-go-v2/config v1.32.9 h1:ktda/mtAydeObvJXlHzyGpK1xcsLaP16zfUPDGoW90A=
+github.com/aws/aws-sdk-go-v2/config v1.32.9/go.mod h1:U+fCQ+9QKsLW786BCfEjYRj34VVTbPdsLP3CHSYXMOI=
+github.com/aws/aws-sdk-go-v2/credentials v1.19.9 h1:sWvTKsyrMlJGEuj/WgrwilpoJ6Xa1+KhIpGdzw7mMU8=
+github.com/aws/aws-sdk-go-v2/credentials v1.19.9/go.mod h1:+J44MBhmfVY/lETFiKI+klz0Vym2aCmIjqgClMmW82w=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 h1:I0GyV8wiYrP8XpA70g1HBcQO1JlQxCMTW9npl5UbDHY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17/go.mod h1:tyw7BOl5bBe/oqvoIeECFJjMdzXoa/dfVz3QQ5lgHGA=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U=
@@ -70,18 +70,18 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 h1:RuNSMooz
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17/go.mod h1:F2xxQ9TZz5gDWsclCtPQscGpP0VUOc8RqgFM3vDENmU=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 h1:VrhDvQib/i0lxvr3zqlUwLwJP4fpmpyD9wYG1vfSu+Y=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.5/go.mod h1:k029+U8SY30/3/ras4G/Fnv/b88N4mAfliNn08Dem4M=
-github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 h1:v6EiMvhEYBoHABfbGB4alOYmCIrcgyPPiBE1wZAEbqk=
-github.com/aws/aws-sdk-go-v2/service/sso v1.30.9/go.mod h1:yifAsgBxgJWn3ggx70A3urX2AN49Y5sJTD1UQFlfqBw=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13 h1:gd84Omyu9JLriJVCbGApcLzVR3XtmC4ZDPcAI6Ftvds=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13/go.mod h1:sTGThjphYE4Ohw8vJiRStAcu3rbjtXRsdNB0TvZ5wwo=
+github.com/aws/aws-sdk-go-v2/service/sso v1.30.10 h1:+VTRawC4iVY58pS/lzpo0lnoa/SYNGF4/B/3/U5ro8Y=
+github.com/aws/aws-sdk-go-v2/service/sso v1.30.10/go.mod h1:yifAsgBxgJWn3ggx70A3urX2AN49Y5sJTD1UQFlfqBw=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.14 h1:0jbJeuEHlwKJ9PfXtpSFc4MF+WIWORdhN1n30ITZGFM=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.14/go.mod h1:sTGThjphYE4Ohw8vJiRStAcu3rbjtXRsdNB0TvZ5wwo=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 h1:5fFjR/ToSOzB2OQ/XqWpZBmNvmP/pJ1jOWYlFDJTjRQ=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.6/go.mod h1:qgFDZQSD/Kys7nJnVqYlWKnh0SSdMjAi0uSwON4wgYQ=
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-nativeclipboard v0.1.2 h1:Z2iVRWQ4IynMLWM6a+lWH2Nk5gPyEtPRMuBIyZ2dECM=
-github.com/aymanbagabas/go-nativeclipboard v0.1.2/go.mod h1:BVJhN7hs5DieCzUB2Atf4Yk9Y9kFe62E95+gOjpJq6Q=
-github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
-github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
+github.com/aymanbagabas/go-nativeclipboard v0.1.3 h1:FmAWHPTwneAixu7uGDn3cL42xPlUCdNp2J8egMn3P1k=
+github.com/aymanbagabas/go-nativeclipboard v0.1.3/go.mod h1:2o7MyZwwi4pmXXpOpvOS5FwaHyoCIUks0ktjUvB0EoE=
+github.com/aymanbagabas/go-udiff v0.4.0 h1:TKnLPh7IbnizJIBKFWa9mKayRUBQ9Kh1BPCk6w2PnYM=
+github.com/aymanbagabas/go-udiff v0.4.0/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
@@ -120,28 +120,27 @@ github.com/charmbracelet/x/exp/strings v0.1.0 h1:i69S2XI7uG1u4NLGeJPSYU++Nmjvpo9
github.com/charmbracelet/x/exp/strings v0.1.0/go.mod h1:/ehtMPNh9K4odGFkqYJKpIYyePhdp1hLBRvyY4bWkH8=
github.com/charmbracelet/x/json v0.2.0 h1:DqB+ZGx2h+Z+1s98HOuOyli+i97wsFQIxP2ZQANTPrQ=
github.com/charmbracelet/x/json v0.2.0/go.mod h1:opFIflx2YgXgi49xVUu8gEQ21teFAxyMwvOiZhIvWNM=
-github.com/charmbracelet/x/powernap v0.0.0-20260209132835-6b065b8ba62c h1:6E+Y7WQ6Rnw+FmeXoRBtyCBkPcXS0hSMuws6QBr+nyQ=
-github.com/charmbracelet/x/powernap v0.0.0-20260209132835-6b065b8ba62c/go.mod h1:cmdl5zlP5mR8TF2Y68UKc7hdGUDiSJ2+4hk0h04Hsx4=
+github.com/charmbracelet/x/powernap v0.1.0 h1:xJPM8szKu3UY4ZuW3puc8R7Hpftq2nLIygyRe3EGUoE=
+github.com/charmbracelet/x/powernap v0.1.0/go.mod h1:cmdl5zlP5mR8TF2Y68UKc7hdGUDiSJ2+4hk0h04Hsx4=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=
github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=
-github.com/clipperhouse/displaywidth v0.10.0 h1:GhBG8WuerxjFQQYeuZAeVTuyxuX+UraiZGD4HJQ3Y8g=
-github.com/clipperhouse/displaywidth v0.10.0/go.mod h1:XqJajYsaiEwkxOj4bowCTMcT1SgvHo9flfF3jQasdbs=
-github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
-github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
-github.com/clipperhouse/uax29/v2 v2.6.0 h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos=
-github.com/clipperhouse/uax29/v2 v2.6.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/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
+github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
+github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
+github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
+github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
+github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
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=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
@@ -150,13 +149,13 @@ 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/ebitengine/purego v0.10.0-alpha.3.0.20260102153238-200df6041cff h1:vAcU1VsCRstZ9ty11yD/L0WDyT73S/gVfmuWvcWX5DA=
-github.com/ebitengine/purego v0.10.0-alpha.3.0.20260102153238-200df6041cff/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
-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/ebitengine/purego v0.10.0-alpha.4 h1:JzPbdf+cqbyT9sZtP4xnqelwUXwf7LvD8xKS6+ofTds=
+github.com/ebitengine/purego v0.10.0-alpha.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
+github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
+github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g=
+github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=
+github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=
+github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
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=
@@ -185,8 +184,8 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
-github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
-github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
+github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
+github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
@@ -194,18 +193,18 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
-github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q=
-github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
+github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8=
+github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
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.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/googleapis/enterprise-certificate-proxy v0.3.12 h1:Fg+zsqzYEs1ZnvmcztTYxhgCBsx3eEhEwQ1W/lHq/sQ=
+github.com/googleapis/enterprise-certificate-proxy v0.3.12/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
+github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc=
+github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@@ -226,16 +225,16 @@ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA
github.com/jordanella/go-ansi-paintbrush v0.0.0-20240728195301-b7ad996ecf3d h1:on25kP+Sx7sxUMRQiA8gdcToAGet4DK/EIA30mXre+4=
github.com/jordanella/go-ansi-paintbrush v0.0.0-20240728195301-b7ad996ecf3d/go.mod h1:SV0W0APWP9MZ1/gfDQ/NzzTlWdIgYZ/ZbpN4d/UXRYw=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
-github.com/kaptinlin/go-i18n v0.2.3 h1:jyN/YOXXLcnGRBLdU+a8+6782B97fWE5aQqAHtvvk8Q=
-github.com/kaptinlin/go-i18n v0.2.3/go.mod h1:O+Ax4HkMO0Jt4OaP4E4WCx0PAADeWkwk8Jgt9bjAU1w=
-github.com/kaptinlin/jsonpointer v0.4.9 h1:o//bYf4PCvnMJIIX8bIg77KB6DO3wBPAabRyPRKh680=
-github.com/kaptinlin/jsonpointer v0.4.9/go.mod h1:9y0LgXavlmVE5FSHShY5LRlURJJVhbyVJSRWkilrTqA=
-github.com/kaptinlin/jsonschema v0.6.10 h1:CYded7nrwVu7pU1GaIjtd9dSzgqZjh7+LTKFaWqS08I=
-github.com/kaptinlin/jsonschema v0.6.10/go.mod h1:ZXZ4K5KrRmCCF1i6dgvBsQifl+WTb8XShKj0NpQNrz8=
-github.com/kaptinlin/messageformat-go v0.4.9 h1:FR5j5n4aL4nG0afKn9vvANrKxLu7HjmbhJnw5ogIwAQ=
-github.com/kaptinlin/messageformat-go v0.4.9/go.mod h1:qZzrGrlvWDz2KyyvN3dOWcK9PVSRV1BnfnNU+zB/RWc=
-github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
-github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
+github.com/kaptinlin/go-i18n v0.2.9 h1:96TWNQI0j5nPhcmeFaCyX8SfyNhA0CTjeilLTy7ol9M=
+github.com/kaptinlin/go-i18n v0.2.9/go.mod h1:Sm0GTLS6hbFDrUQahycQfHF377WR9VF5eDgWrwljCAk=
+github.com/kaptinlin/jsonpointer v0.4.16 h1:Ux4w4FY+uLv+K+TxaCJtM/TpPv+1+eS6gH4Z9/uhOuA=
+github.com/kaptinlin/jsonpointer v0.4.16/go.mod h1:SsfsjqnHG5zuKo1DTBzk1VknaHlL4osHw+X9kZKukpU=
+github.com/kaptinlin/jsonschema v0.7.2 h1:I4AiYZ/be3gtWi4Mb7vtY8W6zN6f4YvT2eHCUXXJfmQ=
+github.com/kaptinlin/jsonschema v0.7.2/go.mod h1:Y6SZ/x3m9LZzEQY/NxCjHCmBPprBGMLWZDX3mFN0lJQ=
+github.com/kaptinlin/messageformat-go v0.4.18 h1:RBlHVWgZyoxTcUgGWBsl2AcyScq/urqbLZvzgryTmSI=
+github.com/kaptinlin/messageformat-go v0.4.18/go.mod h1:ntI3154RnqJgr7GaC+vZBnIExl2V3sv9selvRNNEM24=
+github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
+github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
@@ -255,16 +254,16 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
-github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
+github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ=
+github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
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.2.0 h1:Y23co09300CEk8iZ/tMxIX1dVmKZkzoSBZOpJwUnc/s=
-github.com/modelcontextprotocol/go-sdk v1.2.0/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10=
+github.com/modelcontextprotocol/go-sdk v1.3.1 h1:TfqtNKOIWN4Z1oqmPAiWDC2Jq7K9OdJaooe0teoXASI=
+github.com/modelcontextprotocol/go-sdk v1.3.1/go.mod h1:DgVX498dMD8UJlseK1S5i1T4tFz2fkBk4xogC3D15nw=
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=
@@ -287,8 +286,8 @@ github.com/openai/openai-go/v2 v2.7.1 h1:/tfvTJhfv7hTSL8mWwc5VL4WLLSDL5yn9VqVykd
github.com/openai/openai-go/v2 v2.7.1/go.mod h1:jrJs23apqJKKbT+pqtFgNKpRju/KP9zpUTZhz3GElQE=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
-github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
-github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/pierrec/lz4/v4 v4.1.25 h1:kocOqRffaIbU5djlIBr7Wh+cx82C0vtFb0fOurZHqD0=
+github.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
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=
@@ -296,12 +295,13 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posthog/posthog-go v1.10.0 h1:wfoy7Jfb4LigCoHYyMZoiJmmEoCLOkSaYfDxM/NtCqY=
github.com/posthog/posthog-go v1.10.0/go.mod h1:wB3/9Q7d9gGb1P/yf/Wri9VBlbP8oA8z++prRzL5OcY=
-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/pressly/goose/v3 v3.27.0 h1:/D30gVTuQhu0WsNZYbJi4DMOsx1lNq+6SkLe+Wp59BM=
+github.com/pressly/goose/v3 v3.27.0/go.mod h1:3ZBeCXqzkgIRvrEMDkYh1guvtoJTU5oMMuDdkutoM78=
github.com/qjebbs/go-jsons v1.0.0-alpha.4 h1:Qsb4ohRUHQODIUAsJKdKJ/SIDbsO7oGOzsfy+h1yQZs=
github.com/qjebbs/go-jsons v1.0.0-alpha.4/go.mod h1:wNJrtinHyC3YSf6giEh4FJN8+yZV7nXBjvmfjhBIcw4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
@@ -315,6 +315,10 @@ github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y=
github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
+github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
+github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
+github.com/segmentio/encoding v0.5.3 h1:OjMgICtcSFuNvQCdwqMCv9Tg7lEOXGwm1J5RPQccx6w=
+github.com/segmentio/encoding v0.5.3/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
@@ -366,20 +370,20 @@ github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
-go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
-go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
-go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
-go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
-go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
-go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
-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.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
-go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 h1:XmiuHzgJt067+a6kwyAzkhXooYVv3/TOw9cM2VfJgUM=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0/go.mod h1:KDgtbWKTQs4bM+VPUr6WlL9m/WXcmkCcBlIzqxPGzmI=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=
+go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
+go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
+go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
+go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
+go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
+go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
+go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
+go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
+go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
+go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
@@ -394,20 +398,20 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
-golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
-golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
-golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
-golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
+golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
+golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
+golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
+golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8=
-golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU=
+golang.org/x/image v0.36.0 h1:Iknbfm1afbgtwPTmHnS2gTM/6PPZfH+z2EFuOkSbqwc=
+golang.org/x/image v0.36.0/go.mod h1:YsWD2TyyGKiIX1kZlu9QfKIsQ4nAAK9bdgdrIsE7xy4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
-golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
-golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
+golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
+golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
@@ -419,8 +423,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
-golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
-golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
+golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
+golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -461,8 +465,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
-golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
-golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
+golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
+golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@@ -472,8 +476,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
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.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
-golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
+golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
+golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
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=
@@ -482,21 +486,21 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
-golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
-golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
+golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
+golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
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.45.0 h1:s80ZpS42XW0zu/ogiOtenCio17nJ7reEFJjoCftukpA=
-google.golang.org/genai v1.45.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=
+google.golang.org/api v0.266.0 h1:hco+oNCf9y7DmLeAtHJi/uBAY7n/7XC9mZPxu1ROiyk=
+google.golang.org/api v0.266.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0=
+google.golang.org/genai v1.47.0 h1:iWCS7gEdO6rctOqfCYLOrZGKu2D+N42aTnCEcBvB1jo=
+google.golang.org/genai v1.47.0/go.mod h1:A3kkl0nyBjyFlNjgxIwKq70julKbIxpSxqKO5gw/gmk=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
+google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
+google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
+google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
+google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@@ -516,18 +520,18 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
-modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
-modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
+modernc.org/ccgo/v4 v4.30.2 h1:4yPaaq9dXYXZ2V8s1UgrC3KIj580l2N4ClrLwnbv2so=
+modernc.org/ccgo/v4 v4.30.2/go.mod h1:yZMnhWEdW0qw3EtCndG1+ldRrVGS+bIwyWmAWzS0XEw=
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
-modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
-modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
+modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
+modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
-modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
-modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
+modernc.org/libc v1.68.0 h1:PJ5ikFOV5pwpW+VqCK1hKJuEWsonkIJhhIXyuF/91pQ=
+modernc.org/libc v1.68.0/go.mod h1:NnKCYeoYgsEqnY3PgvNgAeaJnso968ygU8Z0DxjoEc0=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
@@ -536,8 +540,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
-modernc.org/sqlite v1.44.3 h1:+39JvV/HWMcYslAwRxHb8067w+2zowvFOUrOWIy9PjY=
-modernc.org/sqlite v1.44.3/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
+modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU=
+modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
@@ -852,10 +852,7 @@ func (a *sessionAgent) generateTitle(ctx context.Context, sessionID string, user
title = thinkTagRegex.ReplaceAllString(title, "")
title = strings.TrimSpace(title)
- if title == "" {
- slog.Debug("Empty title; using fallback")
- title = defaultSessionName
- }
+ title = cmp.Or(title, defaultSessionName)
// Calculate usage and cost.
var openrouterCost *float64
@@ -473,9 +473,10 @@ func (c *coordinator) buildTools(ctx context.Context, agent config.Agent) ([]fan
}
if len(tools) == 0 || slices.Contains(tools, tool.MCPToolName()) {
filteredTools = append(filteredTools, tool)
+ break
}
+ slog.Debug("MCP not allowed", "tool", tool.Name(), "agent", agent.Name)
}
- slog.Debug("MCP not allowed", "tool", tool.Name(), "agent", agent.Name)
}
slices.SortFunc(filteredTools, func(a, b fantasy.AgentTool) int {
return strings.Compare(a.Info().Name, b.Info().Name)
@@ -506,10 +507,10 @@ func (c *coordinator) buildAgentModels(ctx context.Context, isSubAgent bool) (Mo
smallProviderCfg, ok := c.cfg.Providers.Get(smallModelCfg.Provider)
if !ok {
- return Model{}, Model{}, errors.New("large model provider not configured")
+ return Model{}, Model{}, errors.New("small model provider not configured")
}
- smallProvider, err := c.buildProvider(smallProviderCfg, largeModelCfg, true)
+ smallProvider, err := c.buildProvider(smallProviderCfg, smallModelCfg, true)
if err != nil {
return Model{}, Model{}, err
}
@@ -567,14 +568,19 @@ func (c *coordinator) buildAgentModels(ctx context.Context, isSubAgent bool) (Mo
}, nil
}
-func (c *coordinator) buildAnthropicProvider(baseURL, apiKey string, headers map[string]string) (fantasy.Provider, error) {
+func (c *coordinator) buildAnthropicProvider(baseURL, apiKey string, headers map[string]string, providerID string) (fantasy.Provider, error) {
var opts []anthropic.Option
- if strings.HasPrefix(apiKey, "Bearer ") {
+ switch {
+ case strings.HasPrefix(apiKey, "Bearer "):
// NOTE: Prevent the SDK from picking up the API key from env.
os.Setenv("ANTHROPIC_API_KEY", "")
headers["Authorization"] = apiKey
- } else if apiKey != "" {
+ case providerID == string(catwalk.InferenceProviderMiniMax):
+ // NOTE: Prevent the SDK from picking up the API key from env.
+ os.Setenv("ANTHROPIC_API_KEY", "")
+ headers["Authorization"] = "Bearer " + apiKey
+ case apiKey != "":
// X-Api-Key header
opts = append(opts, anthropic.WithAPIKey(apiKey))
}
@@ -794,7 +800,7 @@ func (c *coordinator) buildProvider(providerCfg config.ProviderConfig, model con
case openai.Name:
return c.buildOpenaiProvider(baseURL, apiKey, headers)
case anthropic.Name:
- return c.buildAnthropicProvider(baseURL, apiKey, headers)
+ return c.buildAnthropicProvider(baseURL, apiKey, headers, providerCfg.ID)
case openrouter.Name:
return c.buildOpenrouterProvider(baseURL, apiKey, headers)
case vercel.Name:
@@ -7,7 +7,6 @@ import (
_ "embed"
"fmt"
"html/template"
- "os"
"path/filepath"
"runtime"
"strings"
@@ -15,6 +14,7 @@ import (
"charm.land/fantasy"
"github.com/charmbracelet/crush/internal/config"
+ "github.com/charmbracelet/crush/internal/fsext"
"github.com/charmbracelet/crush/internal/permission"
"github.com/charmbracelet/crush/internal/shell"
)
@@ -431,12 +431,7 @@ func countLines(s string) int {
func normalizeWorkingDir(path string) string {
if runtime.GOOS == "windows" {
- cwd, err := os.Getwd()
- if err != nil {
- cwd = "C:"
- }
- path = strings.ReplaceAll(path, filepath.VolumeName(cwd), "")
+ path = strings.ReplaceAll(path, fsext.WindowsWorkingDirDrive(), "")
}
-
return filepath.ToSlash(path)
}
@@ -2,6 +2,7 @@ package tools
import (
"bytes"
+ "cmp"
"context"
_ "embed"
"fmt"
@@ -39,10 +40,7 @@ func NewGlobTool(workingDir string) fantasy.AgentTool {
return fantasy.NewTextErrorResponse("pattern is required"), nil
}
- searchPath := params.Path
- if searchPath == "" {
- searchPath = workingDir
- }
+ searchPath := cmp.Or(params.Path, workingDir)
files, truncated, err := globFiles(ctx, params.Pattern, searchPath, 100)
if err != nil {
@@ -81,7 +79,7 @@ func globFiles(ctx context.Context, pattern, searchPath string, limit int) ([]st
slog.Warn("Ripgrep execution failed, falling back to doublestar", "error", err)
}
- return fsext.GlobWithDoubleStar(pattern, searchPath, limit)
+ return fsext.GlobGitignoreAware(pattern, searchPath, limit)
}
func runRipgrep(cmd *exec.Cmd, searchRoot string, limit int) ([]string, error) {
@@ -3,6 +3,7 @@ package tools
import (
"bufio"
"bytes"
+ "cmp"
"context"
_ "embed"
"encoding/json"
@@ -115,10 +116,7 @@ func NewGrepTool(workingDir string, config config.ToolGrep) fantasy.AgentTool {
searchPattern = escapeRegexPattern(params.Pattern)
}
- searchPath := params.Path
- if searchPath == "" {
- searchPath = workingDir
- }
+ searchPath := cmp.Or(params.Path, workingDir)
searchCtx, cancel := context.WithTimeout(ctx, config.GetTimeout())
defer cancel()
@@ -2,6 +2,7 @@ package tools
import (
"context"
+ "runtime"
"testing"
"time"
@@ -98,6 +99,10 @@ func TestBackgroundShell_MultipleOutputCalls(t *testing.T) {
func TestBackgroundShell_EmptyOutput(t *testing.T) {
t.Parallel()
+ if runtime.GOOS == "windows" {
+ t.Skip("This test is flacky on Windows for some reason")
+ }
+
workingDir := t.TempDir()
ctx := context.Background()
@@ -1,6 +1,7 @@
package tools
import (
+ "cmp"
"context"
_ "embed"
"fmt"
@@ -74,13 +75,7 @@ func NewListMCPResourcesTool(cfg *config.Config, permissions permission.Service)
if resource == nil {
continue
}
- title := resource.Title
- if title == "" {
- title = resource.Name
- }
- if title == "" {
- title = resource.URI
- }
+ title := cmp.Or(resource.Title, resource.Name, resource.URI)
line := fmt.Sprintf("- %s", title)
if resource.URI != "" {
line = fmt.Sprintf("%s (%s)", line, resource.URI)
@@ -2,11 +2,13 @@ package mcp
import (
"context"
+ "errors"
"iter"
"log/slog"
"github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/csync"
+ "github.com/modelcontextprotocol/go-sdk/jsonrpc"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
@@ -81,11 +83,22 @@ func getResources(ctx context.Context, c *ClientSession) ([]*Resource, error) {
}
result, err := c.ListResources(ctx, &mcp.ListResourcesParams{})
if err != nil {
+ // Handle "Method not found" errors from MCP servers that don't support resources/list
+ if isMethodNotFoundError(err) {
+ slog.Warn("MCP server does not support resources/list", "error", err)
+ return nil, nil
+ }
return nil, err
}
return result.Resources, nil
}
+// isMethodNotFoundError checks if the error is a JSON-RPC "Method not found" error.
+func isMethodNotFoundError(err error) bool {
+ var rpcErr *jsonrpc.Error
+ return errors.As(err, &rpcErr) && rpcErr != nil && rpcErr.Code == jsonrpc.CodeMethodNotFound
+}
+
func updateResources(name string, resources []*Resource) int {
if len(resources) == 0 {
allResources.Del(name)
@@ -17,6 +17,7 @@ import (
"github.com/charmbracelet/crush/internal/filetracker"
"github.com/charmbracelet/crush/internal/lsp"
"github.com/charmbracelet/crush/internal/permission"
+ "github.com/charmbracelet/crush/internal/skills"
)
//go:embed view.md
@@ -34,9 +35,19 @@ type ViewPermissionsParams struct {
Limit int `json:"limit"`
}
+type ViewResourceType string
+
+const (
+ ViewResourceUnset ViewResourceType = ""
+ ViewResourceSkill ViewResourceType = "skill"
+)
+
type ViewResponseMetadata struct {
- FilePath string `json:"file_path"`
- Content string `json:"content"`
+ FilePath string `json:"file_path"`
+ Content string `json:"content"`
+ ResourceType ViewResourceType `json:"resource_type,omitempty"`
+ ResourceName string `json:"resource_name,omitempty"`
+ ResourceDescription string `json:"resource_description,omitempty"`
}
const (
@@ -175,13 +186,13 @@ func NewViewTool(
// Read the file content
content, lineCount, err := readTextFile(filePath, params.Offset, params.Limit)
+ if err != nil {
+ return fantasy.ToolResponse{}, fmt.Errorf("error reading file: %w", err)
+ }
isValidUt8 := utf8.ValidString(content)
if !isValidUt8 {
return fantasy.NewTextErrorResponse("File content is not valid UTF-8"), nil
}
- if err != nil {
- return fantasy.ToolResponse{}, fmt.Errorf("error reading file: %w", err)
- }
notifyLSPs(ctx, lspManager, filePath)
output := "<file>\n"
@@ -196,12 +207,22 @@ func NewViewTool(
output += "\n</file>\n"
output += getDiagnostics(filePath, lspManager)
filetracker.RecordRead(ctx, sessionID, filePath)
+
+ meta := ViewResponseMetadata{
+ FilePath: filePath,
+ Content: content,
+ }
+ if isSkillFile {
+ if skill, err := skills.Parse(filePath); err == nil {
+ meta.ResourceType = ViewResourceSkill
+ meta.ResourceName = skill.Name
+ meta.ResourceDescription = skill.Description
+ }
+ }
+
return fantasy.WithResponseMetadata(
fantasy.NewTextResponse(output),
- ViewResponseMetadata{
- FilePath: filePath,
- Content: content,
- },
+ meta,
), nil
})
}
@@ -246,6 +246,7 @@ func (app *App) RunNonInteractive(ctx context.Context, output io.Writer, prompt,
done <- response{
err: fmt.Errorf("failed to start agent processing stream: %w", err),
}
+ return
}
done <- response{
result: result,
@@ -551,7 +552,7 @@ func (app *App) Shutdown() {
var wg sync.WaitGroup
// Shared shutdown context for all timeout-bounded cleanup.
- shutdownCtx, cancel := context.WithTimeout(app.globalCtx, 5*time.Second)
+ shutdownCtx, cancel := context.WithTimeout(context.WithoutCancel(app.globalCtx), 5*time.Second)
defer cancel()
// Send exit event
@@ -67,6 +67,8 @@ func updateLSPState(name string, state lsp.ServerState, err error, client *lsp.C
}
if state == lsp.StateReady {
info.ConnectedAt = time.Now()
+ } else if existing, ok := lspStates.Get(name); ok {
+ info.ConnectedAt = existing.ConnectedAt
}
lspStates.Set(name, info)
@@ -70,8 +70,8 @@ func findModels(providers map[string]config.ProviderConfig, largeModel, smallMod
}
func filter(modelFilter, providerFilter, model, provider string) bool {
- return modelFilter != "" && model == modelFilter &&
- (providerFilter == "" || provider == providerFilter)
+ return modelFilter != "" && strings.EqualFold(model, modelFilter) &&
+ (providerFilter == "" || strings.EqualFold(provider, providerFilter))
}
// Validate and return a single match.
@@ -799,11 +799,6 @@ func (c *ProviderConfig) TestConnection(resolver VariableResolver) error {
return fmt.Errorf("invalid API key format for provider %s", c.ID)
}
return nil
- case catwalk.InferenceProviderIoNet:
- if !strings.HasPrefix(apiKey, "io-") {
- return fmt.Errorf("invalid API key format for provider %s", c.ID)
- }
- return nil
}
switch c.Type {
@@ -219,15 +219,19 @@ func (c *Config) configureProviders(env env.Env, resolver VariableResolver, know
switch p.ID {
// Handle specific providers that require additional configuration
case catwalk.InferenceProviderVertexAI:
- if !hasVertexCredentials(env) {
+ var (
+ project = env.Get("VERTEXAI_PROJECT")
+ location = env.Get("VERTEXAI_LOCATION")
+ )
+ if project == "" || location == "" {
if configExists {
slog.Warn("Skipping Vertex AI provider due to missing credentials")
c.Providers.Del(string(p.ID))
}
continue
}
- prepared.ExtraParams["project"] = env.Get("VERTEXAI_PROJECT")
- prepared.ExtraParams["location"] = env.Get("VERTEXAI_LOCATION")
+ prepared.ExtraParams["project"] = project
+ prepared.ExtraParams["location"] = location
case catwalk.InferenceProviderAzure:
endpoint, err := resolver.ResolveValue(p.APIEndpoint)
if err != nil || endpoint == "" {
@@ -278,13 +282,9 @@ func (c *Config) configureProviders(env env.Env, resolver VariableResolver, know
// Make sure the provider ID is set
providerConfig.ID = id
- if providerConfig.Name == "" {
- providerConfig.Name = id // Use ID as name if not set
- }
+ providerConfig.Name = cmp.Or(providerConfig.Name, id) // Use ID as name if not set
// default to OpenAI if not set
- if providerConfig.Type == "" {
- providerConfig.Type = catwalk.TypeOpenAICompat
- }
+ providerConfig.Type = cmp.Or(providerConfig.Type, catwalk.TypeOpenAICompat)
if !slices.Contains(catwalk.KnownProviderTypes(), providerConfig.Type) && providerConfig.Type != hyper.Name {
slog.Warn("Skipping custom provider due to unsupported provider type", "provider", id)
c.Providers.Del(id)
@@ -412,9 +412,7 @@ func (c *Config) setDefaults(workingDir, dataDir string) {
c.Options.Attribution.TrailerStyle = TrailerStyleAssistedBy
}
}
- if c.Options.InitializeAs == "" {
- c.Options.InitializeAs = defaultInitializeAs
- }
+ c.Options.InitializeAs = cmp.Or(c.Options.InitializeAs, defaultInitializeAs)
}
// applyLSPDefaults applies default values from powernap to LSP configurations
@@ -445,9 +443,7 @@ func (c *Config) applyLSPDefaults() {
if len(cfg.RootMarkers) == 0 {
cfg.RootMarkers = base.RootMarkers
}
- if cfg.Command == "" {
- cfg.Command = base.Command
- }
+ cfg.Command = cmp.Or(cfg.Command, base.Command)
if len(cfg.Args) == 0 {
cfg.Args = base.Args
}
@@ -685,12 +681,6 @@ func loadFromBytes(configs [][]byte) (*Config, error) {
return &config, nil
}
-func hasVertexCredentials(env env.Env) bool {
- hasProject := env.Get("VERTEXAI_PROJECT") != ""
- hasLocation := env.Get("VERTEXAI_LOCATION") != ""
- return hasProject && hasLocation
-}
-
func hasAWSCredentials(env env.Env) bool {
if env.Get("AWS_BEARER_TOKEN_BEDROCK") != "" {
return true
@@ -162,7 +162,7 @@ func Providers(cfg *Config) ([]catwalk.Provider, error) {
items, err := catwalkSyncer.Get(ctx)
if err != nil {
catwalkURL := fmt.Sprintf("%s/v2/providers", cmp.Or(os.Getenv("CATWALK_URL"), defaultCatwalkURL))
- errs = append(errs, fmt.Errorf("Crush was unable to fetch an updated list of providers from %s. Consider setting CRUSH_DISABLE_PROVIDER_AUTO_UPDATE=1 to use the embedded providers bundled at the time of this Crush release. You can also update providers manually. For more info see crush update-providers --help.\n\nCause: %w", catwalkURL, providerErr)) //nolint:staticcheck
+ errs = append(errs, fmt.Errorf("Crush was unable to fetch an updated list of providers from %s. Consider setting CRUSH_DISABLE_PROVIDER_AUTO_UPDATE=1 to use the embedded providers bundled at the time of this Crush release. You can also update providers manually. For more info see crush update-providers --help.\n\nCause: %w", catwalkURL, err)) //nolint:staticcheck
return
}
providers.Append(items...)
@@ -0,0 +1,16 @@
+//go:build !windows
+
+package fsext
+
+// WindowsWorkingDirDrive returns the drive letter of the current working
+// directory, e.g. "C:".
+// Falls back to the system drive if the current working directory cannot be
+// determined.
+func WindowsWorkingDirDrive() string {
+ panic("cannot call fsext.WindowsWorkingDirDrive() on non-Windows OS")
+}
+
+// WindowsSystemDrive returns the drive letter of the system drive, e.g. "C:".
+func WindowsSystemDrive() string {
+ panic("cannot call fsext.WindowsSystemDrive() on non-Windows OS")
+}
@@ -0,0 +1,26 @@
+//go:build windows
+
+package fsext
+
+import (
+ "cmp"
+ "os"
+ "path/filepath"
+)
+
+// WindowsWorkingDirDrive returns the drive letter of the current working
+// directory, e.g. "C:".
+// Falls back to the system drive if the current working directory cannot be
+// determined.
+func WindowsWorkingDirDrive() string {
+ if cwd, err := os.Getwd(); err == nil {
+ return filepath.VolumeName(cwd)
+ }
+ return WindowsSystemDrive()
+}
+
+// WindowsSystemDrive returns the drive letter of the system drive, e.g. "C:".
+func WindowsSystemDrive() string {
+ systemRoot := cmp.Or(os.Getenv("SYSTEMROOT"), os.Getenv("WINDIR"))
+ return filepath.VolumeName(systemRoot)
+}
@@ -82,7 +82,19 @@ func (w *FastGlobWalker) ShouldSkipDir(path string) bool {
return w.directoryLister.shouldIgnore(path, nil, true)
}
-func GlobWithDoubleStar(pattern, searchPath string, limit int) ([]string, bool, error) {
+// Glob globs files.
+//
+// Does not respect gitignore.
+func Glob(pattern string, cwd string, limit int) ([]string, bool, error) {
+ return globWithDoubleStar(pattern, cwd, limit, false)
+}
+
+// GlobGitignoreAware globs files respecting gitignore.
+func GlobGitignoreAware(pattern string, cwd string, limit int) ([]string, bool, error) {
+ return globWithDoubleStar(pattern, cwd, limit, true)
+}
+
+func globWithDoubleStar(pattern, searchPath string, limit int, gitignore bool) ([]string, bool, error) {
// Normalize pattern to forward slashes on Windows so their config can use
// backslashes
pattern = filepath.ToSlash(pattern)
@@ -101,11 +113,11 @@ func GlobWithDoubleStar(pattern, searchPath string, limit int) ([]string, bool,
isDir := d.IsDir()
if isDir {
- if walker.ShouldSkipDir(path) {
+ if gitignore && walker.ShouldSkipDir(path) {
return filepath.SkipDir
}
} else {
- if walker.ShouldSkip(path) {
+ if gitignore && walker.ShouldSkip(path) {
return nil
}
}
@@ -24,7 +24,7 @@ func TestGlobWithDoubleStar(t *testing.T) {
require.NoError(t, os.WriteFile(file, []byte("test content"), 0o644))
}
- matches, truncated, err := GlobWithDoubleStar("**/main.go", testDir, 0)
+ matches, truncated, err := GlobGitignoreAware("**/main.go", testDir, 0)
require.NoError(t, err)
require.False(t, truncated)
@@ -47,7 +47,7 @@ func TestGlobWithDoubleStar(t *testing.T) {
require.NoError(t, os.WriteFile(filepath.Join(srcDir, "main.go"), []byte("package main"), 0o644))
require.NoError(t, os.WriteFile(pkgFile, []byte("test"), 0o644))
- matches, truncated, err := GlobWithDoubleStar("pkg", testDir, 0)
+ matches, truncated, err := GlobGitignoreAware("pkg", testDir, 0)
require.NoError(t, err)
require.False(t, truncated)
@@ -66,7 +66,7 @@ func TestGlobWithDoubleStar(t *testing.T) {
require.NoError(t, os.MkdirAll(dir, 0o755))
}
- matches, truncated, err := GlobWithDoubleStar("**/pkg", testDir, 0)
+ matches, truncated, err := GlobGitignoreAware("**/pkg", testDir, 0)
require.NoError(t, err)
require.False(t, truncated)
@@ -95,7 +95,7 @@ func TestGlobWithDoubleStar(t *testing.T) {
require.NoError(t, os.WriteFile(file, []byte("package main"), 0o644))
}
- matches, truncated, err := GlobWithDoubleStar("pkg/**", testDir, 0)
+ matches, truncated, err := GlobGitignoreAware("pkg/**", testDir, 0)
require.NoError(t, err)
require.False(t, truncated)
@@ -124,7 +124,7 @@ func TestGlobWithDoubleStar(t *testing.T) {
require.NoError(t, os.WriteFile(file, []byte("test"), 0o644))
}
- matches, truncated, err := GlobWithDoubleStar("**/*.txt", testDir, 5)
+ matches, truncated, err := GlobGitignoreAware("**/*.txt", testDir, 5)
require.NoError(t, err)
require.True(t, truncated, "Expected truncation with limit")
require.Len(t, matches, 5, "Expected exactly 5 matches with limit")
@@ -143,7 +143,7 @@ func TestGlobWithDoubleStar(t *testing.T) {
require.NoError(t, os.WriteFile(file, []byte("test"), 0o644))
}
- matches, truncated, err := GlobWithDoubleStar("a/b/c/file1.txt", testDir, 0)
+ matches, truncated, err := GlobGitignoreAware("a/b/c/file1.txt", testDir, 0)
require.NoError(t, err)
require.False(t, truncated)
@@ -171,7 +171,7 @@ func TestGlobWithDoubleStar(t *testing.T) {
require.NoError(t, os.Chtimes(file2, m2, m2))
require.NoError(t, os.Chtimes(file3, m3, m3))
- matches, truncated, err := GlobWithDoubleStar("*.txt", testDir, 0)
+ matches, truncated, err := GlobGitignoreAware("*.txt", testDir, 0)
require.NoError(t, err)
require.False(t, truncated)
@@ -181,7 +181,7 @@ func TestGlobWithDoubleStar(t *testing.T) {
t.Run("handles empty directory", func(t *testing.T) {
testDir := t.TempDir()
- matches, truncated, err := GlobWithDoubleStar("**", testDir, 0)
+ matches, truncated, err := GlobGitignoreAware("**", testDir, 0)
require.NoError(t, err)
require.False(t, truncated)
// Even empty directories should return the directory itself
@@ -191,7 +191,7 @@ func TestGlobWithDoubleStar(t *testing.T) {
t.Run("handles non-existent search path", func(t *testing.T) {
nonExistentDir := filepath.Join(t.TempDir(), "does", "not", "exist")
- matches, truncated, err := GlobWithDoubleStar("**", nonExistentDir, 0)
+ matches, truncated, err := GlobGitignoreAware("**", nonExistentDir, 0)
require.Error(t, err, "Should return error for non-existent search path")
require.False(t, truncated)
require.Empty(t, matches)
@@ -219,17 +219,17 @@ func TestGlobWithDoubleStar(t *testing.T) {
ignoredFileInDir := filepath.Join(testDir, "backup", "old.txt")
require.NoError(t, os.WriteFile(ignoredFileInDir, []byte("old content"), 0o644))
- matches, truncated, err := GlobWithDoubleStar("*.tmp", testDir, 0)
+ matches, truncated, err := GlobGitignoreAware("*.tmp", testDir, 0)
require.NoError(t, err)
require.False(t, truncated)
require.Empty(t, matches, "Expected no matches for '*.tmp' pattern (should be ignored)")
- matches, truncated, err = GlobWithDoubleStar("backup", testDir, 0)
+ matches, truncated, err = GlobGitignoreAware("backup", testDir, 0)
require.NoError(t, err)
require.False(t, truncated)
require.Empty(t, matches, "Expected no matches for 'backup' pattern (should be ignored)")
- matches, truncated, err = GlobWithDoubleStar("*.txt", testDir, 0)
+ matches, truncated, err = GlobGitignoreAware("*.txt", testDir, 0)
require.NoError(t, err)
require.False(t, truncated)
require.Equal(t, []string{goodFile}, matches)
@@ -257,7 +257,7 @@ func TestGlobWithDoubleStar(t *testing.T) {
require.NoError(t, os.Chtimes(middleDir, tMiddle, tMiddle))
require.NoError(t, os.Chtimes(oldestFile, tNewest, tNewest))
- matches, truncated, err := GlobWithDoubleStar("*.rs", testDir, 0)
+ matches, truncated, err := GlobGitignoreAware("*.rs", testDir, 0)
require.NoError(t, err)
require.False(t, truncated)
require.Len(t, matches, 3)
@@ -85,7 +85,7 @@ func New(
resolver: resolver,
cwd: cwd,
}
- client.serverState.Store(StateStarting)
+ client.serverState.Store(StateStopped)
if err := client.createPowernapClient(); err != nil {
return nil, err
@@ -324,6 +324,9 @@ type OpenFileInfo struct {
// HandlesFile checks if this LSP client handles the given file based on its
// extension and whether it's within the working directory.
func (c *Client) HandlesFile(path string) bool {
+ if c == nil {
+ return false
+ }
if !fsext.HasPrefix(path, c.cwd) {
slog.Debug("File outside workspace", "name", c.name, "file", path, "workDir", c.cwd)
return false
@@ -364,6 +367,9 @@ func (c *Client) OpenFile(ctx context.Context, filepath string) error {
// NotifyChange notifies the server about a file change.
func (c *Client) NotifyChange(ctx context.Context, filepath string) error {
+ if c == nil {
+ return nil
+ }
uri := string(protocol.URIFromPath(filepath))
content, err := os.ReadFile(filepath)
@@ -420,12 +426,18 @@ func (c *Client) GetFileDiagnostics(uri protocol.DocumentURI) []protocol.Diagnos
// GetDiagnostics returns all diagnostics for all files.
func (c *Client) GetDiagnostics() map[protocol.DocumentURI][]protocol.Diagnostic {
+ if c == nil {
+ return nil
+ }
return c.diagnostics.Copy()
}
// GetDiagnosticCounts returns cached diagnostic counts by severity.
// Uses the VersionedMap version to avoid recomputing on every call.
func (c *Client) GetDiagnosticCounts() DiagnosticCounts {
+ if c == nil {
+ return DiagnosticCounts{}
+ }
currentVersion := c.diagnostics.Version()
c.diagCountsMu.Lock()
@@ -459,6 +471,9 @@ func (c *Client) GetDiagnosticCounts() DiagnosticCounts {
// OpenFileOnDemand opens a file only if it's not already open.
func (c *Client) OpenFileOnDemand(ctx context.Context, filepath string) error {
+ if c == nil {
+ return nil
+ }
// Check if the file is already open
if c.IsFileOpen(filepath) {
return nil
@@ -501,6 +516,9 @@ func (c *Client) openKeyConfigFiles(ctx context.Context) {
// WaitForDiagnostics waits until diagnostics change or the timeout is reached.
func (c *Client) WaitForDiagnostics(ctx context.Context, d time.Duration) {
+ if c == nil {
+ return
+ }
ticker := time.NewTicker(200 * time.Millisecond)
defer ticker.Stop()
timeout := time.After(d)
@@ -3,9 +3,11 @@ package lsp
import (
"context"
"testing"
+ "time"
"github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/env"
+ "github.com/stretchr/testify/require"
)
func TestClient(t *testing.T) {
@@ -55,3 +57,16 @@ func TestClient(t *testing.T) {
t.Logf("Close failed as expected with dummy command: %v", err)
}
}
+
+func TestNilClient(t *testing.T) {
+ t.Parallel()
+
+ var c *Client
+
+ require.False(t, c.HandlesFile("/some/file.go"))
+ require.Equal(t, DiagnosticCounts{}, c.GetDiagnosticCounts())
+ require.Nil(t, c.GetDiagnostics())
+ require.Nil(t, c.OpenFileOnDemand(context.Background(), "/some/file.go"))
+ require.Nil(t, c.NotifyChange(context.Background(), "/some/file.go"))
+ c.WaitForDiagnostics(context.Background(), time.Second)
+}
@@ -21,13 +21,14 @@ import (
"github.com/sourcegraph/jsonrpc2"
)
+var unavailable = csync.NewMap[string, struct{}]()
+
// Manager handles lazy initialization of LSP clients based on file types.
type Manager struct {
clients *csync.Map[string, *Client]
cfg *config.Config
manager *powernapconfig.Manager
callback func(name string, client *Client)
- mu sync.Mutex
}
// NewManager creates a new LSP manager service.
@@ -72,17 +73,12 @@ func (s *Manager) Clients() *csync.Map[string, *Client] {
// SetCallback sets a callback that is invoked when a new LSP
// client is successfully started. This allows the coordinator to add LSP tools.
func (s *Manager) SetCallback(cb func(name string, client *Client)) {
- s.mu.Lock()
- defer s.mu.Unlock()
s.callback = cb
}
// TrackConfigured will callback the user-configured LSPs, but will not create
// any clients.
func (s *Manager) TrackConfigured() {
- s.mu.Lock()
- defer s.mu.Unlock()
-
var wg sync.WaitGroup
for name := range s.manager.GetServers() {
if !s.isUserConfigured(name) {
@@ -102,16 +98,10 @@ func (s *Manager) Start(ctx context.Context, path string) {
return
}
- s.mu.Lock()
- defer s.mu.Unlock()
-
var wg sync.WaitGroup
for name, server := range s.manager.GetServers() {
- if !handles(server, path, s.cfg.WorkingDir()) {
- continue
- }
wg.Go(func() {
- s.startServer(ctx, name, server)
+ s.startServer(ctx, name, path, server)
})
}
wg.Wait()
@@ -149,12 +139,31 @@ var skipAutoStartCommands = map[string]bool{
"tflint": true,
}
-func (s *Manager) startServer(ctx context.Context, name string, server *powernapconfig.ServerConfig) {
+func (s *Manager) startServer(ctx context.Context, name, filepath string, server *powernapconfig.ServerConfig) {
+ cfg := s.buildConfig(name, server)
+ if cfg.Disabled {
+ return
+ }
+
+ if _, exists := unavailable.Get(name); exists {
+ return
+ }
+
+ if client, ok := s.clients.Get(name); ok {
+ switch client.GetServerState() {
+ case StateReady, StateStarting, StateDisabled:
+ s.callback(name, client)
+ // already done, return
+ return
+ }
+ }
+
userConfigured := s.isUserConfigured(name)
if !userConfigured {
if _, err := exec.LookPath(server.Command); err != nil {
slog.Debug("LSP server not installed, skipping", "name", name, "command", server.Command)
+ unavailable.Set(name, struct{}{})
return
}
if skipAutoStartCommands[server.Command] {
@@ -163,15 +172,21 @@ func (s *Manager) startServer(ctx context.Context, name string, server *powernap
}
}
- cfg := s.buildConfig(name, server)
+ // this is the slowest bit, so we do it last.
+ if !handles(server, filepath, s.cfg.WorkingDir()) {
+ // nothing to do
+ return
+ }
+
+ // Check again in case another goroutine started it in the meantime.
if client, ok := s.clients.Get(name); ok {
switch client.GetServerState() {
- case StateReady, StateStarting:
+ case StateReady, StateStarting, StateDisabled:
s.callback(name, client)
- // already done, return
return
}
}
+
client, err := New(
ctx,
name,
@@ -184,13 +199,29 @@ func (s *Manager) startServer(ctx context.Context, name string, server *powernap
slog.Error("Failed to create LSP client", "name", name, "error", err)
return
}
- s.callback(name, client)
-
+ // Only store non-nil clients. If another goroutine raced us,
+ // prefer the already-stored client.
+ if existing, ok := s.clients.Get(name); ok {
+ switch existing.GetServerState() {
+ case StateReady, StateStarting, StateDisabled:
+ client.Close(ctx)
+ s.callback(name, existing)
+ return
+ }
+ }
+ s.clients.Set(name, client)
defer func() {
- s.clients.Set(name, client)
s.callback(name, client)
}()
+ switch client.GetServerState() {
+ case StateReady, StateStarting, StateDisabled:
+ // already done, return
+ return
+ }
+
+ client.serverState.Store(StateStarting)
+
initCtx, cancel := context.WithTimeout(ctx, time.Duration(cmp.Or(cfg.Timeout, 30))*time.Second)
defer cancel()
@@ -271,7 +302,7 @@ func hasRootMarkers(dir string, markers []string) bool {
}
for _, pattern := range markers {
// Use fsext.GlobWithDoubleStar to find matches
- matches, _, err := fsext.GlobWithDoubleStar(pattern, dir, 1)
+ matches, _, err := fsext.Glob(pattern, dir, 1)
if err == nil && len(matches) > 0 {
return true
}
@@ -291,15 +322,13 @@ func handles(server *powernapconfig.ServerConfig, filePath, workDir string) bool
// in the middle of writing something.
// Generally it doesn't matter when shutting down Crush, though.
func (s *Manager) KillAll(context.Context) {
- s.mu.Lock()
- defer s.mu.Unlock()
-
var wg sync.WaitGroup
for name, client := range s.clients.Seq2() {
wg.Go(func() {
defer func() { s.callback(name, client) }()
client.client.Kill()
client.SetServerState(StateStopped)
+ s.clients.Del(name)
slog.Debug("Killed LSP client", "name", name)
})
}
@@ -308,9 +337,6 @@ func (s *Manager) KillAll(context.Context) {
// StopAll stops all running LSP clients and clears the client map.
func (s *Manager) StopAll(ctx context.Context) {
- s.mu.Lock()
- defer s.mu.Unlock()
-
var wg sync.WaitGroup
for name, client := range s.clients.Seq2() {
wg.Go(func() {
@@ -323,6 +349,7 @@ func (s *Manager) StopAll(ctx context.Context) {
slog.Warn("Failed to stop LSP client", "name", name, "error", err)
}
client.SetServerState(StateStopped)
+ s.clients.Del(name)
slog.Debug("Stopped LSP client", "name", name)
})
}
@@ -107,11 +107,23 @@ func (a *AssistantMessageItem) RawRender(width int) string {
// Render implements MessageItem.
func (a *AssistantMessageItem) Render(width int) string {
- style := a.sty.Chat.Message.AssistantBlurred
- if a.focused {
- style = a.sty.Chat.Message.AssistantFocused
+ // XXX: Here, we're manually applying the focused/blurred styles because
+ // using lipgloss.Render can degrade performance for long messages due to
+ // it's wrapping logic.
+ // We already know that the content is wrapped to the correct width in
+ // RawRender, so we can just apply the styles directly to each line.
+ focused := a.sty.Chat.Message.AssistantFocused.Render()
+ blurred := a.sty.Chat.Message.AssistantBlurred.Render()
+ rendered := a.RawRender(width)
+ lines := strings.Split(rendered, "\n")
+ for i, line := range lines {
+ if a.focused {
+ lines[i] = focused + line
+ } else {
+ lines[i] = blurred + line
+ }
}
- return style.Render(a.RawRender(width))
+ return strings.Join(lines, "\n")
}
// renderMessageContent renders the message content including thinking, main content, and finish reason.
@@ -82,6 +82,12 @@ func (v *ViewToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
content = meta.Content
}
+ // Handle skill content.
+ if meta.ResourceType == tools.ViewResourceSkill {
+ body := toolOutputSkillContent(sty, meta.ResourceName, meta.ResourceDescription)
+ return joinToolParts(header, body)
+ }
+
if content == "" {
return header
}
@@ -221,7 +221,12 @@ func (a *AssistantInfoItem) RawRender(width int) string {
// Render implements MessageItem.
func (a *AssistantInfoItem) Render(width int) string {
- return a.sty.Chat.Message.SectionHeader.Render(a.RawRender(width))
+ prefix := a.sty.Chat.Message.SectionHeader.Render()
+ lines := strings.Split(a.RawRender(width), "\n")
+ for i, line := range lines {
+ lines[i] = prefix + line
+ }
+ return strings.Join(lines, "\n")
}
func (a *AssistantInfoItem) renderContent(width int) string {
@@ -323,16 +323,19 @@ func (t *baseToolMessageItem) RawRender(width int) string {
// Render renders the tool message item at the given width.
func (t *baseToolMessageItem) Render(width int) string {
- style := t.sty.Chat.Message.ToolCallBlurred
- if t.focused {
- style = t.sty.Chat.Message.ToolCallFocused
- }
-
+ var prefix string
if t.isCompact {
- style = t.sty.Chat.Message.ToolCallCompact
+ prefix = t.sty.Chat.Message.ToolCallCompact.Render()
+ } else if t.focused {
+ prefix = t.sty.Chat.Message.ToolCallFocused.Render()
+ } else {
+ prefix = t.sty.Chat.Message.ToolCallBlurred.Render()
}
-
- return style.Render(t.RawRender(width))
+ lines := strings.Split(t.RawRender(width), "\n")
+ for i, ln := range lines {
+ lines[i] = prefix + ln
+ }
+ return strings.Join(lines, "\n")
}
// ToolCall returns the tool call associated with this message item.
@@ -622,12 +625,24 @@ func toolOutputImageContent(sty *styles.Styles, data, mediaType string) string {
dataSize := len(data) * 3 / 4
sizeStr := formatSize(dataSize)
- loaded := sty.Base.Foreground(sty.Green).Render("Loaded")
- arrow := sty.Base.Foreground(sty.GreenDark).Render("→")
- typeStyled := sty.Base.Render(mediaType)
- sizeStyled := sty.Subtle.Render(sizeStr)
-
- return sty.Tool.Body.Render(fmt.Sprintf("%s %s %s %s", loaded, arrow, typeStyled, sizeStyled))
+ return sty.Tool.Body.Render(fmt.Sprintf(
+ "%s %s %s %s",
+ sty.Tool.ResourceLoadedText.Render("Loaded Image"),
+ sty.Tool.ResourceLoadedIndicator.Render(styles.ArrowRightIcon),
+ sty.Tool.MediaType.Render(mediaType),
+ sty.Tool.ResourceSize.Render(sizeStr),
+ ))
+}
+
+// toolOutputSkillContent renders a skill loaded indicator.
+func toolOutputSkillContent(sty *styles.Styles, name, description string) string {
+ return sty.Tool.Body.Render(fmt.Sprintf(
+ "%s %s %s %s",
+ sty.Tool.ResourceLoadedText.Render("Loaded Skill"),
+ sty.Tool.ResourceLoadedIndicator.Render(styles.ArrowRightIcon),
+ sty.Tool.ResourceName.Render(name),
+ sty.Tool.ResourceSize.Render(description),
+ ))
}
// getDigits returns the number of digits in a number.
@@ -70,11 +70,17 @@ func (m *UserMessageItem) RawRender(width int) string {
// Render implements MessageItem.
func (m *UserMessageItem) Render(width int) string {
- style := m.sty.Chat.Message.UserBlurred
+ var prefix string
if m.focused {
- style = m.sty.Chat.Message.UserFocused
+ prefix = m.sty.Chat.Message.UserFocused.Render()
+ } else {
+ prefix = m.sty.Chat.Message.UserBlurred.Render()
+ }
+ lines := strings.Split(m.RawRender(width), "\n")
+ for i, line := range lines {
+ lines[i] = prefix + line
}
- return style.Render(m.RawRender(width))
+ return strings.Join(lines, "\n")
}
// ID implements MessageItem.
@@ -1,6 +1,7 @@
package dialog
import (
+ "cmp"
"strings"
"charm.land/bubbles/v2/help"
@@ -311,10 +312,7 @@ func (a *Arguments) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor {
// Use standard header
titleStyle := s.Dialog.Title
- titleText := a.title
- if titleText == "" {
- titleText = "Arguments"
- }
+ titleText := cmp.Or(a.title, "Arguments")
header := common.DialogTitle(s, titleText, width, s.Primary, s.Secondary)
@@ -445,18 +445,13 @@ func (m *Models) setProviderItems() error {
}
continue
}
- if model.Name == "" {
- model.Name = model.ID
- }
+ model.Name = cmp.Or(model.Name, model.ID)
displayProvider.Models = append(displayProvider.Models, model)
modelIndex[model.ID] = len(displayProvider.Models) - 1
}
}
- name := displayProvider.Name
- if name == "" {
- name = providerID
- }
+ name := cmp.Or(displayProvider.Name, providerID)
group := NewModelGroup(t, name, providerConfigured)
for _, model := range displayProvider.Models {
@@ -20,8 +20,8 @@ import (
const (
// ReasoningID is the identifier for the reasoning effort dialog.
ReasoningID = "reasoning"
- reasoningDialogMaxWidth = 80
- reasoningDialogMaxHeight = 12
+ reasoningDialogMaxWidth = 50
+ reasoningDialogMaxHeight = 10
)
// Reasoning represents a dialog for selecting reasoning effort.
@@ -17,10 +17,11 @@ import (
)
const (
- headerDiag = "╱"
- minHeaderDiags = 3
- leftPadding = 1
- rightPadding = 1
+ headerDiag = "╱"
+ minHeaderDiags = 3
+ leftPadding = 1
+ rightPadding = 1
+ diagToDetailsSpacing = 1 // space between diagonal pattern and details section
)
type header struct {
@@ -73,7 +74,7 @@ func (h *header) drawHeader(
var b strings.Builder
b.WriteString(h.compactLogo)
- availDetailWidth := width - leftPadding - rightPadding - lipgloss.Width(b.String()) - minHeaderDiags
+ availDetailWidth := width - leftPadding - rightPadding - lipgloss.Width(b.String()) - minHeaderDiags - diagToDetailsSpacing
details := renderHeaderDetails(
h.com,
session,
@@ -86,7 +87,8 @@ func (h *header) drawHeader(
lipgloss.Width(b.String()) -
lipgloss.Width(details) -
leftPadding -
- rightPadding
+ rightPadding -
+ diagToDetailsSpacing
if remainingWidth > 0 {
b.WriteString(t.Header.Diagonals.Render(
@@ -143,8 +145,8 @@ func renderHeaderDetails(
const dirTrimLimit = 4
cfg := com.Config()
cwd := fsext.DirTrim(fsext.PrettyPath(cfg.WorkingDir()), dirTrimLimit)
- cwd = ansi.Truncate(cwd, max(0, availWidth-lipgloss.Width(metadata)), "…")
cwd = t.Header.WorkingDir.Render(cwd)
- return cwd + metadata
+ result := cwd + metadata
+ return ansi.Truncate(result, max(0, availWidth), "…")
}
@@ -108,7 +108,7 @@ func lspList(t *styles.Styles, lsps []LSPInfo, width, maxItems int) string {
icon = t.ResourceOfflineIcon.Foreground(t.Muted.GetBackground()).String()
description = t.ResourceStatus.Render("disabled")
default:
- icon = t.ResourceOfflineIcon.String()
+ continue
}
renderedLsps = append(renderedLsps, common.Status(t, common.StatusOpts{
Icon: icon,
@@ -139,11 +139,6 @@ func (m *UI) togglePillsExpanded() tea.Cmd {
if !m.hasSession() {
return nil
}
- if m.layout.pills.Dy() > 0 {
- if cmd := m.chat.ScrollByAndAnimate(0); cmd != nil {
- return cmd
- }
- }
hasPills := hasIncompleteTodos(m.session.Todos) || m.promptQueue > 0
if !hasPills {
return nil
@@ -157,6 +152,12 @@ func (m *UI) togglePillsExpanded() tea.Cmd {
}
}
m.updateLayoutAndSize()
+
+ // Make sure to follow scroll if follow is enabled when toggling pills.
+ if m.chat.Follow() {
+ m.chat.ScrollToBottom()
+ }
+
return nil
}
@@ -46,7 +46,9 @@ func (s *Status) ClearInfoMsg() {
// SetWidth sets the width of the status bar and help view.
func (s *Status) SetWidth(width int) {
- s.help.SetWidth(width)
+ helpStyle := s.com.Styles.Status.Help
+ horizontalPadding := helpStyle.GetPaddingLeft() + helpStyle.GetPaddingRight()
+ s.help.SetWidth(width - horizontalPadding)
}
// ShowingAll returns whether the full help view is shown.
@@ -2,6 +2,7 @@ package model
import (
"bytes"
+ "cmp"
"context"
"errors"
"fmt"
@@ -1397,10 +1398,7 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd {
case dialog.ActionRunMCPPrompt:
if len(msg.Arguments) > 0 && msg.Args == nil {
m.dialog.CloseFrontDialog()
- title := msg.Title
- if title == "" {
- title = "MCP Prompt Arguments"
- }
+ title := cmp.Or(msg.Title, "MCP Prompt Arguments")
argsDialog := dialog.NewArguments(
m.com,
title,
@@ -1821,7 +1819,7 @@ func (m *UI) drawHeader(scr uv.Screen, area uv.Rectangle) {
m.session,
m.isCompact,
m.detailsOpen,
- m.width,
+ area.Dx(),
)
}
@@ -2567,10 +2565,7 @@ func (m *UI) insertFileCompletion(path string) tea.Cmd {
// insertMCPResourceCompletion inserts the selected resource into the textarea,
// replacing the @query, and adds the resource as an attachment.
func (m *UI) insertMCPResourceCompletion(item completions.ResourceCompletionValue) tea.Cmd {
- displayText := item.Title
- if displayText == "" {
- displayText = item.URI
- }
+ displayText := cmp.Or(item.Title, item.URI)
if !m.insertCompletionText(displayText) {
return nil
@@ -322,6 +322,13 @@ type Styles struct {
MCPToolName lipgloss.Style // The mcp tool name
MCPArrow lipgloss.Style // The mcp arrow icon
+ // Images and external resources
+ ResourceLoadedText lipgloss.Style
+ ResourceLoadedIndicator lipgloss.Style
+ ResourceName lipgloss.Style
+ ResourceSize lipgloss.Style
+ MediaType lipgloss.Style
+
// Docker MCP tools
DockerMCPActionAdd lipgloss.Style // Docker MCP add action (green)
DockerMCPActionDel lipgloss.Style // Docker MCP remove action (red)
@@ -1172,6 +1179,13 @@ func DefaultStyles() Styles {
s.Tool.MCPToolName = base.Foreground(blueDark)
s.Tool.MCPArrow = base.Foreground(blue).SetString(ArrowRightIcon)
+ // Loading indicators for images, skills
+ s.Tool.ResourceLoadedText = base.Foreground(green)
+ s.Tool.ResourceLoadedIndicator = base.Foreground(greenDark)
+ s.Tool.ResourceName = base
+ s.Tool.MediaType = base
+ s.Tool.ResourceSize = base.Foreground(fgMuted)
+
// Docker MCP styles
s.Tool.DockerMCPActionAdd = base.Foreground(greenLight)
s.Tool.DockerMCPActionDel = base.Foreground(red)