diff --git a/.github/cla-signatures.json b/.github/cla-signatures.json index cffebabffd242a4897a07d79e1e0d051c7662cc2..4026b8c990a227a4a99f6193ff798f4f383e5f09 100644 --- a/.github/cla-signatures.json +++ b/.github/cla-signatures.json @@ -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 } ] } \ No newline at end of file diff --git a/.github/labeler.yml b/.github/labeler.yml index e0e998246f6533415b9c356ccae8d905e0afe1df..a0270f4f4ad0873aa7eb04b5b34bcb703ef859b9 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -57,6 +57,8 @@ - "/vertex/i" "provider: kimi": - "/kimi/i" +"provider: minimax": + - "/minimax/i" "provider: ollama": - "/llama/i" "provider: openai chatgpt": diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index a7b6485e6b5f89cf5a566cf9f5058dd8e72b0d23..89c009fb1ce246b0ca04990d9b2281964c8be0fd 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8c58e6bdf7bd1492665daf7b9ac966edec0da0d5..0cc6e465c675c03cb6d005fd2df5e4fca6bdeadd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 9f1bed1c8ccead5603f6b868e018b4872dbfc1bb..3d488dfaf901404d00a29a6aa52a32ba01f360e3 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -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 diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml index a5a45d8fdeeaf8f0c1374366e7c1d34839c1acc5..3c139d5b7664315e7b66271df5ec0fa2fb52ca4f 100644 --- a/.github/workflows/snapshot.yml +++ b/.github/workflows/snapshot.yml @@ -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 diff --git a/go.mod b/go.mod index 7672d9c5ab326a39ce1374622c1746f80099672f..c47e9b613eac3b7e1fdce1063b8ce623d2fb2c35 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index 81b47ce5913a63c5926298fa5ca94bbc0572a729..685c4df3f6ed6efcab0002e61088fa7ecb37c49b 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/agent/agent.go b/internal/agent/agent.go index 7911b43766c1156a9c33fe4955b45e0d6f88a641..46be4504923b42d753ac1f058648875622605d63 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -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 diff --git a/internal/agent/coordinator.go b/internal/agent/coordinator.go index 058d49777b92ecc788c5ea1ad5f24b857a785221..b64a90bcc58d3dae71c59175e25eec601c7be083 100644 --- a/internal/agent/coordinator.go +++ b/internal/agent/coordinator.go @@ -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: diff --git a/internal/agent/tools/bash.go b/internal/agent/tools/bash.go index ca3612e091f23235688a2a40006469e39093d6a5..8552880b99b7735172ed89c9b6be147c104dcb5d 100644 --- a/internal/agent/tools/bash.go +++ b/internal/agent/tools/bash.go @@ -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) } diff --git a/internal/agent/tools/glob.go b/internal/agent/tools/glob.go index 3f56d9dcb6437d5ad6c9ca3b8416ae45c6eb7969..641a84dd29ff6993b1fb54d0e5675dc3f5d56ae6 100644 --- a/internal/agent/tools/glob.go +++ b/internal/agent/tools/glob.go @@ -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) { diff --git a/internal/agent/tools/grep.go b/internal/agent/tools/grep.go index 8c7ec51e4e3768e4814b2d6baa3c7085357f5b45..8a894a4984ac7f90f89b8a2c7f1d1c637e79aecb 100644 --- a/internal/agent/tools/grep.go +++ b/internal/agent/tools/grep.go @@ -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() diff --git a/internal/agent/tools/job_test.go b/internal/agent/tools/job_test.go index f3958bef19aa6b29240eb8b265e36b56398d364e..7c8cb8f32dcdce7fe4227f05b14cc9b04e879bee 100644 --- a/internal/agent/tools/job_test.go +++ b/internal/agent/tools/job_test.go @@ -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() diff --git a/internal/agent/tools/list_mcp_resources.go b/internal/agent/tools/list_mcp_resources.go index 25671ffe481a21a82c40167c40614603e907052c..032d1eb1888a65e9a14daecc3b503698a6fa60d4 100644 --- a/internal/agent/tools/list_mcp_resources.go +++ b/internal/agent/tools/list_mcp_resources.go @@ -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) diff --git a/internal/agent/tools/mcp/resources.go b/internal/agent/tools/mcp/resources.go index 912651f0eb4d5c8cf3999cc1fb7f6027cd9bcd52..da661817c24f8fc1324f509d1834e9d03d5fd2c9 100644 --- a/internal/agent/tools/mcp/resources.go +++ b/internal/agent/tools/mcp/resources.go @@ -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) diff --git a/internal/agent/tools/view.go b/internal/agent/tools/view.go index 0a754dcb4fd05cc975f84e85532eeab1525c7002..0e56a4f6866d018efabfa952b7a10dc97507656f 100644 --- a/internal/agent/tools/view.go +++ b/internal/agent/tools/view.go @@ -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 := "\n" @@ -196,12 +207,22 @@ func NewViewTool( output += "\n\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 }) } diff --git a/internal/app/app.go b/internal/app/app.go index e923a0337f125cab17192e94ef61e17d23ae6582..3755db0d5321029b88cc1c15d8fb99911f635bf3 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -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 diff --git a/internal/app/lsp_events.go b/internal/app/lsp_events.go index 5292983d46cf867b9380ad45f7831007da54f0d7..babb9afe374108e1b80c4fb8910032f0c631f302 100644 --- a/internal/app/lsp_events.go +++ b/internal/app/lsp_events.go @@ -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) diff --git a/internal/app/provider.go b/internal/app/provider.go index 570edadf9e1647eeeeab32107d3da3a1d3494935..ea7c6ee110a74c17f80a667e7b891317437f6963 100644 --- a/internal/app/provider.go +++ b/internal/app/provider.go @@ -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. diff --git a/internal/config/config.go b/internal/config/config.go index 626fbe327491eb28d41e2972c3cb221b1deeb0c6..753151509315545dfbed9bd74c1455785313c8aa 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -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 { diff --git a/internal/config/load.go b/internal/config/load.go index 7753a50e25a4f6ed419feb1355a99c040a43d9e0..3fba44aa9142c52b8966b1dbe994cef0ae654c48 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -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 diff --git a/internal/config/provider.go b/internal/config/provider.go index bd960e0c27ca11d5ef376c221717f061ad3deb47..645f863628edb8335782fa6c4424ee36f1b10f0e 100644 --- a/internal/config/provider.go +++ b/internal/config/provider.go @@ -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...) diff --git a/internal/fsext/drive_other.go b/internal/fsext/drive_other.go new file mode 100644 index 0000000000000000000000000000000000000000..0141c8baf0a640a6c3fc45500c9acb798664b16a --- /dev/null +++ b/internal/fsext/drive_other.go @@ -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") +} diff --git a/internal/fsext/drive_windows.go b/internal/fsext/drive_windows.go new file mode 100644 index 0000000000000000000000000000000000000000..7d0a798da27fb59d38bce5e5bec52560ff18b54b --- /dev/null +++ b/internal/fsext/drive_windows.go @@ -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) +} diff --git a/internal/fsext/fileutil.go b/internal/fsext/fileutil.go index c091820935d9c13142b12d3bd79d8c023a42a2fd..72410a10721ae6af1965942d455a8310096399e6 100644 --- a/internal/fsext/fileutil.go +++ b/internal/fsext/fileutil.go @@ -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 } } diff --git a/internal/fsext/fileutil_test.go b/internal/fsext/fileutil_test.go index 3788fe5477b082dec496275a8ac028788d55fc64..e80b902c08301be3a6b02e9a8d0e9c82895410b3 100644 --- a/internal/fsext/fileutil_test.go +++ b/internal/fsext/fileutil_test.go @@ -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) diff --git a/internal/lsp/client.go b/internal/lsp/client.go index c82dffabf40e99dc932e2fd326b24031d3e04ebb..18bc1ed954acbf4a0397dfa497bd2133513bb090 100644 --- a/internal/lsp/client.go +++ b/internal/lsp/client.go @@ -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) diff --git a/internal/lsp/client_test.go b/internal/lsp/client_test.go index e444354800b6e41ad26a1d8f0d8ffb097a1f060a..62979144babd0abc77006a621301809c997420a4 100644 --- a/internal/lsp/client_test.go +++ b/internal/lsp/client_test.go @@ -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) +} diff --git a/internal/lsp/manager.go b/internal/lsp/manager.go index efa7596a685786e3a3c4053eb94f8858c7549e9f..d6b1eaba5498b71c59566c2fbb1df642b6335c6d 100644 --- a/internal/lsp/manager.go +++ b/internal/lsp/manager.go @@ -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) }) } diff --git a/internal/ui/chat/assistant.go b/internal/ui/chat/assistant.go index 4ce71dda2515e5489900c33eb716e1d6d884409a..3a727ea7638915c54fac94872d9487fd283f9076 100644 --- a/internal/ui/chat/assistant.go +++ b/internal/ui/chat/assistant.go @@ -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. diff --git a/internal/ui/chat/file.go b/internal/ui/chat/file.go index d558f79d597871bf6074d33c76b44549ee6725d5..3b1fef8530506be70e512a3cb801ed34a81e0c62 100644 --- a/internal/ui/chat/file.go +++ b/internal/ui/chat/file.go @@ -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 } diff --git a/internal/ui/chat/messages.go b/internal/ui/chat/messages.go index 5dac49c08d32ae2315f9d8096f0410b2511ecb04..8b14e91ad76e866662d2def273fb5cffd3328f95 100644 --- a/internal/ui/chat/messages.go +++ b/internal/ui/chat/messages.go @@ -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 { diff --git a/internal/ui/chat/tools.go b/internal/ui/chat/tools.go index 45c8985992665dddfa9380366035dc2ea20a0d5f..b777a4c1fac31324311595580b1f48207244accd 100644 --- a/internal/ui/chat/tools.go +++ b/internal/ui/chat/tools.go @@ -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. diff --git a/internal/ui/chat/user.go b/internal/ui/chat/user.go index 91211590ce66dd0dd7edbde03becdf469e26b521..a2ccf9d476e686818b284a074f462a9f65012edc 100644 --- a/internal/ui/chat/user.go +++ b/internal/ui/chat/user.go @@ -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. diff --git a/internal/ui/dialog/arguments.go b/internal/ui/dialog/arguments.go index 5cec78593a15356b8fd18d952f78e88c7f158bab..03904651a1a75de5ac7fb7c053566ef310fcfa25 100644 --- a/internal/ui/dialog/arguments.go +++ b/internal/ui/dialog/arguments.go @@ -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) diff --git a/internal/ui/dialog/models.go b/internal/ui/dialog/models.go index 937eac19cab996104a22751270839ebf0656a04d..977f04a61e98f79adb9bb35777fac905508f47d5 100644 --- a/internal/ui/dialog/models.go +++ b/internal/ui/dialog/models.go @@ -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 { diff --git a/internal/ui/dialog/reasoning.go b/internal/ui/dialog/reasoning.go index 2a333f155cdc1499993f05411d7090793f74f54e..cd8ba7d1e293fcb66d04c76f420463aa3a94770f 100644 --- a/internal/ui/dialog/reasoning.go +++ b/internal/ui/dialog/reasoning.go @@ -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. diff --git a/internal/ui/model/header.go b/internal/ui/model/header.go index 92321d5d9cf67c96731fab102436f662f86cdc1b..24254a0f69e5803e4bcbe89274f21db5b04ef541 100644 --- a/internal/ui/model/header.go +++ b/internal/ui/model/header.go @@ -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), "…") } diff --git a/internal/ui/model/lsp.go b/internal/ui/model/lsp.go index 87de0d39d20520b3b24f5da3861efe7d5f9fe4a5..1458d3402cbfa1536e5bef31f7d72ac5d58dddfe 100644 --- a/internal/ui/model/lsp.go +++ b/internal/ui/model/lsp.go @@ -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, diff --git a/internal/ui/model/pills.go b/internal/ui/model/pills.go index fb3dcc1e3a86cb63d9e0a267476863a6260d0816..9b3307135aec89105adb895ef07bbe30484ec658 100644 --- a/internal/ui/model/pills.go +++ b/internal/ui/model/pills.go @@ -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 } diff --git a/internal/ui/model/status.go b/internal/ui/model/status.go index 66dd4082bcc90470129b4a8ebf4ebd65e8567d6c..00f637832cf67a65efb66630308234f353169d3c 100644 --- a/internal/ui/model/status.go +++ b/internal/ui/model/status.go @@ -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. diff --git a/internal/ui/model/ui.go b/internal/ui/model/ui.go index a6199d9fac2c967c73771766ad8faf98dbbd8a4b..7480f7755f108a75c2cf3ea7f68a5adaf5d11e9e 100644 --- a/internal/ui/model/ui.go +++ b/internal/ui/model/ui.go @@ -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 diff --git a/internal/ui/styles/styles.go b/internal/ui/styles/styles.go index fd52fa635a56ac3b61f4b239efe46e09030cd7ea..8f71a348bc1d78b619855f7dbf8c7b52beb0690f 100644 --- a/internal/ui/styles/styles.go +++ b/internal/ui/styles/styles.go @@ -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)