Compare commits

..
45 Commits
Author SHA1 Message Date
Kujtim Hoxha bce2ec5c10 fix duplicate context
release / goreleaser (push) Has been cancelled
2025-04-27 20:43:27 +02:00
Kujtim Hoxha 292e9d90ca remove unnecessary var 2025-04-27 20:34:20 +02:00
Kujtim Hoxha 2b4441a0d1 fix context
release / goreleaser (push) Has been cancelled
2025-04-27 20:31:53 +02:00
Garrett Ladley 8f3a94df92 feat: configure context paths (#86)
release / goreleaser (push) Has been cancelled
2025-04-27 20:11:09 +02:00
Kujtim Hoxha 4415220555 fix minor issue
release / goreleaser (push) Has been cancelled
2025-04-27 19:24:46 +02:00
Kujtim Hoxha a3a04d8a54 fix gemini provider
release / goreleaser (push) Has been cancelled
2025-04-27 19:12:02 +02:00
Lukáš Loukota 792e2b164b fix: gemini tool calling 2025-04-27 19:12:02 +02:00
Kujtim Hoxha 5859dcdc00 small glob fixes 2025-04-27 18:01:31 +02:00
isaac-scarrott 3c2b0f4dd0 [feature/ripgrep-glob] Add ripgrep-based file globbing to improve performance
- Introduced `globWithRipgrep` function to perform file globbing using the `rg` (ripgrep) command.
- Updated `globFiles` to prioritize ripgrep-based globbing and fall back to doublestar-based globbing if ripgrep fails.
- Added logic to handle ripgrep command execution, output parsing, and filtering of hidden files.
- Ensured results are sorted by path length and limited to the specified maximum number of matches.
- Modified imports to include `os/exec` and `bytes` for ripgrep integration.
2025-04-27 18:01:31 +02:00
Kujtim Hoxha 9738886620 fix provider config
release / goreleaser (push) Has been cancelled
2025-04-27 14:44:40 +02:00
Sam Ottenhoff f3dccad54b Handle new Cursor rules format
1. Check if a path ends with a slash (/)
2. If it does, treat it as a directory and read all files within it
3. For directories like .cursor/rules/, it will scan all files and include their content in the prompt
4. Each file from a directory will be prefixed with "# From filename" for clarity
2025-04-27 14:17:06 +02:00
Kujtim Hoxha b3a8dbd0d9 fix retry warning
release / goreleaser (push) Has been cancelled
2025-04-27 14:08:09 +02:00
Garrett Mitchell Ladley d93694a979 feat: simpler diff implementation 2025-04-27 13:56:57 +02:00
Fuad 8a4d4152ce use workingDir if shellInstance is nil otherwise use cwd if shellInstance is not nil 2025-04-27 13:46:59 +02:00
Fuad f12386e558 use provided workingg dir 2025-04-27 13:46:59 +02:00
Fuad 94aeb7b7fe Fix nil pointer dereference in GetPersistentShell
Added nil check in GetPersistentShell before accessing
shellInstance.isAlive
to prevent panic when newPersistentShell returns nil due to shell
startup
errors. This resolves the "invalid memory address or nil pointer
dereference"
error that was occurring in the shell tool.
2025-04-27 13:46:59 +02:00
Kujtim Hoxha a35466cdb3 fix acc error
release / goreleaser (push) Has been cancelled
2025-04-25 21:58:14 +02:00
Kujtim Hoxha 170c7ad67a small fixes 2025-04-25 14:42:47 +02:00
Hunter Casten 7a62ab7675 feat(groq): add support for Groq using the OpenAI provider 2025-04-25 11:11:52 +02:00
Kujtim Hoxha 1586d757dc remove tool timeout 2025-04-24 22:35:17 +02:00
Dax Raad d043526200 add more installation options 2025-04-24 16:34:57 -04:00
Kujtim Hoxha aaf0bc14ba try fix 2025-04-24 22:27:51 +02:00
Kujtim Hoxha f2d9bb7ee3 try fix 2025-04-24 22:27:51 +02:00
Kujtim Hoxha de41703e20 change db driver 2025-04-24 22:27:51 +02:00
Kujtim Hoxha 2c24bfb7b3 fix kitty issues 2025-04-24 19:57:04 +02:00
Dax Raad 47a37b7dd6 back to disablign cgo 2025-04-24 12:46:11 -04:00
Dax Raad bdbf31f0b9 force CGO
release / goreleaser (push) Has been cancelled
2025-04-24 12:34:55 -04:00
Dax Raad 4e6560efb9 turn on cgo
release / goreleaser (push) Has been cancelled
2025-04-24 12:30:33 -04:00
Dax Raad f2f6efdd35 fix version ldflags
release / goreleaser (push) Has been cancelled
2025-04-24 12:26:42 -04:00
Kujtim Hoxha b106787a50 change package name 2025-04-24 18:26:16 +02:00
Kujtim Hoxha e1b2ce483f change additions/removals 2025-04-24 16:40:36 +02:00
Kujtim Hoxha c42d94c465 small fixes 2025-04-24 16:40:36 +02:00
Kujtim Hoxha f879a94c95 update mainter email 2025-04-24 16:40:36 +02:00
Kujtim Hoxha 6d05d5a7c3 small changes 2025-04-24 16:40:36 +02:00
Kujtim Hoxha 2c5003e3fc remove edit/normal mode 2025-04-24 16:40:36 +02:00
Dax Raad 1b22acbc58 sync 2025-04-22 21:29:32 -04:00
Dax Raad 7648a2d790 remove cache
release / goreleaser (push) Has been cancelled
2025-04-22 21:23:20 -04:00
Dax Raad 789ff3c59b sync
release / goreleaser (push) Has been cancelled
2025-04-22 21:21:53 -04:00
Dax Raad 259858df5f attempt cache builds 2025-04-22 21:20:35 -04:00
Dax 289f6af3de CI (#43)
release / goreleaser (push) Has been cancelled
* sync

* ci test

* ci test

* tag

* sync

* sync

* ci

* sync

* enable cgo

* sync

* add back brew

* switch to main
2025-04-22 21:16:14 -04:00
Kujtim Hoxha cded7ee9fd Merge pull request #38 from rgodha24/fix-provider-defaults
fix: set provider defaults correctly in AWS projects
2025-04-22 12:15:27 +02:00
Rohan Godha c02721ce6b fix: set provider defaults correctly in AWS projects 2025-04-22 00:53:05 -04:00
Kujtim Hoxha 5d7a9a98b3 Merge pull request #34 from shreyassanthu77/main 2025-04-22 00:06:03 +02:00
Shreyas Mididoddi ce8bf37f15 look for $HOME/.config for config in addition to $XDG_CONFIG_HOME/ 2025-04-22 00:49:23 +05:30
Kujtim Hoxha f33dff8772 Merge pull request #27 from kujtimiihoxha/opencode
OpenCode - Initial Implementation
2025-04-21 19:59:35 +02:00
79 changed files with 1218 additions and 921 deletions
+4 -1
View File
@@ -4,7 +4,7 @@ on:
workflow_dispatch: workflow_dispatch:
push: push:
branches: branches:
- opencode - main
concurrency: ${{ github.workflow }}-${{ github.ref }} concurrency: ${{ github.workflow }}-${{ github.ref }}
@@ -25,8 +25,11 @@ jobs:
- uses: actions/setup-go@v5 - uses: actions/setup-go@v5
with: with:
go-version: ">=1.23.2" go-version: ">=1.23.2"
cache: true
cache-dependency-path: go.sum
- run: go mod download - run: go mod download
- uses: goreleaser/goreleaser-action@v6 - uses: goreleaser/goreleaser-action@v6
with: with:
distribution: goreleaser distribution: goreleaser
+4 -1
View File
@@ -25,13 +25,16 @@ jobs:
- uses: actions/setup-go@v5 - uses: actions/setup-go@v5
with: with:
go-version: ">=1.23.2" go-version: ">=1.23.2"
cache: true
cache-dependency-path: go.sum
- run: go mod download - run: go mod download
- uses: goreleaser/goreleaser-action@v6 - uses: goreleaser/goreleaser-action@v6
with: with:
distribution: goreleaser distribution: goreleaser
version: latest version: latest
args: release --clean args: release --clean
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_TOKEN }}
AUR_KEY: ${{ secrets.AUR_KEY }} AUR_KEY: ${{ secrets.AUR_KEY }}
+17 -7
View File
@@ -8,11 +8,15 @@ builds:
goos: goos:
- linux - linux
- darwin - darwin
goarch:
- amd64
- arm64
ldflags:
- -s -w -X github.com/opencode-ai/opencode/internal/version.Version={{.Version}}
main: ./main.go main: ./main.go
archives: archives:
- format: tar.gz - format: tar.gz
# this name template makes the OS and Arch compatible with the results of uname.
name_template: >- name_template: >-
opencode- opencode-
{{- if eq .Os "darwin" }}mac- {{- if eq .Os "darwin" }}mac-
@@ -22,7 +26,6 @@ archives:
{{- else if eq .Arch "#86" }}i386 {{- else if eq .Arch "#86" }}i386
{{- else }}{{ .Arch }}{{ end }} {{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }} {{- if .Arm }}v{{ .Arm }}{{ end }}
# use zip for windows archives
format_overrides: format_overrides:
- goos: windows - goos: windows
format: zip format: zip
@@ -31,11 +34,18 @@ checksum:
snapshot: snapshot:
name_template: "0.0.0-{{ .Timestamp }}" name_template: "0.0.0-{{ .Timestamp }}"
aurs: aurs:
- homepage: "https://github.com/opencode-ai/opencode" - name: opencode
description: "Deploy anything" homepage: "https://github.com/opencode-ai/opencode"
private_key: "{{ .Env.AUR_KEY }}" description: "terminal based agent that can build anything"
git_url: "ssh://aur@aur.archlinux.org/opencode.git" maintainers:
- "kujtimiihoxha <kujtimii.h@gmail.com>"
license: "MIT" license: "MIT"
private_key: "{{ .Env.AUR_KEY }}"
git_url: "ssh://aur@aur.archlinux.org/opencode-bin.git"
provides:
- opencode
conflicts:
- opencode
package: |- package: |-
install -Dm755 ./opencode "${pkgdir}/usr/bin/opencode" install -Dm755 ./opencode "${pkgdir}/usr/bin/opencode"
brews: brews:
@@ -43,7 +53,7 @@ brews:
owner: opencode-ai owner: opencode-ai
name: homebrew-tap name: homebrew-tap
nfpms: nfpms:
- maintainer: opencode - maintainer: kujtimiihoxha
description: terminal based agent that can build anything description: terminal based agent that can build anything
formats: formats:
- deb - deb
+43 -4
View File
@@ -1,4 +1,4 @@
# OpenCode # OpenCode
> **⚠️ Early Development Notice:** This project is in early development and is not yet ready for production use. Features may change, break, or be incomplete. Use at your own risk. > **⚠️ Early Development Notice:** This project is in early development and is not yet ready for production use. Features may change, break, or be incomplete. Use at your own risk.
@@ -22,9 +22,36 @@ OpenCode is a Go-based CLI application that brings AI assistance to your termina
## Installation ## Installation
### Using the Install Script
```bash ```bash
# Coming soon # Install the latest version
go install github.com/kujtimiihoxha/opencode@latest curl -fsSL https://opencode.ai/install | bash
# Install a specific version
curl -fsSL https://opencode.ai/install | VERSION=0.1.0 bash
```
### Using Homebrew (macOS and Linux)
```bash
brew install opencode-ai/tap/opencode
```
### Using AUR (Arch Linux)
```bash
# Using yay
yay -S opencode-bin
# Using paru
paru -S opencode-bin
```
### Using Go
```bash
go install github.com/opencode-ai/opencode@latest
``` ```
## Configuration ## Configuration
@@ -64,6 +91,10 @@ You can configure OpenCode using environment variables:
"anthropic": { "anthropic": {
"apiKey": "your-api-key", "apiKey": "your-api-key",
"disabled": false "disabled": false
},
"groq": {
"apiKey": "your-api-key",
"disabled": false
} }
}, },
"agents": { "agents": {
@@ -131,6 +162,14 @@ OpenCode supports a variety of AI models from different providers:
- Claude 3.7 Sonnet - Claude 3.7 Sonnet
### Groq
- Llama 4 Maverick (17b-128e-instruct)
- Llama 4 Scout (17b-16e-instruct)
- QWEN QWQ-32b
- Deepseek R1 distill Llama 70b
- Llama 3.3 70b Versatile
## Usage ## Usage
```bash ```bash
@@ -341,7 +380,7 @@ While the LSP client implementation supports the full LSP protocol (including co
```bash ```bash
# Clone the repository # Clone the repository
git clone https://github.com/kujtimiihoxha/opencode.git git clone https://github.com/opencode-ai/opencode.git
cd opencode cd opencode
# Build # Build
+13 -7
View File
@@ -8,13 +8,14 @@ import (
"time" "time"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/kujtimiihoxha/opencode/internal/app" "github.com/opencode-ai/opencode/internal/app"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/db" "github.com/opencode-ai/opencode/internal/db"
"github.com/kujtimiihoxha/opencode/internal/llm/agent" "github.com/opencode-ai/opencode/internal/llm/agent"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/pubsub" "github.com/opencode-ai/opencode/internal/pubsub"
"github.com/kujtimiihoxha/opencode/internal/tui" "github.com/opencode-ai/opencode/internal/tui"
"github.com/opencode-ai/opencode/internal/version"
zone "github.com/lrstanley/bubblezone" zone "github.com/lrstanley/bubblezone"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -31,6 +32,10 @@ to assist developers in writing, debugging, and understanding code directly from
cmd.Help() cmd.Help()
return nil return nil
} }
if cmd.Flag("version").Changed {
fmt.Println(version.Version)
return nil
}
// Load the config // Load the config
debug, _ := cmd.Flags().GetBool("debug") debug, _ := cmd.Flags().GetBool("debug")
@@ -247,6 +252,7 @@ func Execute() {
func init() { func init() {
rootCmd.Flags().BoolP("help", "h", false, "Help") rootCmd.Flags().BoolP("help", "h", false, "Help")
rootCmd.Flags().BoolP("version", "v", false, "Version")
rootCmd.Flags().BoolP("debug", "d", false, "Debug") rootCmd.Flags().BoolP("debug", "d", false, "Debug")
rootCmd.Flags().StringP("cwd", "c", "", "Current working directory") rootCmd.Flags().StringP("cwd", "c", "", "Current working directory")
} }
+23 -3
View File
@@ -5,8 +5,8 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/llm/models" "github.com/opencode-ai/opencode/internal/llm/models"
) )
// JSONSchemaType represents a JSON Schema type // JSONSchemaType represents a JSON Schema type
@@ -77,6 +77,27 @@ func generateSchema() map[string]any {
"default": false, "default": false,
} }
schema["properties"].(map[string]any)["contextPaths"] = map[string]any{
"type": "array",
"description": "Context paths for the application",
"items": map[string]any{
"type": "string",
},
"default": []string{
".github/copilot-instructions.md",
".cursorrules",
".cursor/rules/",
"CLAUDE.md",
"CLAUDE.local.md",
"opencode.md",
"opencode.local.md",
"OpenCode.md",
"OpenCode.local.md",
"OPENCODE.md",
"OPENCODE.local.md",
},
}
// Add MCP servers // Add MCP servers
schema["properties"].(map[string]any)["mcpServers"] = map[string]any{ schema["properties"].(map[string]any)["mcpServers"] = map[string]any{
"type": "object", "type": "object",
@@ -259,4 +280,3 @@ func generateSchema() map[string]any {
return schema return schema
} }
+19 -31
View File
@@ -1,4 +1,4 @@
module github.com/kujtimiihoxha/opencode module github.com/opencode-ai/opencode
go 1.24.0 go 1.24.0
@@ -9,6 +9,7 @@ require (
github.com/PuerkitoBio/goquery v1.9.2 github.com/PuerkitoBio/goquery v1.9.2
github.com/alecthomas/chroma/v2 v2.15.0 github.com/alecthomas/chroma/v2 v2.15.0
github.com/anthropics/anthropic-sdk-go v0.2.0-beta.2 github.com/anthropics/anthropic-sdk-go v0.2.0-beta.2
github.com/aymanbagabas/go-udiff v0.2.0
github.com/bmatcuk/doublestar/v4 v4.8.1 github.com/bmatcuk/doublestar/v4 v4.8.1
github.com/catppuccin/go v0.3.0 github.com/catppuccin/go v0.3.0
github.com/charmbracelet/bubbles v0.20.0 github.com/charmbracelet/bubbles v0.20.0
@@ -18,19 +19,17 @@ require (
github.com/charmbracelet/lipgloss v1.1.0 github.com/charmbracelet/lipgloss v1.1.0
github.com/charmbracelet/x/ansi v0.8.0 github.com/charmbracelet/x/ansi v0.8.0
github.com/fsnotify/fsnotify v1.8.0 github.com/fsnotify/fsnotify v1.8.0
github.com/go-git/go-git/v5 v5.15.0
github.com/go-logfmt/logfmt v0.6.0 github.com/go-logfmt/logfmt v0.6.0
github.com/golang-migrate/migrate/v4 v4.18.2
github.com/google/generative-ai-go v0.19.0 github.com/google/generative-ai-go v0.19.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/lrstanley/bubblezone v0.0.0-20250315020633-c249a3fe1231 github.com/lrstanley/bubblezone v0.0.0-20250315020633-c249a3fe1231
github.com/mark3labs/mcp-go v0.17.0 github.com/mark3labs/mcp-go v0.17.0
github.com/mattn/go-runewidth v0.0.16
github.com/mattn/go-sqlite3 v1.14.24
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6
github.com/muesli/reflow v0.3.0 github.com/muesli/reflow v0.3.0
github.com/muesli/termenv v0.16.0 github.com/muesli/termenv v0.16.0
github.com/ncruces/go-sqlite3 v0.25.0
github.com/openai/openai-go v0.1.0-beta.2 github.com/openai/openai-go v0.1.0-beta.2
github.com/pressly/goose/v3 v3.24.2
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
github.com/spf13/cobra v1.9.1 github.com/spf13/cobra v1.9.1
github.com/spf13/viper v1.20.0 github.com/spf13/viper v1.20.0
@@ -45,9 +44,6 @@ require (
cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect cloud.google.com/go/compute/metadata v0.6.0 // indirect
cloud.google.com/go/longrunning v0.5.7 // indirect cloud.google.com/go/longrunning v0.5.7 // indirect
dario.cat/mergo v1.0.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/andybalholm/cascadia v1.3.2 // indirect github.com/andybalholm/cascadia v1.3.2 // indirect
github.com/atotto/clipboard v0.1.4 // indirect github.com/atotto/clipboard v0.1.4 // indirect
github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect
@@ -70,62 +66,55 @@ require (
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dlclark/regexp2 v1.11.4 // indirect github.com/dlclark/regexp2 v1.11.4 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/s2a-go v0.1.8 // indirect github.com/google/s2a-go v0.1.8 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/gorilla/css v1.0.1 // indirect github.com/gorilla/css v1.0.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mfridman/interpolate v0.0.2 // indirect
github.com/microcosm-cc/bluemonday v1.0.27 // indirect github.com/microcosm-cc/bluemonday v1.0.27 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/ncruces/julianday v1.0.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.12.0 // indirect github.com/spf13/afero v1.12.0 // indirect
github.com/spf13/cast v1.7.1 // indirect github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect github.com/spf13/pflag v1.0.6 // indirect
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
github.com/tetratelabs/wazero v1.9.0 // indirect
github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect github.com/tidwall/sjson v1.2.5 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
github.com/yuin/goldmark v1.7.8 // indirect github.com/yuin/goldmark v1.7.8 // indirect
github.com/yuin/goldmark-emoji v1.0.5 // indirect github.com/yuin/goldmark-emoji v1.0.5 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
go.opentelemetry.io/otel v1.29.0 // indirect go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/metric v1.29.0 // indirect go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.29.0 // indirect go.opentelemetry.io/otel/trace v1.35.0 // indirect
go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/crypto v0.37.0 // indirect golang.org/x/crypto v0.37.0 // indirect
golang.org/x/net v0.39.0 // indirect golang.org/x/net v0.39.0 // indirect
golang.org/x/oauth2 v0.25.0 // indirect golang.org/x/oauth2 v0.25.0 // indirect
@@ -134,10 +123,9 @@ require (
golang.org/x/term v0.31.0 // indirect golang.org/x/term v0.31.0 // indirect
golang.org/x/text v0.24.0 // indirect golang.org/x/text v0.24.0 // indirect
golang.org/x/time v0.8.0 // indirect golang.org/x/time v0.8.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
google.golang.org/grpc v1.67.3 // indirect google.golang.org/grpc v1.71.0 // indirect
google.golang.org/protobuf v1.36.1 // indirect google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
+50 -87
View File
@@ -10,17 +10,10 @@ cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/JohannesKaufmann/html-to-markdown v1.6.0 h1:04VXMiE50YYfCfLboJCLcgqF5x+rHJnb1ssNmqpLH/k= github.com/JohannesKaufmann/html-to-markdown v1.6.0 h1:04VXMiE50YYfCfLboJCLcgqF5x+rHJnb1ssNmqpLH/k=
github.com/JohannesKaufmann/html-to-markdown v1.6.0/go.mod h1:NUI78lGg/a7vpEJTz/0uOcYMaibytE4BUOQS8k78yPQ= github.com/JohannesKaufmann/html-to-markdown v1.6.0/go.mod h1:NUI78lGg/a7vpEJTz/0uOcYMaibytE4BUOQS8k78yPQ=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE= github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE=
github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk= github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
@@ -31,12 +24,8 @@ github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/anthropics/anthropic-sdk-go v0.2.0-beta.2 h1:h7qxtumNjKPWFv1QM/HJy60MteeW23iKeEtBoY7bYZk= github.com/anthropics/anthropic-sdk-go v0.2.0-beta.2 h1:h7qxtumNjKPWFv1QM/HJy60MteeW23iKeEtBoY7bYZk=
github.com/anthropics/anthropic-sdk-go v0.2.0-beta.2/go.mod h1:AapDW22irxK2PSumZiQXYUFvsdQgkwIWlpESweWZI/c= github.com/anthropics/anthropic-sdk-go v0.2.0-beta.2/go.mod h1:AapDW22irxK2PSumZiQXYUFvsdQgkwIWlpESweWZI/c=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY= github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY=
@@ -99,11 +88,7 @@ github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ= github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 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.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -111,10 +96,6 @@ github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yA
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 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/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
@@ -123,16 +104,6 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.15.0 h1:f5Qn0W0F7ry1iN0ZwIU5m/n7/BKB4hiZfc+zlZx7ly0=
github.com/go-git/go-git/v5 v5.15.0/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -142,10 +113,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/golang-migrate/migrate/v4 v4.18.2 h1:2VSCMz7x7mjyTXx3m2zPokOY82LTRgxK1yQYKo6wWQ8= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang-migrate/migrate/v4 v4.18.2/go.mod h1:2CM6tJvn2kqPXwnXO/d3rAQYiyoIm180VsO8PRX6Rpk= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/google/generative-ai-go v0.19.0 h1:R71szggh8wHMCUlEMsW2A/3T+5LdEIkiaHSYgSpUgdg= github.com/google/generative-ai-go v0.19.0 h1:R71szggh8wHMCUlEMsW2A/3T+5LdEIkiaHSYgSpUgdg=
github.com/google/generative-ai-go v0.19.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E= github.com/google/generative-ai-go v0.19.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
@@ -160,19 +129,10 @@ github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrk
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= 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/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -180,8 +140,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lrstanley/bubblezone v0.0.0-20250315020633-c249a3fe1231 h1:9rjt7AfnrXKNSZhp36A3/4QAZAwGGCGD/p8Bse26zms= github.com/lrstanley/bubblezone v0.0.0-20250315020633-c249a3fe1231 h1:9rjt7AfnrXKNSZhp36A3/4QAZAwGGCGD/p8Bse26zms=
github.com/lrstanley/bubblezone v0.0.0-20250315020633-c249a3fe1231/go.mod h1:S5etECMx+sZnW0Gm100Ma9J1PgVCTgNyFaqGu2b08b4= github.com/lrstanley/bubblezone v0.0.0-20250315020633-c249a3fe1231/go.mod h1:S5etECMx+sZnW0Gm100Ma9J1PgVCTgNyFaqGu2b08b4=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
@@ -195,8 +153,8 @@ github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+Ei
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 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 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
@@ -209,19 +167,23 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/ncruces/go-sqlite3 v0.25.0 h1:trugKUs98Zwy9KwRr/EUxZHL92LYt7UqcKqAfpGpK+I=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/ncruces/go-sqlite3 v0.25.0/go.mod h1:n6Z7036yFilJx04yV0mi5JWaF66rUmXn1It9Ux8dx68=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/openai/openai-go v0.1.0-beta.2 h1:Ra5nCFkbEl9w+UJwAciC4kqnIBUCcJazhmMA0/YN894= github.com/openai/openai-go v0.1.0-beta.2 h1:Ra5nCFkbEl9w+UJwAciC4kqnIBUCcJazhmMA0/YN894=
github.com/openai/openai-go v0.1.0-beta.2/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y= github.com/openai/openai-go v0.1.0-beta.2/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pressly/goose/v3 v3.24.2 h1:c/ie0Gm8rnIVKvnDQ/scHErv46jrDv9b4I0WRcFJzYU=
github.com/pressly/goose/v3 v3.24.2/go.mod h1:kjefwFB0eR4w30Td2Gj2Mznyw94vSP+2jJYkOVNbD1k=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
@@ -237,9 +199,8 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= 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= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
@@ -253,13 +214,14 @@ github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY= github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY=
github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
@@ -270,8 +232,6 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
@@ -282,35 +242,37 @@ github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk= github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk=
github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U= github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= 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.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
@@ -328,14 +290,10 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -358,7 +316,6 @@ golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
@@ -375,22 +332,28 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.215.0 h1:jdYF4qnyczlEz2ReWIsosNLDuzXyvFHJtI5gcr0J7t0= google.golang.org/api v0.215.0 h1:jdYF4qnyczlEz2ReWIsosNLDuzXyvFHJtI5gcr0J7t0=
google.golang.org/api v0.215.0/go.mod h1:fta3CVtuJYOEdugLNWm6WodzOS8KdFckABwN4I40hzY= google.golang.org/api v0.215.0/go.mod h1:fta3CVtuJYOEdugLNWm6WodzOS8KdFckABwN4I40hzY=
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24=
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A= google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.67.3 h1:OgPcDAFKHnH8X3O4WcO4XUc8GRDeKsKReqbQtiCj7N8= google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
google.golang.org/grpc v1.67.3/go.mod h1:YGaHCc6Oap+FzBJTZLBzkGSYt/cvGPFTPxkn7QfSU8s= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8=
modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E=
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.9.1 h1:V/Z1solwAVmMW1yttq3nDdZPJqV1rM05Ccq6KMSZ34g=
modernc.org/memory v1.9.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/sqlite v1.36.2 h1:vjcSazuoFve9Wm0IVNHgmJECoOXLZM1KfMXbcX2axHA=
modernc.org/sqlite v1.36.2/go.mod h1:ADySlx7K4FdY5MaJcEv86hTJ0PjedAloTUuif0YS3ws=
Executable
+180
View File
@@ -0,0 +1,180 @@
#!/usr/bin/env bash
set -euo pipefail
APP=opencode
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
ORANGE='\033[38;2;255;140;0m'
NC='\033[0m' # No Color
requested_version=${VERSION:-}
os=$(uname -s | tr '[:upper:]' '[:lower:]')
if [[ "$os" == "darwin" ]]; then
os="mac"
fi
arch=$(uname -m)
if [[ "$arch" == "aarch64" ]]; then
arch="arm64"
fi
filename="$APP-$os-$arch.tar.gz"
case "$filename" in
*"-linux-"*)
[[ "$arch" == "x86_64" || "$arch" == "arm64" || "$arch" == "i386" ]] || exit 1
;;
*"-mac-"*)
[[ "$arch" == "x86_64" || "$arch" == "arm64" ]] || exit 1
;;
*)
echo "${RED}Unsupported OS/Arch: $os/$arch${NC}"
exit 1
;;
esac
INSTALL_DIR=$HOME/.opencode/bin
mkdir -p "$INSTALL_DIR"
if [ -z "$requested_version" ]; then
url="https://github.com/opencode-ai/opencode/releases/latest/download/$filename"
specific_version=$(curl -s https://api.github.com/repos/opencode-ai/opencode/releases/latest | awk -F'"' '/"tag_name": "/ {gsub(/^v/, "", $4); print $4}')
if [[ $? -ne 0 ]]; then
echo "${RED}Failed to fetch version information${NC}"
exit 1
fi
else
url="https://github.com/opencode-ai/opencode/releases/download/v${requested_version}/$filename"
specific_version=$requested_version
fi
print_message() {
local level=$1
local message=$2
local color=""
case $level in
info) color="${GREEN}" ;;
warning) color="${YELLOW}" ;;
error) color="${RED}" ;;
esac
echo -e "${color}${message}${NC}"
}
check_version() {
if command -v opencode >/dev/null 2>&1; then
opencode_path=$(which opencode)
## TODO: check if version is installed
# installed_version=$(opencode version)
installed_version="0.0.1"
installed_version=$(echo $installed_version | awk '{print $2}')
if [[ "$installed_version" != "$specific_version" ]]; then
print_message info "Installed version: ${YELLOW}$installed_version."
else
print_message info "Version ${YELLOW}$specific_version${GREEN} already installed"
exit 0
fi
fi
}
download_and_install() {
print_message info "Downloading ${ORANGE}opencode ${GREEN}version: ${YELLOW}$specific_version ${GREEN}..."
mkdir -p opencodetmp && cd opencodetmp
curl -# -L $url | tar xz
mv opencode $INSTALL_DIR
cd .. && rm -rf opencodetmp
}
check_version
download_and_install
add_to_path() {
local config_file=$1
local command=$2
if [[ -w $config_file ]]; then
echo -e "\n# opencode" >> "$config_file"
echo "$command" >> "$config_file"
print_message info "Successfully added ${ORANGE}opencode ${GREEN}to \$PATH in $config_file"
else
print_message warning "Manually add the directory to $config_file (or similar):"
print_message info " $command"
fi
}
XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config}
current_shell=$(basename "$SHELL")
case $current_shell in
fish)
config_files="$HOME/.config/fish/config.fish"
;;
zsh)
config_files="$HOME/.zshrc $HOME/.zshenv $XDG_CONFIG_HOME/zsh/.zshrc $XDG_CONFIG_HOME/zsh/.zshenv"
;;
bash)
config_files="$HOME/.bashrc $HOME/.bash_profile $HOME/.profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile"
;;
ash)
config_files="$HOME/.ashrc $HOME/.profile /etc/profile"
;;
sh)
config_files="$HOME/.ashrc $HOME/.profile /etc/profile"
;;
*)
# Default case if none of the above matches
config_files="$HOME/.bashrc $HOME/.bash_profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile"
;;
esac
config_file=""
for file in $config_files; do
if [[ -f $file ]]; then
config_file=$file
break
fi
done
if [[ -z $config_file ]]; then
print_message error "No config file found for $current_shell. Checked files: ${config_files[@]}"
exit 1
fi
if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
case $current_shell in
fish)
add_to_path "$config_file" "fish_add_path $INSTALL_DIR"
;;
zsh)
add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
;;
bash)
add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
;;
ash)
add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
;;
sh)
add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
;;
*)
print_message warning "Manually add the directory to $config_file (or similar):"
print_message info " export PATH=$INSTALL_DIR:\$PATH"
;;
esac
fi
if [ -n "${GITHUB_ACTIONS-}" ] && [ "${GITHUB_ACTIONS}" == "true" ]; then
echo "$INSTALL_DIR" >> $GITHUB_PATH
print_message info "Added $INSTALL_DIR to \$GITHUB_PATH"
fi
+9 -9
View File
@@ -7,15 +7,15 @@ import (
"sync" "sync"
"time" "time"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/db" "github.com/opencode-ai/opencode/internal/db"
"github.com/kujtimiihoxha/opencode/internal/history" "github.com/opencode-ai/opencode/internal/history"
"github.com/kujtimiihoxha/opencode/internal/llm/agent" "github.com/opencode-ai/opencode/internal/llm/agent"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/lsp" "github.com/opencode-ai/opencode/internal/lsp"
"github.com/kujtimiihoxha/opencode/internal/message" "github.com/opencode-ai/opencode/internal/message"
"github.com/kujtimiihoxha/opencode/internal/permission" "github.com/opencode-ai/opencode/internal/permission"
"github.com/kujtimiihoxha/opencode/internal/session" "github.com/opencode-ai/opencode/internal/session"
) )
type App struct { type App struct {
+4 -4
View File
@@ -4,10 +4,10 @@ import (
"context" "context"
"time" "time"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/lsp" "github.com/opencode-ai/opencode/internal/lsp"
"github.com/kujtimiihoxha/opencode/internal/lsp/watcher" "github.com/opencode-ai/opencode/internal/lsp/watcher"
) )
func (app *App) initLSPClients(ctx context.Context) { func (app *App) initLSPClients(ctx context.Context) {
+67 -34
View File
@@ -7,8 +7,8 @@ import (
"os" "os"
"strings" "strings"
"github.com/kujtimiihoxha/opencode/internal/llm/models" "github.com/opencode-ai/opencode/internal/llm/models"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@@ -67,14 +67,15 @@ type LSPConfig struct {
// Config is the main configuration structure for the application. // Config is the main configuration structure for the application.
type Config struct { type Config struct {
Data Data `json:"data"` Data Data `json:"data"`
WorkingDir string `json:"wd,omitempty"` WorkingDir string `json:"wd,omitempty"`
MCPServers map[string]MCPServer `json:"mcpServers,omitempty"` MCPServers map[string]MCPServer `json:"mcpServers,omitempty"`
Providers map[models.ModelProvider]Provider `json:"providers,omitempty"` Providers map[models.ModelProvider]Provider `json:"providers,omitempty"`
LSP map[string]LSPConfig `json:"lsp,omitempty"` LSP map[string]LSPConfig `json:"lsp,omitempty"`
Agents map[AgentName]Agent `json:"agents"` Agents map[AgentName]Agent `json:"agents"`
Debug bool `json:"debug,omitempty"` Debug bool `json:"debug,omitempty"`
DebugLSP bool `json:"debugLSP,omitempty"` DebugLSP bool `json:"debugLSP,omitempty"`
ContextPaths []string `json:"contextPaths,omitempty"`
} }
// Application constants // Application constants
@@ -84,6 +85,20 @@ const (
appName = "opencode" appName = "opencode"
) )
var defaultContextPaths = []string{
".github/copilot-instructions.md",
".cursorrules",
".cursor/rules/",
"CLAUDE.md",
"CLAUDE.local.md",
"opencode.md",
"opencode.local.md",
"OpenCode.md",
"OpenCode.local.md",
"OPENCODE.md",
"OPENCODE.local.md",
}
// Global configuration instance // Global configuration instance
var cfg *Config var cfg *Config
@@ -177,6 +192,7 @@ func configureViper() {
viper.SetConfigType("json") viper.SetConfigType("json")
viper.AddConfigPath("$HOME") viper.AddConfigPath("$HOME")
viper.AddConfigPath(fmt.Sprintf("$XDG_CONFIG_HOME/%s", appName)) viper.AddConfigPath(fmt.Sprintf("$XDG_CONFIG_HOME/%s", appName))
viper.AddConfigPath(fmt.Sprintf("$HOME/.config/%s", appName))
viper.SetEnvPrefix(strings.ToUpper(appName)) viper.SetEnvPrefix(strings.ToUpper(appName))
viper.AutomaticEnv() viper.AutomaticEnv()
} }
@@ -184,6 +200,7 @@ func configureViper() {
// setDefaults configures default values for configuration options. // setDefaults configures default values for configuration options.
func setDefaults(debug bool) { func setDefaults(debug bool) {
viper.SetDefault("data.directory", defaultDataDirectory) viper.SetDefault("data.directory", defaultDataDirectory)
viper.SetDefault("contextPaths", defaultContextPaths)
if debug { if debug {
viper.SetDefault("debug", true) viper.SetDefault("debug", true)
@@ -195,49 +212,65 @@ func setDefaults(debug bool) {
} }
// setProviderDefaults configures LLM provider defaults based on environment variables. // setProviderDefaults configures LLM provider defaults based on environment variables.
// the default model priority is:
// 1. Anthropic
// 2. OpenAI
// 3. Google Gemini
// 4. AWS Bedrock
func setProviderDefaults() { func setProviderDefaults() {
// Groq configuration // Set all API keys we can find in the environment
if apiKey := os.Getenv("GROQ_API_KEY"); apiKey != "" { if apiKey := os.Getenv("ANTHROPIC_API_KEY"); apiKey != "" {
viper.SetDefault("providers.groq.apiKey", apiKey) viper.SetDefault("providers.anthropic.apiKey", apiKey)
viper.SetDefault("agents.coder.model", models.QWENQwq) }
viper.SetDefault("agents.task.model", models.QWENQwq) if apiKey := os.Getenv("OPENAI_API_KEY"); apiKey != "" {
viper.SetDefault("agents.title.model", models.QWENQwq) viper.SetDefault("providers.openai.apiKey", apiKey)
} }
// Google Gemini configuration
if apiKey := os.Getenv("GEMINI_API_KEY"); apiKey != "" { if apiKey := os.Getenv("GEMINI_API_KEY"); apiKey != "" {
viper.SetDefault("providers.gemini.apiKey", apiKey) viper.SetDefault("providers.gemini.apiKey", apiKey)
viper.SetDefault("agents.coder.model", models.Gemini25) }
viper.SetDefault("agents.task.model", models.Gemini25Flash) if apiKey := os.Getenv("GROQ_API_KEY"); apiKey != "" {
viper.SetDefault("agents.title.model", models.Gemini25Flash) viper.SetDefault("providers.groq.apiKey", apiKey)
}
// Use this order to set the default models
// 1. Anthropic
// 2. OpenAI
// 3. Google Gemini
// 4. Groq
// 5. AWS Bedrock
// Anthropic configuration
if apiKey := os.Getenv("ANTHROPIC_API_KEY"); apiKey != "" {
viper.SetDefault("agents.coder.model", models.Claude37Sonnet)
viper.SetDefault("agents.task.model", models.Claude37Sonnet)
viper.SetDefault("agents.title.model", models.Claude37Sonnet)
return
} }
// OpenAI configuration // OpenAI configuration
if apiKey := os.Getenv("OPENAI_API_KEY"); apiKey != "" { if apiKey := os.Getenv("OPENAI_API_KEY"); apiKey != "" {
viper.SetDefault("providers.openai.apiKey", apiKey)
viper.SetDefault("agents.coder.model", models.GPT41) viper.SetDefault("agents.coder.model", models.GPT41)
viper.SetDefault("agents.task.model", models.GPT41Mini) viper.SetDefault("agents.task.model", models.GPT41Mini)
viper.SetDefault("agents.title.model", models.GPT41Mini) viper.SetDefault("agents.title.model", models.GPT41Mini)
return
} }
// Anthropic configuration // Google Gemini configuration
if apiKey := os.Getenv("ANTHROPIC_API_KEY"); apiKey != "" { if apiKey := os.Getenv("GEMINI_API_KEY"); apiKey != "" {
viper.SetDefault("providers.anthropic.apiKey", apiKey) viper.SetDefault("agents.coder.model", models.Gemini25)
viper.SetDefault("agents.coder.model", models.Claude37Sonnet) viper.SetDefault("agents.task.model", models.Gemini25Flash)
viper.SetDefault("agents.task.model", models.Claude37Sonnet) viper.SetDefault("agents.title.model", models.Gemini25Flash)
viper.SetDefault("agents.title.model", models.Claude37Sonnet) return
} }
// Groq configuration
if apiKey := os.Getenv("GROQ_API_KEY"); apiKey != "" {
viper.SetDefault("agents.coder.model", models.QWENQwq)
viper.SetDefault("agents.task.model", models.QWENQwq)
viper.SetDefault("agents.title.model", models.QWENQwq)
return
}
// AWS Bedrock configuration
if hasAWSCredentials() { if hasAWSCredentials() {
viper.SetDefault("agents.coder.model", models.BedrockClaude37Sonnet) viper.SetDefault("agents.coder.model", models.BedrockClaude37Sonnet)
viper.SetDefault("agents.task.model", models.BedrockClaude37Sonnet) viper.SetDefault("agents.task.model", models.BedrockClaude37Sonnet)
viper.SetDefault("agents.title.model", models.BedrockClaude37Sonnet) viper.SetDefault("agents.title.model", models.BedrockClaude37Sonnet)
return
} }
} }
+13 -36
View File
@@ -6,14 +6,13 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/golang-migrate/migrate/v4" _ "github.com/ncruces/go-sqlite3/driver"
"github.com/golang-migrate/migrate/v4/source/iofs" _ "github.com/ncruces/go-sqlite3/embed"
"github.com/golang-migrate/migrate/v4/database/sqlite3" "github.com/opencode-ai/opencode/internal/config"
_ "github.com/mattn/go-sqlite3" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/pressly/goose/v3"
"github.com/kujtimiihoxha/opencode/internal/logging"
) )
func Connect() (*sql.DB, error) { func Connect() (*sql.DB, error) {
@@ -54,38 +53,16 @@ func Connect() (*sql.DB, error) {
} }
} }
// Initialize schema from embedded file goose.SetBaseFS(FS)
d, err := iofs.New(FS, "migrations")
if err != nil { if err := goose.SetDialect("sqlite3"); err != nil {
logging.Error("Failed to open embedded migrations", "error", err) logging.Error("Failed to set dialect", "error", err)
db.Close() return nil, fmt.Errorf("failed to set dialect: %w", err)
return nil, fmt.Errorf("failed to open embedded migrations: %w", err)
} }
driver, err := sqlite3.WithInstance(db, &sqlite3.Config{}) if err := goose.Up(db, "migrations"); err != nil {
if err != nil { logging.Error("Failed to apply migrations", "error", err)
logging.Error("Failed to create SQLite driver", "error", err) return nil, fmt.Errorf("failed to apply migrations: %w", err)
db.Close()
return nil, fmt.Errorf("failed to create SQLite driver: %w", err)
} }
m, err := migrate.NewWithInstance("iofs", d, "ql", driver)
if err != nil {
logging.Error("Failed to create migration instance", "error", err)
db.Close()
return nil, fmt.Errorf("failed to create migration instance: %w", err)
}
err = m.Up()
if err != nil && err != migrate.ErrNoChange {
logging.Error("Migration failed", "error", err)
db.Close()
return nil, fmt.Errorf("failed to apply schema: %w", err)
} else if err == migrate.ErrNoChange {
logging.Info("No schema changes to apply")
} else {
logging.Info("Schema migration applied successfully")
}
return db, nil return db, nil
} }
@@ -1,10 +0,0 @@
DROP TRIGGER IF EXISTS update_sessions_updated_at;
DROP TRIGGER IF EXISTS update_messages_updated_at;
DROP TRIGGER IF EXISTS update_files_updated_at;
DROP TRIGGER IF EXISTS update_session_message_count_on_delete;
DROP TRIGGER IF EXISTS update_session_message_count_on_insert;
DROP TABLE IF EXISTS sessions;
DROP TABLE IF EXISTS messages;
DROP TABLE IF EXISTS files;
@@ -1,3 +1,5 @@
-- +goose Up
-- +goose StatementBegin
-- Sessions -- Sessions
CREATE TABLE IF NOT EXISTS sessions ( CREATE TABLE IF NOT EXISTS sessions (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
@@ -78,3 +80,19 @@ UPDATE sessions SET
message_count = message_count - 1 message_count = message_count - 1
WHERE id = old.session_id; WHERE id = old.session_id;
END; END;
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TRIGGER IF EXISTS update_sessions_updated_at;
DROP TRIGGER IF EXISTS update_messages_updated_at;
DROP TRIGGER IF EXISTS update_files_updated_at;
DROP TRIGGER IF EXISTS update_session_message_count_on_delete;
DROP TRIGGER IF EXISTS update_session_message_count_on_insert;
DROP TABLE IF EXISTS sessions;
DROP TABLE IF EXISTS messages;
DROP TABLE IF EXISTS files;
-- +goose StatementEnd
+16 -106
View File
@@ -4,23 +4,18 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"os"
"path/filepath"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/alecthomas/chroma/v2" "github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/formatters" "github.com/alecthomas/chroma/v2/formatters"
"github.com/alecthomas/chroma/v2/lexers" "github.com/alecthomas/chroma/v2/lexers"
"github.com/alecthomas/chroma/v2/styles" "github.com/alecthomas/chroma/v2/styles"
"github.com/aymanbagabas/go-udiff"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/x/ansi" "github.com/charmbracelet/x/ansi"
"github.com/go-git/go-git/v5" "github.com/opencode-ai/opencode/internal/config"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/kujtimiihoxha/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/logging"
"github.com/sergi/go-diff/diffmatchpatch" "github.com/sergi/go-diff/diffmatchpatch"
) )
@@ -942,106 +937,21 @@ func GenerateDiff(beforeContent, afterContent, fileName string) (string, int, in
cwd := config.WorkingDirectory() cwd := config.WorkingDirectory()
fileName = strings.TrimPrefix(fileName, cwd) fileName = strings.TrimPrefix(fileName, cwd)
fileName = strings.TrimPrefix(fileName, "/") fileName = strings.TrimPrefix(fileName, "/")
// Create temporary directory for git operations
tempDir, err := os.MkdirTemp("", fmt.Sprintf("git-diff-%d", time.Now().UnixNano()))
if err != nil {
logging.Error("Failed to create temp directory for git diff", "error", err)
return "", 0, 0
}
defer os.RemoveAll(tempDir)
// Initialize git repo var (
repo, err := git.PlainInit(tempDir, false) unified = udiff.Unified("a/"+fileName, "b/"+fileName, beforeContent, afterContent)
if err != nil { additions = 0
logging.Error("Failed to initialize git repository", "error", err) removals = 0
return "", 0, 0 )
lines := strings.Split(unified, "\n")
for _, line := range lines {
if strings.HasPrefix(line, "+") && !strings.HasPrefix(line, "+++") {
additions++
} else if strings.HasPrefix(line, "-") && !strings.HasPrefix(line, "---") {
removals++
}
} }
wt, err := repo.Worktree() return unified, additions, removals
if err != nil {
logging.Error("Failed to get git worktree", "error", err)
return "", 0, 0
}
// Write the "before" content and commit it
fullPath := filepath.Join(tempDir, fileName)
if err = os.MkdirAll(filepath.Dir(fullPath), 0o755); err != nil {
logging.Error("Failed to create directory for file", "error", err)
return "", 0, 0
}
if err = os.WriteFile(fullPath, []byte(beforeContent), 0o644); err != nil {
logging.Error("Failed to write before content to file", "error", err)
return "", 0, 0
}
_, err = wt.Add(fileName)
if err != nil {
logging.Error("Failed to add file to git", "error", err)
return "", 0, 0
}
beforeCommit, err := wt.Commit("Before", &git.CommitOptions{
Author: &object.Signature{
Name: "OpenCode",
Email: "coder@opencode.ai",
When: time.Now(),
},
})
if err != nil {
logging.Error("Failed to commit before content", "error", err)
return "", 0, 0
}
// Write the "after" content and commit it
if err = os.WriteFile(fullPath, []byte(afterContent), 0o644); err != nil {
logging.Error("Failed to write after content to file", "error", err)
return "", 0, 0
}
_, err = wt.Add(fileName)
if err != nil {
logging.Error("Failed to add file to git", "error", err)
return "", 0, 0
}
afterCommit, err := wt.Commit("After", &git.CommitOptions{
Author: &object.Signature{
Name: "OpenCode",
Email: "coder@opencode.ai",
When: time.Now(),
},
})
if err != nil {
logging.Error("Failed to commit after content", "error", err)
return "", 0, 0
}
// Get the diff between the two commits
beforeCommitObj, err := repo.CommitObject(beforeCommit)
if err != nil {
logging.Error("Failed to get before commit object", "error", err)
return "", 0, 0
}
afterCommitObj, err := repo.CommitObject(afterCommit)
if err != nil {
logging.Error("Failed to get after commit object", "error", err)
return "", 0, 0
}
patch, err := beforeCommitObj.Patch(afterCommitObj)
if err != nil {
logging.Error("Failed to create git diff patch", "error", err)
return "", 0, 0
}
// Count additions and removals
additions := 0
removals := 0
for _, fileStat := range patch.Stats() {
additions += fileStat.Addition
removals += fileStat.Deletion
}
return patch.String(), additions, removals
} }
+2 -2
View File
@@ -9,8 +9,8 @@ import (
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/kujtimiihoxha/opencode/internal/db" "github.com/opencode-ai/opencode/internal/db"
"github.com/kujtimiihoxha/opencode/internal/pubsub" "github.com/opencode-ai/opencode/internal/pubsub"
) )
const ( const (
+5 -5
View File
@@ -5,11 +5,11 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/llm/tools" "github.com/opencode-ai/opencode/internal/llm/tools"
"github.com/kujtimiihoxha/opencode/internal/lsp" "github.com/opencode-ai/opencode/internal/lsp"
"github.com/kujtimiihoxha/opencode/internal/message" "github.com/opencode-ai/opencode/internal/message"
"github.com/kujtimiihoxha/opencode/internal/session" "github.com/opencode-ai/opencode/internal/session"
) )
type agentTool struct { type agentTool struct {
+9 -9
View File
@@ -7,15 +7,15 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/llm/models" "github.com/opencode-ai/opencode/internal/llm/models"
"github.com/kujtimiihoxha/opencode/internal/llm/prompt" "github.com/opencode-ai/opencode/internal/llm/prompt"
"github.com/kujtimiihoxha/opencode/internal/llm/provider" "github.com/opencode-ai/opencode/internal/llm/provider"
"github.com/kujtimiihoxha/opencode/internal/llm/tools" "github.com/opencode-ai/opencode/internal/llm/tools"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/message" "github.com/opencode-ai/opencode/internal/message"
"github.com/kujtimiihoxha/opencode/internal/permission" "github.com/opencode-ai/opencode/internal/permission"
"github.com/kujtimiihoxha/opencode/internal/session" "github.com/opencode-ai/opencode/internal/session"
) )
// Common errors // Common errors
+6 -6
View File
@@ -5,11 +5,11 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/llm/tools" "github.com/opencode-ai/opencode/internal/llm/tools"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/permission" "github.com/opencode-ai/opencode/internal/permission"
"github.com/kujtimiihoxha/opencode/internal/version" "github.com/opencode-ai/opencode/internal/version"
"github.com/mark3labs/mcp-go/client" "github.com/mark3labs/mcp-go/client"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
@@ -58,7 +58,7 @@ func runTool(ctx context.Context, c MCPClient, toolName string, input string) (t
toolRequest := mcp.CallToolRequest{} toolRequest := mcp.CallToolRequest{}
toolRequest.Params.Name = toolName toolRequest.Params.Name = toolName
var args map[string]any var args map[string]any
if err = json.Unmarshal([]byte(input), &input); err != nil { if err = json.Unmarshal([]byte(input), &args); err != nil {
return tools.NewTextErrorResponse(fmt.Sprintf("error parsing parameters: %s", err)), nil return tools.NewTextErrorResponse(fmt.Sprintf("error parsing parameters: %s", err)), nil
} }
toolRequest.Params.Arguments = args toolRequest.Params.Arguments = args
+6 -6
View File
@@ -3,12 +3,12 @@ package agent
import ( import (
"context" "context"
"github.com/kujtimiihoxha/opencode/internal/history" "github.com/opencode-ai/opencode/internal/history"
"github.com/kujtimiihoxha/opencode/internal/llm/tools" "github.com/opencode-ai/opencode/internal/llm/tools"
"github.com/kujtimiihoxha/opencode/internal/lsp" "github.com/opencode-ai/opencode/internal/lsp"
"github.com/kujtimiihoxha/opencode/internal/message" "github.com/opencode-ai/opencode/internal/message"
"github.com/kujtimiihoxha/opencode/internal/permission" "github.com/opencode-ai/opencode/internal/permission"
"github.com/kujtimiihoxha/opencode/internal/session" "github.com/opencode-ai/opencode/internal/session"
) )
func CoderAgentTools( func CoderAgentTools(
+82
View File
@@ -0,0 +1,82 @@
package models
const (
ProviderGROQ ModelProvider = "groq"
// GROQ
QWENQwq ModelID = "qwen-qwq"
// GROQ preview models
Llama4Scout ModelID = "meta-llama/llama-4-scout-17b-16e-instruct"
Llama4Maverick ModelID = "meta-llama/llama-4-maverick-17b-128e-instruct"
Llama3_3_70BVersatile ModelID = "llama-3.3-70b-versatile"
DeepseekR1DistillLlama70b ModelID = "deepseek-r1-distill-llama-70b"
)
var GroqModels = map[ModelID]Model{
//
// GROQ
QWENQwq: {
ID: QWENQwq,
Name: "Qwen Qwq",
Provider: ProviderGROQ,
APIModel: "qwen-qwq-32b",
CostPer1MIn: 0.29,
CostPer1MInCached: 0.275,
CostPer1MOutCached: 0.0,
CostPer1MOut: 0.39,
ContextWindow: 128_000,
DefaultMaxTokens: 50000,
// for some reason, the groq api doesn't like the reasoningEffort parameter
CanReason: false,
},
Llama4Scout: {
ID: Llama4Scout,
Name: "Llama4Scout",
Provider: ProviderGROQ,
APIModel: "meta-llama/llama-4-scout-17b-16e-instruct",
CostPer1MIn: 0.11,
CostPer1MInCached: 0,
CostPer1MOutCached: 0,
CostPer1MOut: 0.34,
ContextWindow: 128_000, // 10M when?
},
Llama4Maverick: {
ID: Llama4Maverick,
Name: "Llama4Maverick",
Provider: ProviderGROQ,
APIModel: "meta-llama/llama-4-maverick-17b-128e-instruct",
CostPer1MIn: 0.20,
CostPer1MInCached: 0,
CostPer1MOutCached: 0,
CostPer1MOut: 0.20,
ContextWindow: 128_000,
},
Llama3_3_70BVersatile: {
ID: Llama3_3_70BVersatile,
Name: "Llama3_3_70BVersatile",
Provider: ProviderGROQ,
APIModel: "llama-3.3-70b-versatile",
CostPer1MIn: 0.59,
CostPer1MInCached: 0,
CostPer1MOutCached: 0,
CostPer1MOut: 0.79,
ContextWindow: 128_000,
},
DeepseekR1DistillLlama70b: {
ID: DeepseekR1DistillLlama70b,
Name: "DeepseekR1DistillLlama70b",
Provider: ProviderGROQ,
APIModel: "deepseek-r1-distill-llama-70b",
CostPer1MIn: 0.75,
CostPer1MInCached: 0,
CostPer1MOutCached: 0,
CostPer1MOut: 0.99,
ContextWindow: 128_000,
CanReason: true,
},
}
+1 -17
View File
@@ -23,17 +23,12 @@ type Model struct {
// Model IDs // Model IDs
const ( // GEMINI const ( // GEMINI
// GROQ
QWENQwq ModelID = "qwen-qwq"
// Bedrock // Bedrock
BedrockClaude37Sonnet ModelID = "bedrock.claude-3.7-sonnet" BedrockClaude37Sonnet ModelID = "bedrock.claude-3.7-sonnet"
) )
const ( const (
ProviderBedrock ModelProvider = "bedrock" ProviderBedrock ModelProvider = "bedrock"
ProviderGROQ ModelProvider = "groq"
// ForTests // ForTests
ProviderMock ModelProvider = "__mock" ProviderMock ModelProvider = "__mock"
) )
@@ -63,18 +58,6 @@ var SupportedModels = map[ModelID]Model{
// CostPer1MOut: 0.4, // CostPer1MOut: 0.4,
// }, // },
// //
// // GROQ
// QWENQwq: {
// ID: QWENQwq,
// Name: "Qwen Qwq",
// Provider: ProviderGROQ,
// APIModel: "qwen-qwq-32b",
// CostPer1MIn: 0,
// CostPer1MInCached: 0,
// CostPer1MOutCached: 0,
// CostPer1MOut: 0,
// },
//
// // Bedrock // // Bedrock
BedrockClaude37Sonnet: { BedrockClaude37Sonnet: {
ID: BedrockClaude37Sonnet, ID: BedrockClaude37Sonnet,
@@ -92,4 +75,5 @@ func init() {
maps.Copy(SupportedModels, AnthropicModels) maps.Copy(SupportedModels, AnthropicModels)
maps.Copy(SupportedModels, OpenAIModels) maps.Copy(SupportedModels, OpenAIModels)
maps.Copy(SupportedModels, GeminiModels) maps.Copy(SupportedModels, GeminiModels)
maps.Copy(SupportedModels, GroqModels)
} }
+3 -3
View File
@@ -8,9 +8,9 @@ import (
"runtime" "runtime"
"time" "time"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/llm/models" "github.com/opencode-ai/opencode/internal/llm/models"
"github.com/kujtimiihoxha/opencode/internal/llm/tools" "github.com/opencode-ai/opencode/internal/llm/tools"
) )
func CoderPrompt(provider models.ModelProvider) string { func CoderPrompt(provider models.ModelProvider) string {
+101 -29
View File
@@ -4,25 +4,14 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"sync"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/llm/models" "github.com/opencode-ai/opencode/internal/llm/models"
"github.com/opencode-ai/opencode/internal/logging"
) )
// contextFiles is a list of potential context files to check for
var contextFiles = []string{
".github/copilot-instructions.md",
".cursorrules",
"CLAUDE.md",
"CLAUDE.local.md",
"opencode.md",
"opencode.local.md",
"OpenCode.md",
"OpenCode.local.md",
"OPENCODE.md",
"OPENCODE.local.md",
}
func GetAgentPrompt(agentName config.AgentName, provider models.ModelProvider) string { func GetAgentPrompt(agentName config.AgentName, provider models.ModelProvider) string {
basePrompt := "" basePrompt := ""
switch agentName { switch agentName {
@@ -38,26 +27,109 @@ func GetAgentPrompt(agentName config.AgentName, provider models.ModelProvider) s
if agentName == config.AgentCoder || agentName == config.AgentTask { if agentName == config.AgentCoder || agentName == config.AgentTask {
// Add context from project-specific instruction files if they exist // Add context from project-specific instruction files if they exist
contextContent := getContextFromFiles() contextContent := getContextFromPaths()
logging.Debug("Context content", "Context", contextContent)
if contextContent != "" { if contextContent != "" {
return fmt.Sprintf("%s\n\n# Project-Specific Context\n%s", basePrompt, contextContent) return fmt.Sprintf("%s\n\n# Project-Specific Context\n Make sure to follow the instructions in the context below\n%s", basePrompt, contextContent)
} }
} }
return basePrompt return basePrompt
} }
// getContextFromFiles checks for the existence of context files and returns their content var (
func getContextFromFiles() string { onceContext sync.Once
workDir := config.WorkingDirectory() contextContent string
var contextContent string )
for _, file := range contextFiles { func getContextFromPaths() string {
filePath := filepath.Join(workDir, file) onceContext.Do(func() {
content, err := os.ReadFile(filePath) var (
if err == nil { cfg = config.Get()
contextContent += fmt.Sprintf("\n%s\n", string(content)) workDir = cfg.WorkingDir
} contextPaths = cfg.ContextPaths
} )
contextContent = processContextPaths(workDir, contextPaths)
})
return contextContent return contextContent
} }
func processContextPaths(workDir string, paths []string) string {
var (
wg sync.WaitGroup
resultCh = make(chan string)
)
// Track processed files to avoid duplicates
processedFiles := make(map[string]bool)
var processedMutex sync.Mutex
for _, path := range paths {
wg.Add(1)
go func(p string) {
defer wg.Done()
if strings.HasSuffix(p, "/") {
filepath.WalkDir(filepath.Join(workDir, p), func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
if !d.IsDir() {
// Check if we've already processed this file (case-insensitive)
processedMutex.Lock()
lowerPath := strings.ToLower(path)
if !processedFiles[lowerPath] {
processedFiles[lowerPath] = true
processedMutex.Unlock()
if result := processFile(path); result != "" {
resultCh <- result
}
} else {
processedMutex.Unlock()
}
}
return nil
})
} else {
fullPath := filepath.Join(workDir, p)
// Check if we've already processed this file (case-insensitive)
processedMutex.Lock()
lowerPath := strings.ToLower(fullPath)
if !processedFiles[lowerPath] {
processedFiles[lowerPath] = true
processedMutex.Unlock()
result := processFile(fullPath)
if result != "" {
resultCh <- result
}
} else {
processedMutex.Unlock()
}
}
}(path)
}
go func() {
wg.Wait()
close(resultCh)
}()
results := make([]string, 0)
for result := range resultCh {
results = append(results, result)
}
return strings.Join(results, "\n")
}
func processFile(filePath string) string {
content, err := os.ReadFile(filePath)
if err != nil {
return ""
}
return "# From:" + filePath + "\n" + string(content)
}
+1 -1
View File
@@ -3,7 +3,7 @@ package prompt
import ( import (
"fmt" "fmt"
"github.com/kujtimiihoxha/opencode/internal/llm/models" "github.com/opencode-ai/opencode/internal/llm/models"
) )
func TaskPrompt(_ models.ModelProvider) string { func TaskPrompt(_ models.ModelProvider) string {
+1 -1
View File
@@ -1,6 +1,6 @@
package prompt package prompt
import "github.com/kujtimiihoxha/opencode/internal/llm/models" import "github.com/opencode-ai/opencode/internal/llm/models"
func TitlePrompt(_ models.ModelProvider) string { func TitlePrompt(_ models.ModelProvider) string {
return `you will generate a short title based on the first message a user begins a conversation with return `you will generate a short title based on the first message a user begins a conversation with
+7 -7
View File
@@ -12,10 +12,10 @@ import (
"github.com/anthropics/anthropic-sdk-go" "github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/bedrock" "github.com/anthropics/anthropic-sdk-go/bedrock"
"github.com/anthropics/anthropic-sdk-go/option" "github.com/anthropics/anthropic-sdk-go/option"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/llm/tools" "github.com/opencode-ai/opencode/internal/llm/tools"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/message" "github.com/opencode-ai/opencode/internal/message"
) )
type anthropicOptions struct { type anthropicOptions struct {
@@ -213,7 +213,7 @@ func (a *anthropicClient) send(ctx context.Context, messages []message.Message,
return nil, retryErr return nil, retryErr
} }
if retry { if retry {
logging.WarnPersist("Retrying due to rate limit... attempt %d of %d", logging.PersistTimeArg, time.Millisecond*time.Duration(after+100)) logging.WarnPersist(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries), logging.PersistTimeArg, time.Millisecond*time.Duration(after+100))
select { select {
case <-ctx.Done(): case <-ctx.Done():
return nil, ctx.Err() return nil, ctx.Err()
@@ -262,7 +262,7 @@ func (a *anthropicClient) stream(ctx context.Context, messages []message.Message
event := anthropicStream.Current() event := anthropicStream.Current()
err := accumulatedMessage.Accumulate(event) err := accumulatedMessage.Accumulate(event)
if err != nil { if err != nil {
eventChan <- ProviderEvent{Type: EventError, Error: err} logging.Warn("Error accumulating message", "error", err)
continue continue
} }
@@ -351,7 +351,7 @@ func (a *anthropicClient) stream(ctx context.Context, messages []message.Message
return return
} }
if retry { if retry {
logging.WarnPersist("Retrying due to rate limit... attempt %d of %d", logging.PersistTimeArg, time.Millisecond*time.Duration(after+100)) logging.WarnPersist(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries), logging.PersistTimeArg, time.Millisecond*time.Duration(after+100))
select { select {
case <-ctx.Done(): case <-ctx.Done():
// context cancelled // context cancelled
+2 -2
View File
@@ -7,8 +7,8 @@ import (
"os" "os"
"strings" "strings"
"github.com/kujtimiihoxha/opencode/internal/llm/tools" "github.com/opencode-ai/opencode/internal/llm/tools"
"github.com/kujtimiihoxha/opencode/internal/message" "github.com/opencode-ai/opencode/internal/message"
) )
type bedrockOptions struct { type bedrockOptions struct {
+55 -82
View File
@@ -11,10 +11,10 @@ import (
"github.com/google/generative-ai-go/genai" "github.com/google/generative-ai-go/genai"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/llm/tools" "github.com/opencode-ai/opencode/internal/llm/tools"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/message" "github.com/opencode-ai/opencode/internal/message"
"google.golang.org/api/iterator" "google.golang.org/api/iterator"
"google.golang.org/api/option" "google.golang.org/api/option"
) )
@@ -54,19 +54,6 @@ func newGeminiClient(opts providerClientOptions) GeminiClient {
func (g *geminiClient) convertMessages(messages []message.Message) []*genai.Content { func (g *geminiClient) convertMessages(messages []message.Message) []*genai.Content {
var history []*genai.Content var history []*genai.Content
// Add system message first
history = append(history, &genai.Content{
Parts: []genai.Part{genai.Text(g.providerOptions.systemMessage)},
Role: "user",
})
// Add a system response to acknowledge the system message
history = append(history, &genai.Content{
Parts: []genai.Part{genai.Text("I'll help you with that.")},
Role: "model",
})
for _, msg := range messages { for _, msg := range messages {
switch msg.Role { switch msg.Role {
case message.User: case message.User:
@@ -132,7 +119,8 @@ func (g *geminiClient) convertMessages(messages []message.Message) []*genai.Cont
} }
func (g *geminiClient) convertTools(tools []tools.BaseTool) []*genai.Tool { func (g *geminiClient) convertTools(tools []tools.BaseTool) []*genai.Tool {
geminiTools := make([]*genai.Tool, 0, len(tools)) geminiTool := &genai.Tool{}
geminiTool.FunctionDeclarations = make([]*genai.FunctionDeclaration, 0, len(tools))
for _, tool := range tools { for _, tool := range tools {
info := tool.Info() info := tool.Info()
@@ -146,23 +134,18 @@ func (g *geminiClient) convertTools(tools []tools.BaseTool) []*genai.Tool {
}, },
} }
geminiTools = append(geminiTools, &genai.Tool{ geminiTool.FunctionDeclarations = append(geminiTool.FunctionDeclarations, declaration)
FunctionDeclarations: []*genai.FunctionDeclaration{declaration},
})
} }
return geminiTools return []*genai.Tool{geminiTool}
} }
func (g *geminiClient) finishReason(reason genai.FinishReason) message.FinishReason { func (g *geminiClient) finishReason(reason genai.FinishReason) message.FinishReason {
reasonStr := reason.String()
switch { switch {
case reasonStr == "STOP": case reason == genai.FinishReasonStop:
return message.FinishReasonEndTurn return message.FinishReasonEndTurn
case reasonStr == "MAX_TOKENS": case reason == genai.FinishReasonMaxTokens:
return message.FinishReasonMaxTokens return message.FinishReasonMaxTokens
case strings.Contains(reasonStr, "FUNCTION") || strings.Contains(reasonStr, "TOOL"):
return message.FinishReasonToolUse
default: default:
return message.FinishReasonUnknown return message.FinishReasonUnknown
} }
@@ -171,7 +154,11 @@ func (g *geminiClient) finishReason(reason genai.FinishReason) message.FinishRea
func (g *geminiClient) send(ctx context.Context, messages []message.Message, tools []tools.BaseTool) (*ProviderResponse, error) { func (g *geminiClient) send(ctx context.Context, messages []message.Message, tools []tools.BaseTool) (*ProviderResponse, error) {
model := g.client.GenerativeModel(g.providerOptions.model.APIModel) model := g.client.GenerativeModel(g.providerOptions.model.APIModel)
model.SetMaxOutputTokens(int32(g.providerOptions.maxTokens)) model.SetMaxOutputTokens(int32(g.providerOptions.maxTokens))
model.SystemInstruction = &genai.Content{
Parts: []genai.Part{
genai.Text(g.providerOptions.systemMessage),
},
}
// Convert tools // Convert tools
if len(tools) > 0 { if len(tools) > 0 {
model.Tools = g.convertTools(tools) model.Tools = g.convertTools(tools)
@@ -189,19 +176,13 @@ func (g *geminiClient) send(ctx context.Context, messages []message.Message, too
attempts := 0 attempts := 0
for { for {
attempts++ attempts++
var toolCalls []message.ToolCall
chat := model.StartChat() chat := model.StartChat()
chat.History = geminiMessages[:len(geminiMessages)-1] // All but last message chat.History = geminiMessages[:len(geminiMessages)-1] // All but last message
lastMsg := geminiMessages[len(geminiMessages)-1] lastMsg := geminiMessages[len(geminiMessages)-1]
var lastText string
for _, part := range lastMsg.Parts {
if text, ok := part.(genai.Text); ok {
lastText = string(text)
break
}
}
resp, err := chat.SendMessage(ctx, genai.Text(lastText)) resp, err := chat.SendMessage(ctx, lastMsg.Parts...)
// If there is an error we are going to see if we can retry the call // If there is an error we are going to see if we can retry the call
if err != nil { if err != nil {
retry, after, retryErr := g.shouldRetry(attempts, err) retry, after, retryErr := g.shouldRetry(attempts, err)
@@ -209,7 +190,7 @@ func (g *geminiClient) send(ctx context.Context, messages []message.Message, too
return nil, retryErr return nil, retryErr
} }
if retry { if retry {
logging.WarnPersist("Retrying due to rate limit... attempt %d of %d", logging.PersistTimeArg, time.Millisecond*time.Duration(after+100)) logging.WarnPersist(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries), logging.PersistTimeArg, time.Millisecond*time.Duration(after+100))
select { select {
case <-ctx.Done(): case <-ctx.Done():
return nil, ctx.Err() return nil, ctx.Err()
@@ -221,7 +202,6 @@ func (g *geminiClient) send(ctx context.Context, messages []message.Message, too
} }
content := "" content := ""
var toolCalls []message.ToolCall
if len(resp.Candidates) > 0 && resp.Candidates[0].Content != nil { if len(resp.Candidates) > 0 && resp.Candidates[0].Content != nil {
for _, part := range resp.Candidates[0].Content.Parts { for _, part := range resp.Candidates[0].Content.Parts {
@@ -232,20 +212,28 @@ func (g *geminiClient) send(ctx context.Context, messages []message.Message, too
id := "call_" + uuid.New().String() id := "call_" + uuid.New().String()
args, _ := json.Marshal(p.Args) args, _ := json.Marshal(p.Args)
toolCalls = append(toolCalls, message.ToolCall{ toolCalls = append(toolCalls, message.ToolCall{
ID: id, ID: id,
Name: p.Name, Name: p.Name,
Input: string(args), Input: string(args),
Type: "function", Type: "function",
Finished: true,
}) })
} }
} }
} }
finishReason := message.FinishReasonEndTurn
if len(resp.Candidates) > 0 {
finishReason = g.finishReason(resp.Candidates[0].FinishReason)
}
if len(toolCalls) > 0 {
finishReason = message.FinishReasonToolUse
}
return &ProviderResponse{ return &ProviderResponse{
Content: content, Content: content,
ToolCalls: toolCalls, ToolCalls: toolCalls,
Usage: g.usage(resp), Usage: g.usage(resp),
FinishReason: g.finishReason(resp.Candidates[0].FinishReason), FinishReason: finishReason,
}, nil }, nil
} }
} }
@@ -253,7 +241,11 @@ func (g *geminiClient) send(ctx context.Context, messages []message.Message, too
func (g *geminiClient) stream(ctx context.Context, messages []message.Message, tools []tools.BaseTool) <-chan ProviderEvent { func (g *geminiClient) stream(ctx context.Context, messages []message.Message, tools []tools.BaseTool) <-chan ProviderEvent {
model := g.client.GenerativeModel(g.providerOptions.model.APIModel) model := g.client.GenerativeModel(g.providerOptions.model.APIModel)
model.SetMaxOutputTokens(int32(g.providerOptions.maxTokens)) model.SetMaxOutputTokens(int32(g.providerOptions.maxTokens))
model.SystemInstruction = &genai.Content{
Parts: []genai.Part{
genai.Text(g.providerOptions.systemMessage),
},
}
// Convert tools // Convert tools
if len(tools) > 0 { if len(tools) > 0 {
model.Tools = g.convertTools(tools) model.Tools = g.convertTools(tools)
@@ -277,18 +269,10 @@ func (g *geminiClient) stream(ctx context.Context, messages []message.Message, t
for { for {
attempts++ attempts++
chat := model.StartChat() chat := model.StartChat()
chat.History = geminiMessages[:len(geminiMessages)-1] // All but last message chat.History = geminiMessages[:len(geminiMessages)-1]
lastMsg := geminiMessages[len(geminiMessages)-1] lastMsg := geminiMessages[len(geminiMessages)-1]
var lastText string
for _, part := range lastMsg.Parts {
if text, ok := part.(genai.Text); ok {
lastText = string(text)
break
}
}
iter := chat.SendMessageStream(ctx, genai.Text(lastText)) iter := chat.SendMessageStream(ctx, lastMsg.Parts...)
currentContent := "" currentContent := ""
toolCalls := []message.ToolCall{} toolCalls := []message.ToolCall{}
@@ -308,7 +292,7 @@ func (g *geminiClient) stream(ctx context.Context, messages []message.Message, t
return return
} }
if retry { if retry {
logging.WarnPersist("Retrying due to rate limit... attempt %d of %d", logging.PersistTimeArg, time.Millisecond*time.Duration(after+100)) logging.WarnPersist(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries), logging.PersistTimeArg, time.Millisecond*time.Duration(after+100))
select { select {
case <-ctx.Done(): case <-ctx.Done():
if ctx.Err() != nil { if ctx.Err() != nil {
@@ -331,23 +315,23 @@ func (g *geminiClient) stream(ctx context.Context, messages []message.Message, t
for _, part := range resp.Candidates[0].Content.Parts { for _, part := range resp.Candidates[0].Content.Parts {
switch p := part.(type) { switch p := part.(type) {
case genai.Text: case genai.Text:
newText := string(p) delta := string(p)
delta := newText[len(currentContent):]
if delta != "" { if delta != "" {
eventChan <- ProviderEvent{ eventChan <- ProviderEvent{
Type: EventContentDelta, Type: EventContentDelta,
Content: delta, Content: delta,
} }
currentContent = newText currentContent += delta
} }
case genai.FunctionCall: case genai.FunctionCall:
id := "call_" + uuid.New().String() id := "call_" + uuid.New().String()
args, _ := json.Marshal(p.Args) args, _ := json.Marshal(p.Args)
newCall := message.ToolCall{ newCall := message.ToolCall{
ID: id, ID: id,
Name: p.Name, Name: p.Name,
Input: string(args), Input: string(args),
Type: "function", Type: "function",
Finished: true,
} }
isNew := true isNew := true
@@ -369,37 +353,26 @@ func (g *geminiClient) stream(ctx context.Context, messages []message.Message, t
eventChan <- ProviderEvent{Type: EventContentStop} eventChan <- ProviderEvent{Type: EventContentStop}
if finalResp != nil { if finalResp != nil {
finishReason := message.FinishReasonEndTurn
if len(finalResp.Candidates) > 0 {
finishReason = g.finishReason(finalResp.Candidates[0].FinishReason)
}
if len(toolCalls) > 0 {
finishReason = message.FinishReasonToolUse
}
eventChan <- ProviderEvent{ eventChan <- ProviderEvent{
Type: EventComplete, Type: EventComplete,
Response: &ProviderResponse{ Response: &ProviderResponse{
Content: currentContent, Content: currentContent,
ToolCalls: toolCalls, ToolCalls: toolCalls,
Usage: g.usage(finalResp), Usage: g.usage(finalResp),
FinishReason: g.finishReason(finalResp.Candidates[0].FinishReason), FinishReason: finishReason,
}, },
} }
return return
} }
// If we get here, we need to retry
if attempts > maxRetries {
eventChan <- ProviderEvent{
Type: EventError,
Error: fmt.Errorf("maximum retry attempts reached: %d retries", maxRetries),
}
return
}
// Wait before retrying
select {
case <-ctx.Done():
if ctx.Err() != nil {
eventChan <- ProviderEvent{Type: EventError, Error: ctx.Err()}
}
return
case <-time.After(time.Duration(2000*(1<<(attempts-1))) * time.Millisecond):
continue
}
} }
}() }()
+6 -6
View File
@@ -8,13 +8,13 @@ import (
"io" "io"
"time" "time"
"github.com/kujtimiihoxha/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/llm/tools"
"github.com/kujtimiihoxha/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/message"
"github.com/openai/openai-go" "github.com/openai/openai-go"
"github.com/openai/openai-go/option" "github.com/openai/openai-go/option"
"github.com/openai/openai-go/shared" "github.com/openai/openai-go/shared"
"github.com/opencode-ai/opencode/internal/config"
"github.com/opencode-ai/opencode/internal/llm/tools"
"github.com/opencode-ai/opencode/internal/logging"
"github.com/opencode-ai/opencode/internal/message"
) )
type openaiOptions struct { type openaiOptions struct {
@@ -188,7 +188,7 @@ func (o *openaiClient) send(ctx context.Context, messages []message.Message, too
return nil, retryErr return nil, retryErr
} }
if retry { if retry {
logging.WarnPersist("Retrying due to rate limit... attempt %d of %d", logging.PersistTimeArg, time.Millisecond*time.Duration(after+100)) logging.WarnPersist(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries), logging.PersistTimeArg, time.Millisecond*time.Duration(after+100))
select { select {
case <-ctx.Done(): case <-ctx.Done():
return nil, ctx.Err() return nil, ctx.Err()
@@ -288,7 +288,7 @@ func (o *openaiClient) stream(ctx context.Context, messages []message.Message, t
return return
} }
if retry { if retry {
logging.WarnPersist("Retrying due to rate limit... attempt %d of %d", logging.PersistTimeArg, time.Millisecond*time.Duration(after+100)) logging.WarnPersist(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries), logging.PersistTimeArg, time.Millisecond*time.Duration(after+100))
select { select {
case <-ctx.Done(): case <-ctx.Done():
// context cancelled // context cancelled
+11 -3
View File
@@ -4,9 +4,9 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/kujtimiihoxha/opencode/internal/llm/models" "github.com/opencode-ai/opencode/internal/llm/models"
"github.com/kujtimiihoxha/opencode/internal/llm/tools" "github.com/opencode-ai/opencode/internal/llm/tools"
"github.com/kujtimiihoxha/opencode/internal/message" "github.com/opencode-ai/opencode/internal/message"
) )
type EventType string type EventType string
@@ -107,6 +107,14 @@ func NewProvider(providerName models.ModelProvider, opts ...ProviderClientOption
options: clientOptions, options: clientOptions,
client: newBedrockClient(clientOptions), client: newBedrockClient(clientOptions),
}, nil }, nil
case models.ProviderGROQ:
clientOptions.openaiOptions = append(clientOptions.openaiOptions,
WithOpenAIBaseURL("https://api.groq.com/openai/v1"),
)
return &baseProvider[OpenAIClient]{
options: clientOptions,
client: newOpenAIClient(clientOptions),
}, nil
case models.ProviderMock: case models.ProviderMock:
// TODO: implement mock client for test // TODO: implement mock client for test
panic("not implemented") panic("not implemented")
+3 -3
View File
@@ -7,9 +7,9 @@ import (
"strings" "strings"
"time" "time"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/llm/tools/shell" "github.com/opencode-ai/opencode/internal/llm/tools/shell"
"github.com/kujtimiihoxha/opencode/internal/permission" "github.com/opencode-ai/opencode/internal/permission"
) )
type BashParams struct { type BashParams struct {
+2 -2
View File
@@ -9,8 +9,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/kujtimiihoxha/opencode/internal/lsp" "github.com/opencode-ai/opencode/internal/lsp"
"github.com/kujtimiihoxha/opencode/internal/lsp/protocol" "github.com/opencode-ai/opencode/internal/lsp/protocol"
) )
type DiagnosticsParams struct { type DiagnosticsParams struct {
+6 -6
View File
@@ -9,12 +9,12 @@ import (
"strings" "strings"
"time" "time"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/diff" "github.com/opencode-ai/opencode/internal/diff"
"github.com/kujtimiihoxha/opencode/internal/history" "github.com/opencode-ai/opencode/internal/history"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/lsp" "github.com/opencode-ai/opencode/internal/lsp"
"github.com/kujtimiihoxha/opencode/internal/permission" "github.com/opencode-ai/opencode/internal/permission"
) )
type EditParams struct { type EditParams struct {
+2 -2
View File
@@ -11,8 +11,8 @@ import (
md "github.com/JohannesKaufmann/html-to-markdown" md "github.com/JohannesKaufmann/html-to-markdown"
"github.com/PuerkitoBio/goquery" "github.com/PuerkitoBio/goquery"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/permission" "github.com/opencode-ai/opencode/internal/permission"
) )
type FetchParams struct { type FetchParams struct {
+73 -8
View File
@@ -1,18 +1,20 @@
package tools package tools
import ( import (
"bytes"
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/fs" "io/fs"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"sort" "sort"
"strings" "strings"
"time" "time"
"github.com/bmatcuk/doublestar/v4" "github.com/bmatcuk/doublestar/v4"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
) )
const ( const (
@@ -132,14 +134,73 @@ func (g *globTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
} }
func globFiles(pattern, searchPath string, limit int) ([]string, bool, error) { func globFiles(pattern, searchPath string, limit int) ([]string, bool, error) {
if !strings.HasPrefix(pattern, "/") && !strings.HasPrefix(pattern, searchPath) { matches, err := globWithRipgrep(pattern, searchPath, limit)
if !strings.HasSuffix(searchPath, "/") { if err == nil {
searchPath += "/" return matches, len(matches) >= limit, nil
}
pattern = searchPath + pattern
} }
fsys := os.DirFS("/") return globWithDoublestar(pattern, searchPath, limit)
}
func globWithRipgrep(
pattern, searchRoot string,
limit int,
) ([]string, error) {
if searchRoot == "" {
searchRoot = "."
}
rgBin, err := exec.LookPath("rg")
if err != nil {
return nil, fmt.Errorf("ripgrep not found in $PATH: %w", err)
}
if !filepath.IsAbs(pattern) && !strings.HasPrefix(pattern, "/") {
pattern = "/" + pattern
}
args := []string{
"--files",
"--null",
"--glob", pattern,
"-L",
}
cmd := exec.Command(rgBin, args...)
cmd.Dir = searchRoot
out, err := cmd.CombinedOutput()
if err != nil {
if ee, ok := err.(*exec.ExitError); ok && ee.ExitCode() == 1 {
return nil, nil
}
return nil, fmt.Errorf("ripgrep: %w\n%s", err, out)
}
var matches []string
for _, p := range bytes.Split(out, []byte{0}) {
if len(p) == 0 {
continue
}
abs := filepath.Join(searchRoot, string(p))
if skipHidden(abs) {
continue
}
matches = append(matches, abs)
}
sort.SliceStable(matches, func(i, j int) bool {
return len(matches[i]) < len(matches[j])
})
if len(matches) > limit {
matches = matches[:limit]
}
return matches, nil
}
func globWithDoublestar(pattern, searchPath string, limit int) ([]string, bool, error) {
fsys := os.DirFS(searchPath)
relPattern := strings.TrimPrefix(pattern, "/") relPattern := strings.TrimPrefix(pattern, "/")
@@ -158,7 +219,11 @@ func globFiles(pattern, searchPath string, limit int) ([]string, bool, error) {
return nil // Skip files we can't access return nil // Skip files we can't access
} }
absPath := "/" + path // Restore absolute path absPath := path // Restore absolute path
if !strings.HasPrefix(absPath, searchPath) {
absPath = filepath.Join(searchPath, absPath)
}
matches = append(matches, fileInfo{ matches = append(matches, fileInfo{
path: absPath, path: absPath,
modTime: info.ModTime(), modTime: info.ModTime(),
+1 -1
View File
@@ -14,7 +14,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
) )
type GrepParams struct { type GrepParams struct {
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
) )
type LSParams struct { type LSParams struct {
+6 -6
View File
@@ -8,12 +8,12 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/diff" "github.com/opencode-ai/opencode/internal/diff"
"github.com/kujtimiihoxha/opencode/internal/history" "github.com/opencode-ai/opencode/internal/history"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/lsp" "github.com/opencode-ai/opencode/internal/lsp"
"github.com/kujtimiihoxha/opencode/internal/permission" "github.com/opencode-ai/opencode/internal/permission"
) )
type PatchParams struct { type PatchParams struct {
+3 -1
View File
@@ -47,7 +47,9 @@ func GetPersistentShell(workingDir string) *PersistentShell {
shellInstance = newPersistentShell(workingDir) shellInstance = newPersistentShell(workingDir)
}) })
if !shellInstance.isAlive { if shellInstance == nil {
shellInstance = newPersistentShell(workingDir)
} else if !shellInstance.isAlive {
shellInstance = newPersistentShell(shellInstance.cwd) shellInstance = newPersistentShell(shellInstance.cwd)
} }
+2 -2
View File
@@ -10,8 +10,8 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/lsp" "github.com/opencode-ai/opencode/internal/lsp"
) )
type ViewParams struct { type ViewParams struct {
+6 -6
View File
@@ -9,12 +9,12 @@ import (
"strings" "strings"
"time" "time"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/diff" "github.com/opencode-ai/opencode/internal/diff"
"github.com/kujtimiihoxha/opencode/internal/history" "github.com/opencode-ai/opencode/internal/history"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/lsp" "github.com/opencode-ai/opencode/internal/lsp"
"github.com/kujtimiihoxha/opencode/internal/permission" "github.com/opencode-ai/opencode/internal/permission"
) )
type WriteParams struct { type WriteParams struct {
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"time" "time"
"github.com/go-logfmt/logfmt" "github.com/go-logfmt/logfmt"
"github.com/kujtimiihoxha/opencode/internal/pubsub" "github.com/opencode-ai/opencode/internal/pubsub"
) )
const ( const (
+3 -3
View File
@@ -14,9 +14,9 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/lsp/protocol" "github.com/opencode-ai/opencode/internal/lsp/protocol"
) )
type Client struct { type Client struct {
+4 -4
View File
@@ -3,10 +3,10 @@ package lsp
import ( import (
"encoding/json" "encoding/json"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/lsp/protocol" "github.com/opencode-ai/opencode/internal/lsp/protocol"
"github.com/kujtimiihoxha/opencode/internal/lsp/util" "github.com/opencode-ai/opencode/internal/lsp/util"
) )
// Requests // Requests
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/kujtimiihoxha/opencode/internal/lsp/protocol" "github.com/opencode-ai/opencode/internal/lsp/protocol"
) )
func DetectLanguageID(uri string) protocol.LanguageKind { func DetectLanguageID(uri string) protocol.LanguageKind {
+1 -1
View File
@@ -4,7 +4,7 @@ package lsp
import ( import (
"context" "context"
"github.com/kujtimiihoxha/opencode/internal/lsp/protocol" "github.com/opencode-ai/opencode/internal/lsp/protocol"
) )
// Implementation sends a textDocument/implementation request to the LSP server. // Implementation sends a textDocument/implementation request to the LSP server.
+2 -2
View File
@@ -8,8 +8,8 @@ import (
"io" "io"
"strings" "strings"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
) )
// Write writes an LSP message to the given writer // Write writes an LSP message to the given writer
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/kujtimiihoxha/opencode/internal/lsp/protocol" "github.com/opencode-ai/opencode/internal/lsp/protocol"
) )
func applyTextEdits(uri protocol.DocumentUri, edits []protocol.TextEdit) error { func applyTextEdits(uri protocol.DocumentUri, edits []protocol.TextEdit) error {
+40 -36
View File
@@ -11,10 +11,10 @@ import (
"github.com/bmatcuk/doublestar/v4" "github.com/bmatcuk/doublestar/v4"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/lsp" "github.com/opencode-ai/opencode/internal/lsp"
"github.com/kujtimiihoxha/opencode/internal/lsp/protocol" "github.com/opencode-ai/opencode/internal/lsp/protocol"
) )
// WorkspaceWatcher manages LSP file watching // WorkspaceWatcher manages LSP file watching
@@ -96,19 +96,19 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
// Determine server type for specialized handling // Determine server type for specialized handling
serverName := getServerNameFromContext(ctx) serverName := getServerNameFromContext(ctx)
logging.Debug("Server type detected", "serverName", serverName) logging.Debug("Server type detected", "serverName", serverName)
// Check if this server has sent file watchers // Check if this server has sent file watchers
hasFileWatchers := len(watchers) > 0 hasFileWatchers := len(watchers) > 0
// For servers that need file preloading, we'll use a smart approach // For servers that need file preloading, we'll use a smart approach
if shouldPreloadFiles(serverName) || !hasFileWatchers { if shouldPreloadFiles(serverName) || !hasFileWatchers {
go func() { go func() {
startTime := time.Now() startTime := time.Now()
filesOpened := 0 filesOpened := 0
// Determine max files to open based on server type // Determine max files to open based on server type
maxFilesToOpen := 50 // Default conservative limit maxFilesToOpen := 50 // Default conservative limit
switch serverName { switch serverName {
case "typescript", "typescript-language-server", "tsserver", "vtsls": case "typescript", "typescript-language-server", "tsserver", "vtsls":
// TypeScript servers benefit from seeing more files // TypeScript servers benefit from seeing more files
@@ -117,17 +117,17 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
// Java servers need to see many files for project model // Java servers need to see many files for project model
maxFilesToOpen = 200 maxFilesToOpen = 200
} }
// First, open high-priority files // First, open high-priority files
highPriorityFilesOpened := w.openHighPriorityFiles(ctx, serverName) highPriorityFilesOpened := w.openHighPriorityFiles(ctx, serverName)
filesOpened += highPriorityFilesOpened filesOpened += highPriorityFilesOpened
if cnf.DebugLSP { if cnf.DebugLSP {
logging.Debug("Opened high-priority files", logging.Debug("Opened high-priority files",
"count", highPriorityFilesOpened, "count", highPriorityFilesOpened,
"serverName", serverName) "serverName", serverName)
} }
// If we've already opened enough high-priority files, we might not need more // If we've already opened enough high-priority files, we might not need more
if filesOpened >= maxFilesToOpen { if filesOpened >= maxFilesToOpen {
if cnf.DebugLSP { if cnf.DebugLSP {
@@ -137,9 +137,9 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
} }
return return
} }
// For the remaining slots, walk the directory and open matching files // For the remaining slots, walk the directory and open matching files
err := filepath.WalkDir(w.workspacePath, func(path string, d os.DirEntry, err error) error { err := filepath.WalkDir(w.workspacePath, func(path string, d os.DirEntry, err error) error {
if err != nil { if err != nil {
return err return err
@@ -199,10 +199,10 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName string) int { func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName string) int {
cnf := config.Get() cnf := config.Get()
filesOpened := 0 filesOpened := 0
// Define patterns for high-priority files based on server type // Define patterns for high-priority files based on server type
var patterns []string var patterns []string
switch serverName { switch serverName {
case "typescript", "typescript-language-server", "tsserver", "vtsls": case "typescript", "typescript-language-server", "tsserver", "vtsls":
patterns = []string{ patterns = []string{
@@ -256,7 +256,7 @@ func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName
"**/.editorconfig", "**/.editorconfig",
} }
} }
// For each pattern, find and open matching files // For each pattern, find and open matching files
for _, pattern := range patterns { for _, pattern := range patterns {
// Use doublestar.Glob to find files matching the pattern (supports ** patterns) // Use doublestar.Glob to find files matching the pattern (supports ** patterns)
@@ -267,17 +267,17 @@ func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName
} }
continue continue
} }
for _, match := range matches { for _, match := range matches {
// Convert relative path to absolute // Convert relative path to absolute
fullPath := filepath.Join(w.workspacePath, match) fullPath := filepath.Join(w.workspacePath, match)
// Skip directories and excluded files // Skip directories and excluded files
info, err := os.Stat(fullPath) info, err := os.Stat(fullPath)
if err != nil || info.IsDir() || shouldExcludeFile(fullPath) { if err != nil || info.IsDir() || shouldExcludeFile(fullPath) {
continue continue
} }
// Open the file // Open the file
if err := w.client.OpenFile(ctx, fullPath); err != nil { if err := w.client.OpenFile(ctx, fullPath); err != nil {
if cnf.DebugLSP { if cnf.DebugLSP {
@@ -289,17 +289,17 @@ func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName
logging.Debug("Opened high-priority file", "path", fullPath) logging.Debug("Opened high-priority file", "path", fullPath)
} }
} }
// Add a small delay to prevent overwhelming the server // Add a small delay to prevent overwhelming the server
time.Sleep(20 * time.Millisecond) time.Sleep(20 * time.Millisecond)
// Limit the number of files opened per pattern // Limit the number of files opened per pattern
if filesOpened >= 5 && (serverName != "java" && serverName != "jdtls") { if filesOpened >= 5 && (serverName != "java" && serverName != "jdtls") {
break break
} }
} }
} }
return filesOpened return filesOpened
} }
@@ -310,16 +310,16 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
// Store the watcher in the context for later use // Store the watcher in the context for later use
ctx = context.WithValue(ctx, "workspaceWatcher", w) ctx = context.WithValue(ctx, "workspaceWatcher", w)
// If the server name isn't already in the context, try to detect it // If the server name isn't already in the context, try to detect it
if _, ok := ctx.Value("serverName").(string); !ok { if _, ok := ctx.Value("serverName").(string); !ok {
serverName := getServerNameFromContext(ctx) serverName := getServerNameFromContext(ctx)
ctx = context.WithValue(ctx, "serverName", serverName) ctx = context.WithValue(ctx, "serverName", serverName)
} }
serverName := getServerNameFromContext(ctx) serverName := getServerNameFromContext(ctx)
logging.Debug("Starting workspace watcher", "workspacePath", workspacePath, "serverName", serverName) logging.Debug("Starting workspace watcher", "workspacePath", workspacePath, "serverName", serverName)
// Register handler for file watcher registrations from the server // Register handler for file watcher registrations from the server
lsp.RegisterFileWatchHandler(func(id string, watchers []protocol.FileSystemWatcher) { lsp.RegisterFileWatchHandler(func(id string, watchers []protocol.FileSystemWatcher) {
w.AddRegistrations(ctx, id, watchers) w.AddRegistrations(ctx, id, watchers)
@@ -414,7 +414,11 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
case event.Op&fsnotify.Create != 0: case event.Op&fsnotify.Create != 0:
// Already handled earlier in the event loop // Already handled earlier in the event loop
// Just send the notification if needed // Just send the notification if needed
info, _ := os.Stat(event.Name) info, err := os.Stat(event.Name)
if err != nil {
logging.Error("Error getting file info", "path", event.Name, "error", err)
return
}
if !info.IsDir() && watchKind&protocol.WatchCreate != 0 { if !info.IsDir() && watchKind&protocol.WatchCreate != 0 {
w.debounceHandleFileEvent(ctx, uri, protocol.FileChangeType(protocol.Created)) w.debounceHandleFileEvent(ctx, uri, protocol.FileChangeType(protocol.Created))
} }
@@ -682,7 +686,7 @@ func getServerNameFromContext(ctx context.Context) string {
if serverName, ok := ctx.Value("serverName").(string); ok && serverName != "" { if serverName, ok := ctx.Value("serverName").(string); ok && serverName != "" {
return strings.ToLower(serverName) return strings.ToLower(serverName)
} }
// Otherwise, try to extract server name from the client command path // Otherwise, try to extract server name from the client command path
if w, ok := ctx.Value("workspaceWatcher").(*WorkspaceWatcher); ok && w != nil && w.client != nil && w.client.Cmd != nil { if w, ok := ctx.Value("workspaceWatcher").(*WorkspaceWatcher); ok && w != nil && w.client != nil && w.client.Cmd != nil {
path := strings.ToLower(w.client.Cmd.Path) path := strings.ToLower(w.client.Cmd.Path)
@@ -865,7 +869,7 @@ func (w *WorkspaceWatcher) openMatchingFile(ctx context.Context, path string) {
if watched, _ := w.isPathWatched(path); watched { if watched, _ := w.isPathWatched(path); watched {
// Get server name for specialized handling // Get server name for specialized handling
serverName := getServerNameFromContext(ctx) serverName := getServerNameFromContext(ctx)
// Check if the file is a high-priority file that should be opened immediately // Check if the file is a high-priority file that should be opened immediately
// This helps with project initialization for certain language servers // This helps with project initialization for certain language servers
if isHighPriorityFile(path, serverName) { if isHighPriorityFile(path, serverName) {
@@ -881,7 +885,7 @@ func (w *WorkspaceWatcher) openMatchingFile(ctx context.Context, path string) {
// For non-high-priority files, we'll use different strategies based on server type // For non-high-priority files, we'll use different strategies based on server type
if shouldPreloadFiles(serverName) { if shouldPreloadFiles(serverName) {
// For servers that benefit from preloading, open files but with limits // For servers that benefit from preloading, open files but with limits
// Check file size - for preloading we're more conservative // Check file size - for preloading we're more conservative
if info.Size() > (1 * 1024 * 1024) { // 1MB limit for preloaded files if info.Size() > (1 * 1024 * 1024) { // 1MB limit for preloaded files
if cnf.DebugLSP { if cnf.DebugLSP {
@@ -889,13 +893,13 @@ func (w *WorkspaceWatcher) openMatchingFile(ctx context.Context, path string) {
} }
return return
} }
// Check file extension for common source files // Check file extension for common source files
ext := strings.ToLower(filepath.Ext(path)) ext := strings.ToLower(filepath.Ext(path))
// Only preload source files for the specific language // Only preload source files for the specific language
shouldOpen := false shouldOpen := false
switch serverName { switch serverName {
case "typescript", "typescript-language-server", "tsserver", "vtsls": case "typescript", "typescript-language-server", "tsserver", "vtsls":
shouldOpen = ext == ".ts" || ext == ".js" || ext == ".tsx" || ext == ".jsx" shouldOpen = ext == ".ts" || ext == ".js" || ext == ".tsx" || ext == ".jsx"
@@ -913,7 +917,7 @@ func (w *WorkspaceWatcher) openMatchingFile(ctx context.Context, path string) {
// For unknown servers, be conservative // For unknown servers, be conservative
shouldOpen = false shouldOpen = false
} }
if shouldOpen { if shouldOpen {
// Don't need to check if it's already open - the client.OpenFile handles that // Don't need to check if it's already open - the client.OpenFile handles that
if err := w.client.OpenFile(ctx, path); err != nil && cnf.DebugLSP { if err := w.client.OpenFile(ctx, path); err != nil && cnf.DebugLSP {
@@ -943,13 +947,13 @@ func isHighPriorityFile(path string, serverName string) bool {
fileName == "main.js" fileName == "main.js"
case "gopls": case "gopls":
// For Go, we want to open go.mod files immediately // For Go, we want to open go.mod files immediately
return fileName == "go.mod" || return fileName == "go.mod" ||
fileName == "go.sum" || fileName == "go.sum" ||
// Also open main.go files // Also open main.go files
fileName == "main.go" fileName == "main.go"
case "rust-analyzer": case "rust-analyzer":
// For Rust, we want to open Cargo.toml files immediately // For Rust, we want to open Cargo.toml files immediately
return fileName == "Cargo.toml" || return fileName == "Cargo.toml" ||
fileName == "Cargo.lock" || fileName == "Cargo.lock" ||
// Also open lib.rs and main.rs // Also open lib.rs and main.rs
fileName == "lib.rs" || fileName == "lib.rs" ||
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"slices" "slices"
"time" "time"
"github.com/kujtimiihoxha/opencode/internal/llm/models" "github.com/opencode-ai/opencode/internal/llm/models"
) )
type MessageRole string type MessageRole string
+3 -3
View File
@@ -8,9 +8,9 @@ import (
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/kujtimiihoxha/opencode/internal/db" "github.com/opencode-ai/opencode/internal/db"
"github.com/kujtimiihoxha/opencode/internal/llm/models" "github.com/opencode-ai/opencode/internal/llm/models"
"github.com/kujtimiihoxha/opencode/internal/pubsub" "github.com/opencode-ai/opencode/internal/pubsub"
) )
type CreateMessageParams struct { type CreateMessageParams struct {
+4 -9
View File
@@ -5,11 +5,10 @@ import (
"path/filepath" "path/filepath"
"slices" "slices"
"sync" "sync"
"time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/pubsub" "github.com/opencode-ai/opencode/internal/pubsub"
) )
var ErrorPermissionDenied = errors.New("permission denied") var ErrorPermissionDenied = errors.New("permission denied")
@@ -104,12 +103,8 @@ func (s *permissionService) Request(opts CreatePermissionRequest) bool {
s.Publish(pubsub.CreatedEvent, permission) s.Publish(pubsub.CreatedEvent, permission)
// Wait for the response with a timeout // Wait for the response with a timeout
select { resp := <-respCh
case resp := <-respCh: return resp
return resp
case <-time.After(10 * time.Minute):
return false
}
} }
func (s *permissionService) AutoApproveSession(sessionID string) { func (s *permissionService) AutoApproveSession(sessionID string) {
+2 -2
View File
@@ -5,8 +5,8 @@ import (
"database/sql" "database/sql"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/kujtimiihoxha/opencode/internal/db" "github.com/opencode-ai/opencode/internal/db"
"github.com/kujtimiihoxha/opencode/internal/pubsub" "github.com/opencode-ai/opencode/internal/pubsub"
) )
type Session struct { type Session struct {
+5 -5
View File
@@ -6,10 +6,10 @@ import (
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/x/ansi" "github.com/charmbracelet/x/ansi"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/session" "github.com/opencode-ai/opencode/internal/session"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/kujtimiihoxha/opencode/internal/version" "github.com/opencode-ai/opencode/internal/version"
) )
type SendMsg struct { type SendMsg struct {
@@ -92,7 +92,7 @@ func logo(width int) string {
} }
func repo(width int) string { func repo(width int) string {
repo := "https://github.com/kujtimiihoxha/opencode" repo := "https://github.com/opencode-ai/opencode"
return styles.BaseStyle. return styles.BaseStyle.
Foreground(styles.ForgroundDim). Foreground(styles.ForgroundDim).
Width(width). Width(width).
+29 -59
View File
@@ -8,11 +8,11 @@ import (
"github.com/charmbracelet/bubbles/textarea" "github.com/charmbracelet/bubbles/textarea"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/app" "github.com/opencode-ai/opencode/internal/app"
"github.com/kujtimiihoxha/opencode/internal/session" "github.com/opencode-ai/opencode/internal/session"
"github.com/kujtimiihoxha/opencode/internal/tui/layout" "github.com/opencode-ai/opencode/internal/tui/layout"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/kujtimiihoxha/opencode/internal/tui/util" "github.com/opencode-ai/opencode/internal/tui/util"
) )
type editorCmp struct { type editorCmp struct {
@@ -21,12 +21,9 @@ type editorCmp struct {
textarea textarea.Model textarea textarea.Model
} }
type FocusEditorMsg bool type EditorKeyMaps struct {
type focusedEditorKeyMaps struct {
Send key.Binding Send key.Binding
OpenEditor key.Binding OpenEditor key.Binding
Blur key.Binding
} }
type bluredEditorKeyMaps struct { type bluredEditorKeyMaps struct {
@@ -35,29 +32,10 @@ type bluredEditorKeyMaps struct {
OpenEditor key.Binding OpenEditor key.Binding
} }
var focusedKeyMaps = focusedEditorKeyMaps{ var editorMaps = EditorKeyMaps{
Send: key.NewBinding( Send: key.NewBinding(
key.WithKeys("ctrl+s"), key.WithKeys("enter", "ctrl+s"),
key.WithHelp("ctrl+s", "send message"), key.WithHelp("enter", "send message"),
),
Blur: key.NewBinding(
key.WithKeys("esc"),
key.WithHelp("esc", "focus messages"),
),
OpenEditor: key.NewBinding(
key.WithKeys("ctrl+e"),
key.WithHelp("ctrl+e", "open editor"),
),
}
var bluredKeyMaps = bluredEditorKeyMaps{
Send: key.NewBinding(
key.WithKeys("ctrl+s", "enter"),
key.WithHelp("ctrl+s/enter", "send message"),
),
Focus: key.NewBinding(
key.WithKeys("i"),
key.WithHelp("i", "focus editor"),
), ),
OpenEditor: key.NewBinding( OpenEditor: key.NewBinding(
key.WithKeys("ctrl+e"), key.WithKeys("ctrl+e"),
@@ -88,6 +66,9 @@ func openEditor() tea.Cmd {
if err != nil { if err != nil {
return util.ReportError(err) return util.ReportError(err)
} }
if len(content) == 0 {
return util.ReportWarn("Message is empty")
}
os.Remove(tmpfile.Name()) os.Remove(tmpfile.Name())
return SendMsg{ return SendMsg{
Text: string(content), Text: string(content),
@@ -106,7 +87,6 @@ func (m *editorCmp) send() tea.Cmd {
value := m.textarea.Value() value := m.textarea.Value()
m.textarea.Reset() m.textarea.Reset()
m.textarea.Blur()
if value == "" { if value == "" {
return nil return nil
} }
@@ -125,32 +105,28 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.session = msg m.session = msg
} }
return m, nil return m, nil
case FocusEditorMsg:
if msg {
m.textarea.Focus()
return m, tea.Batch(textarea.Blink, util.CmdHandler(EditorFocusMsg(true)))
}
case tea.KeyMsg: case tea.KeyMsg:
if key.Matches(msg, focusedKeyMaps.OpenEditor) { if key.Matches(msg, messageKeys.PageUp) || key.Matches(msg, messageKeys.PageDown) ||
key.Matches(msg, messageKeys.HalfPageUp) || key.Matches(msg, messageKeys.HalfPageDown) {
return m, nil
}
if key.Matches(msg, editorMaps.OpenEditor) {
if m.app.CoderAgent.IsSessionBusy(m.session.ID) { if m.app.CoderAgent.IsSessionBusy(m.session.ID) {
return m, util.ReportWarn("Agent is working, please wait...") return m, util.ReportWarn("Agent is working, please wait...")
} }
return m, openEditor() return m, openEditor()
} }
// if the key does not match any binding, return // Handle Enter key
if m.textarea.Focused() && key.Matches(msg, focusedKeyMaps.Send) { if m.textarea.Focused() && key.Matches(msg, editorMaps.Send) {
return m, m.send() value := m.textarea.Value()
} if len(value) > 0 && value[len(value)-1] == '\\' {
if !m.textarea.Focused() && key.Matches(msg, bluredKeyMaps.Send) { // If the last character is a backslash, remove it and add a newline
return m, m.send() m.textarea.SetValue(value[:len(value)-1] + "\n")
} return m, nil
if m.textarea.Focused() && key.Matches(msg, focusedKeyMaps.Blur) { } else {
m.textarea.Blur() // Otherwise, send the message
return m, util.CmdHandler(EditorFocusMsg(false)) return m, m.send()
} }
if !m.textarea.Focused() && key.Matches(msg, bluredKeyMaps.Focus) {
m.textarea.Focus()
return m, tea.Batch(textarea.Blink, util.CmdHandler(EditorFocusMsg(true)))
} }
} }
m.textarea, cmd = m.textarea.Update(msg) m.textarea, cmd = m.textarea.Update(msg)
@@ -175,13 +151,7 @@ func (m *editorCmp) GetSize() (int, int) {
func (m *editorCmp) BindingKeys() []key.Binding { func (m *editorCmp) BindingKeys() []key.Binding {
bindings := []key.Binding{} bindings := []key.Binding{}
if m.textarea.Focused() { bindings = append(bindings, layout.KeyMapToSlice(editorMaps)...)
bindings = append(bindings, layout.KeyMapToSlice(focusedKeyMaps)...)
} else {
bindings = append(bindings, layout.KeyMapToSlice(bluredKeyMaps)...)
}
bindings = append(bindings, layout.KeyMapToSlice(m.textarea.KeyMap)...)
return bindings return bindings
} }
+60 -27
View File
@@ -10,13 +10,12 @@ import (
"github.com/charmbracelet/bubbles/viewport" "github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/app" "github.com/opencode-ai/opencode/internal/app"
"github.com/kujtimiihoxha/opencode/internal/message" "github.com/opencode-ai/opencode/internal/message"
"github.com/kujtimiihoxha/opencode/internal/pubsub" "github.com/opencode-ai/opencode/internal/pubsub"
"github.com/kujtimiihoxha/opencode/internal/session" "github.com/opencode-ai/opencode/internal/session"
"github.com/kujtimiihoxha/opencode/internal/tui/layout" "github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/util"
"github.com/kujtimiihoxha/opencode/internal/tui/util"
) )
type cacheItem struct { type cacheItem struct {
@@ -26,7 +25,6 @@ type cacheItem struct {
type messagesCmp struct { type messagesCmp struct {
app *app.App app *app.App
width, height int width, height int
writingMode bool
viewport viewport.Model viewport viewport.Model
session session.Session session session.Session
messages []message.Message messages []message.Message
@@ -38,6 +36,32 @@ type messagesCmp struct {
} }
type renderFinishedMsg struct{} type renderFinishedMsg struct{}
type MessageKeys struct {
PageDown key.Binding
PageUp key.Binding
HalfPageUp key.Binding
HalfPageDown key.Binding
}
var messageKeys = MessageKeys{
PageDown: key.NewBinding(
key.WithKeys("pgdown"),
key.WithHelp("f/pgdn", "page down"),
),
PageUp: key.NewBinding(
key.WithKeys("pgup"),
key.WithHelp("b/pgup", "page up"),
),
HalfPageUp: key.NewBinding(
key.WithKeys("ctrl+u"),
key.WithHelp("ctrl+u", "½ page up"),
),
HalfPageDown: key.NewBinding(
key.WithKeys("ctrl+d", "ctrl+d"),
key.WithHelp("ctrl+d", "½ page down"),
),
}
func (m *messagesCmp) Init() tea.Cmd { func (m *messagesCmp) Init() tea.Cmd {
return tea.Batch(m.viewport.Init(), m.spinner.Tick) return tea.Batch(m.viewport.Init(), m.spinner.Tick)
} }
@@ -45,8 +69,7 @@ func (m *messagesCmp) Init() tea.Cmd {
func (m *messagesCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m *messagesCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd var cmds []tea.Cmd
switch msg := msg.(type) { switch msg := msg.(type) {
case EditorFocusMsg:
m.writingMode = bool(msg)
case SessionSelectedMsg: case SessionSelectedMsg:
if msg.ID != m.session.ID { if msg.ID != m.session.ID {
cmd := m.SetSession(msg) cmd := m.SetSession(msg)
@@ -60,13 +83,17 @@ func (m *messagesCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.rendering = false m.rendering = false
return m, nil return m, nil
case tea.KeyMsg:
if key.Matches(msg, messageKeys.PageUp) || key.Matches(msg, messageKeys.PageDown) ||
key.Matches(msg, messageKeys.HalfPageUp) || key.Matches(msg, messageKeys.HalfPageDown) {
u, cmd := m.viewport.Update(msg)
m.viewport = u
cmds = append(cmds, cmd)
}
case renderFinishedMsg: case renderFinishedMsg:
m.rendering = false m.rendering = false
m.viewport.GotoBottom() m.viewport.GotoBottom()
case tea.KeyMsg:
if m.writingMode {
return m, nil
}
case pubsub.Event[message.Message]: case pubsub.Event[message.Message]:
needsRerender := false needsRerender := false
if msg.Type == pubsub.CreatedEvent { if msg.Type == pubsub.CreatedEvent {
@@ -122,10 +149,6 @@ func (m *messagesCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
} }
} }
u, cmd := m.viewport.Update(msg)
m.viewport = u
cmds = append(cmds, cmd)
spinner, cmd := m.spinner.Update(msg) spinner, cmd := m.spinner.Update(msg)
m.spinner = spinner m.spinner = spinner
cmds = append(cmds, cmd) cmds = append(cmds, cmd)
@@ -326,22 +349,24 @@ func (m *messagesCmp) working() string {
func (m *messagesCmp) help() string { func (m *messagesCmp) help() string {
text := "" text := ""
if m.writingMode { if m.app.CoderAgent.IsBusy() {
text += lipgloss.JoinHorizontal( text += lipgloss.JoinHorizontal(
lipgloss.Left, lipgloss.Left,
styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render("press "), styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render("press "),
styles.BaseStyle.Foreground(styles.Forground).Bold(true).Render("esc"), styles.BaseStyle.Foreground(styles.Forground).Bold(true).Render("esc"),
styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render(" to exit writing mode"), styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render(" to exit cancel"),
) )
} else { } else {
text += lipgloss.JoinHorizontal( text += lipgloss.JoinHorizontal(
lipgloss.Left, lipgloss.Left,
styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render("press "), styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render("press "),
styles.BaseStyle.Foreground(styles.Forground).Bold(true).Render("i"), styles.BaseStyle.Foreground(styles.Forground).Bold(true).Render("enter"),
styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render(" to start writing"), styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render(" to send the message,"),
styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render(" write"),
styles.BaseStyle.Foreground(styles.Forground).Bold(true).Render(" \\"),
styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render(" and enter to add a new line"),
) )
} }
return styles.BaseStyle. return styles.BaseStyle.
Width(m.width). Width(m.width).
Render(text) Render(text)
@@ -398,18 +423,26 @@ func (m *messagesCmp) SetSession(session session.Session) tea.Cmd {
} }
func (m *messagesCmp) BindingKeys() []key.Binding { func (m *messagesCmp) BindingKeys() []key.Binding {
bindings := layout.KeyMapToSlice(m.viewport.KeyMap) return []key.Binding{
return bindings m.viewport.KeyMap.PageDown,
m.viewport.KeyMap.PageUp,
m.viewport.KeyMap.HalfPageUp,
m.viewport.KeyMap.HalfPageDown,
}
} }
func NewMessagesCmp(app *app.App) tea.Model { func NewMessagesCmp(app *app.App) tea.Model {
s := spinner.New() s := spinner.New()
s.Spinner = spinner.Pulse s.Spinner = spinner.Pulse
vp := viewport.New(0, 0)
vp.KeyMap.PageUp = messageKeys.PageUp
vp.KeyMap.PageDown = messageKeys.PageDown
vp.KeyMap.HalfPageUp = messageKeys.HalfPageUp
vp.KeyMap.HalfPageDown = messageKeys.HalfPageDown
return &messagesCmp{ return &messagesCmp{
app: app, app: app,
writingMode: true,
cachedContent: make(map[string]cacheItem), cachedContent: make(map[string]cacheItem),
viewport: viewport.New(0, 0), viewport: vp,
spinner: s, spinner: s,
} }
} }
+9 -9
View File
@@ -12,13 +12,13 @@ import (
"github.com/charmbracelet/glamour" "github.com/charmbracelet/glamour"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/x/ansi" "github.com/charmbracelet/x/ansi"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/diff" "github.com/opencode-ai/opencode/internal/diff"
"github.com/kujtimiihoxha/opencode/internal/llm/agent" "github.com/opencode-ai/opencode/internal/llm/agent"
"github.com/kujtimiihoxha/opencode/internal/llm/models" "github.com/opencode-ai/opencode/internal/llm/models"
"github.com/kujtimiihoxha/opencode/internal/llm/tools" "github.com/opencode-ai/opencode/internal/llm/tools"
"github.com/kujtimiihoxha/opencode/internal/message" "github.com/opencode-ai/opencode/internal/message"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
) )
type uiMessageType int type uiMessageType int
@@ -28,7 +28,7 @@ const (
assistantMessageType assistantMessageType
toolMessageType toolMessageType
maxResultHeight = 15 maxResultHeight = 10
) )
var diffStyle = diff.NewStyleConfig(diff.WithShowHeader(false), diff.WithShowHunkHeader(false)) var diffStyle = diff.NewStyleConfig(diff.WithShowHeader(false), diff.WithShowHunkHeader(false))
@@ -148,7 +148,7 @@ func renderAssistantMessage(
content = "*Finished without output*" content = "*Finished without output*"
} }
content = renderMessage(content, false, msg.ID == focusedUIMessageId, width, info...) content = renderMessage(content, false, true, width, info...)
messages = append(messages, uiMessage{ messages = append(messages, uiMessage{
ID: msg.ID, ID: msg.ID,
messageType: assistantMessageType, messageType: assistantMessageType,
+15 -10
View File
@@ -8,12 +8,12 @@ import (
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/diff" "github.com/opencode-ai/opencode/internal/diff"
"github.com/kujtimiihoxha/opencode/internal/history" "github.com/opencode-ai/opencode/internal/history"
"github.com/kujtimiihoxha/opencode/internal/pubsub" "github.com/opencode-ai/opencode/internal/pubsub"
"github.com/kujtimiihoxha/opencode/internal/session" "github.com/opencode-ai/opencode/internal/session"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
) )
type sidebarCmp struct { type sidebarCmp struct {
@@ -116,13 +116,18 @@ func (m *sidebarCmp) sessionSection() string {
func (m *sidebarCmp) modifiedFile(filePath string, additions, removals int) string { func (m *sidebarCmp) modifiedFile(filePath string, additions, removals int) string {
stats := "" stats := ""
if additions > 0 && removals > 0 { if additions > 0 && removals > 0 {
stats = styles.BaseStyle.Foreground(styles.ForgroundDim).Render(fmt.Sprintf(" %d additions and %d removals", additions, removals)) additions := styles.BaseStyle.Foreground(styles.Green).PaddingLeft(1).Render(fmt.Sprintf("+%d", additions))
removals := styles.BaseStyle.Foreground(styles.Red).PaddingLeft(1).Render(fmt.Sprintf("-%d", removals))
content := lipgloss.JoinHorizontal(lipgloss.Left, additions, removals)
stats = styles.BaseStyle.Width(lipgloss.Width(content)).Render(content)
} else if additions > 0 { } else if additions > 0 {
stats = styles.BaseStyle.Foreground(styles.ForgroundDim).Render(fmt.Sprintf(" %d additions", additions)) additions := fmt.Sprintf(" %s", styles.BaseStyle.PaddingLeft(1).Foreground(styles.Green).Render(fmt.Sprintf("+%d", additions)))
stats = styles.BaseStyle.Width(lipgloss.Width(additions)).Render(additions)
} else if removals > 0 { } else if removals > 0 {
stats = styles.BaseStyle.Foreground(styles.ForgroundDim).Render(fmt.Sprintf(" %d removals", removals)) removals := fmt.Sprintf(" %s", styles.BaseStyle.PaddingLeft(1).Foreground(styles.Red).Render(fmt.Sprintf("-%d", removals)))
stats = styles.BaseStyle.Width(lipgloss.Width(removals)).Render(removals)
} }
filePathStr := styles.BaseStyle.Foreground(styles.Forground).Render(filePath) filePathStr := styles.BaseStyle.Render(filePath)
return styles.BaseStyle. return styles.BaseStyle.
Width(m.width). Width(m.width).
+9 -9
View File
@@ -7,15 +7,15 @@ import (
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/llm/models" "github.com/opencode-ai/opencode/internal/llm/models"
"github.com/kujtimiihoxha/opencode/internal/lsp" "github.com/opencode-ai/opencode/internal/lsp"
"github.com/kujtimiihoxha/opencode/internal/lsp/protocol" "github.com/opencode-ai/opencode/internal/lsp/protocol"
"github.com/kujtimiihoxha/opencode/internal/pubsub" "github.com/opencode-ai/opencode/internal/pubsub"
"github.com/kujtimiihoxha/opencode/internal/session" "github.com/opencode-ai/opencode/internal/session"
"github.com/kujtimiihoxha/opencode/internal/tui/components/chat" "github.com/opencode-ai/opencode/internal/tui/components/chat"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/kujtimiihoxha/opencode/internal/tui/util" "github.com/opencode-ai/opencode/internal/tui/util"
) )
type StatusCmp interface { type StatusCmp interface {
+3 -5
View File
@@ -4,9 +4,9 @@ import (
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/tui/layout" "github.com/opencode-ai/opencode/internal/tui/layout"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/kujtimiihoxha/opencode/internal/tui/util" "github.com/opencode-ai/opencode/internal/tui/util"
) )
// Command represents a command that can be executed // Command represents a command that can be executed
@@ -190,7 +190,6 @@ func (c *commandDialogCmp) View() string {
styles.BaseStyle.Width(maxWidth).Render(""), styles.BaseStyle.Width(maxWidth).Render(""),
styles.BaseStyle.Width(maxWidth).Render(lipgloss.JoinVertical(lipgloss.Left, commandItems...)), styles.BaseStyle.Width(maxWidth).Render(lipgloss.JoinVertical(lipgloss.Left, commandItems...)),
styles.BaseStyle.Width(maxWidth).Render(""), styles.BaseStyle.Width(maxWidth).Render(""),
styles.BaseStyle.Width(maxWidth).Padding(0, 1).Foreground(styles.ForgroundDim).Render("↑/k: up ↓/j: down enter: select esc: cancel"),
) )
return styles.BaseStyle.Padding(1, 2). return styles.BaseStyle.Padding(1, 2).
@@ -244,4 +243,3 @@ func NewCommandDialogCmp() CommandDialog {
selectedCommandID: "", selectedCommandID: "",
} }
} }
+2 -2
View File
@@ -6,7 +6,7 @@ import (
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
) )
type helpCmp struct { type helpCmp struct {
@@ -62,7 +62,7 @@ func (h *helpCmp) render() string {
var ( var (
pairs []string pairs []string
width int width int
rows = 14 - 2 rows = 10 - 2
) )
for i := 0; i < len(bindings); i += rows { for i := 0; i < len(bindings); i += rows {
var ( var (
+5 -11
View File
@@ -5,8 +5,8 @@ import (
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/kujtimiihoxha/opencode/internal/tui/util" "github.com/opencode-ai/opencode/internal/tui/util"
) )
// InitDialogCmp is a component that asks the user if they want to initialize the project. // InitDialogCmp is a component that asks the user if they want to initialize the project.
@@ -46,8 +46,8 @@ func (k initDialogKeyMap) ShortHelp() []key.Binding {
key.WithHelp("enter", "confirm"), key.WithHelp("enter", "confirm"),
), ),
key.NewBinding( key.NewBinding(
key.WithKeys("esc"), key.WithKeys("esc", "q"),
key.WithHelp("esc", "cancel"), key.WithHelp("esc/q", "cancel"),
), ),
key.NewBinding( key.NewBinding(
key.WithKeys("y", "n"), key.WithKeys("y", "n"),
@@ -114,6 +114,7 @@ func (m InitDialogCmp) View() string {
Padding(1, 1). Padding(1, 1).
Render("Would you like to initialize this project?") Render("Would you like to initialize this project?")
maxWidth = min(maxWidth, m.width-10)
yesStyle := styles.BaseStyle yesStyle := styles.BaseStyle
noStyle := styles.BaseStyle noStyle := styles.BaseStyle
@@ -144,12 +145,6 @@ func (m InitDialogCmp) View() string {
Padding(1, 0). Padding(1, 0).
Render(buttons) Render(buttons)
help := styles.BaseStyle.
Width(maxWidth).
Padding(0, 1).
Foreground(styles.ForgroundDim).
Render("tab/←/→: toggle y/n: yes/no enter: confirm esc: cancel")
content := lipgloss.JoinVertical( content := lipgloss.JoinVertical(
lipgloss.Left, lipgloss.Left,
title, title,
@@ -158,7 +153,6 @@ func (m InitDialogCmp) View() string {
question, question,
buttons, buttons,
styles.BaseStyle.Width(maxWidth).Render(""), styles.BaseStyle.Width(maxWidth).Render(""),
help,
) )
return styles.BaseStyle.Padding(1, 2). return styles.BaseStyle.Padding(1, 2).
+10 -14
View File
@@ -9,12 +9,12 @@ import (
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/glamour" "github.com/charmbracelet/glamour"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/diff" "github.com/opencode-ai/opencode/internal/diff"
"github.com/kujtimiihoxha/opencode/internal/llm/tools" "github.com/opencode-ai/opencode/internal/llm/tools"
"github.com/kujtimiihoxha/opencode/internal/permission" "github.com/opencode-ai/opencode/internal/permission"
"github.com/kujtimiihoxha/opencode/internal/tui/layout" "github.com/opencode-ai/opencode/internal/tui/layout"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/kujtimiihoxha/opencode/internal/tui/util" "github.com/opencode-ai/opencode/internal/tui/util"
) )
type PermissionAction string type PermissionAction string
@@ -67,8 +67,8 @@ var permissionsKeys = permissionsMapping{
key.WithHelp("a", "allow"), key.WithHelp("a", "allow"),
), ),
AllowSession: key.NewBinding( AllowSession: key.NewBinding(
key.WithKeys("A"), key.WithKeys("s"),
key.WithHelp("A", "allow for session"), key.WithHelp("s", "allow for session"),
), ),
Deny: key.NewBinding( Deny: key.NewBinding(
key.WithKeys("d"), key.WithKeys("d"),
@@ -171,7 +171,7 @@ func (p *permissionDialogCmp) renderButtons() string {
} }
allowButton := allowStyle.Padding(0, 1).Render("Allow (a)") allowButton := allowStyle.Padding(0, 1).Render("Allow (a)")
allowSessionButton := allowSessionStyle.Padding(0, 1).Render("Allow for session (A)") allowSessionButton := allowSessionStyle.Padding(0, 1).Render("Allow for session (s)")
denyButton := denyStyle.Padding(0, 1).Render("Deny (d)") denyButton := denyStyle.Padding(0, 1).Render("Deny (d)")
content := lipgloss.JoinHorizontal( content := lipgloss.JoinHorizontal(
@@ -375,9 +375,6 @@ func (p *permissionDialogCmp) render() string {
contentFinal = p.renderDefaultContent() contentFinal = p.renderDefaultContent()
} }
// Add help text
helpText := styles.BaseStyle.Width(p.width - 4).Padding(0, 1).Foreground(styles.ForgroundDim).Render("←/→/tab: switch options a: allow A: allow for session d: deny enter/space: confirm")
content := lipgloss.JoinVertical( content := lipgloss.JoinVertical(
lipgloss.Top, lipgloss.Top,
title, title,
@@ -385,8 +382,7 @@ func (p *permissionDialogCmp) render() string {
headerContent, headerContent,
contentFinal, contentFinal,
buttons, buttons,
styles.BaseStyle.Render(strings.Repeat(" ", p.width - 4)), styles.BaseStyle.Render(strings.Repeat(" ", p.width-4)),
helpText,
) )
return styles.BaseStyle. return styles.BaseStyle.
+3 -3
View File
@@ -6,9 +6,9 @@ import (
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/tui/layout" "github.com/opencode-ai/opencode/internal/tui/layout"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/kujtimiihoxha/opencode/internal/tui/util" "github.com/opencode-ai/opencode/internal/tui/util"
) )
const question = "Are you sure you want to quit?" const question = "Are you sure you want to quit?"
+6 -6
View File
@@ -4,10 +4,10 @@ import (
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/session" "github.com/opencode-ai/opencode/internal/session"
"github.com/kujtimiihoxha/opencode/internal/tui/layout" "github.com/opencode-ai/opencode/internal/tui/layout"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/kujtimiihoxha/opencode/internal/tui/util" "github.com/opencode-ai/opencode/internal/tui/util"
) )
// SessionSelectedMsg is sent when a session is selected // SessionSelectedMsg is sent when a session is selected
@@ -122,6 +122,8 @@ func (s *sessionDialogCmp) View() string {
} }
} }
maxWidth = max(30, min(maxWidth, s.width-15)) // Limit width to avoid overflow
// Limit height to avoid taking up too much screen space // Limit height to avoid taking up too much screen space
maxVisibleSessions := min(10, len(s.sessions)) maxVisibleSessions := min(10, len(s.sessions))
@@ -169,7 +171,6 @@ func (s *sessionDialogCmp) View() string {
styles.BaseStyle.Width(maxWidth).Render(""), styles.BaseStyle.Width(maxWidth).Render(""),
styles.BaseStyle.Width(maxWidth).Render(lipgloss.JoinVertical(lipgloss.Left, sessionItems...)), styles.BaseStyle.Width(maxWidth).Render(lipgloss.JoinVertical(lipgloss.Left, sessionItems...)),
styles.BaseStyle.Width(maxWidth).Render(""), styles.BaseStyle.Width(maxWidth).Render(""),
styles.BaseStyle.Width(maxWidth).Padding(0, 1).Foreground(styles.ForgroundDim).Render("↑/k: up ↓/j: down enter: select esc: cancel"),
) )
return styles.BaseStyle.Padding(1, 2). return styles.BaseStyle.Padding(1, 2).
@@ -223,4 +224,3 @@ func NewSessionDialogCmp() SessionDialog {
selectedSessionID: "", selectedSessionID: "",
} }
} }
+3 -3
View File
@@ -9,9 +9,9 @@ import (
"github.com/charmbracelet/bubbles/viewport" "github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/tui/layout" "github.com/opencode-ai/opencode/internal/tui/layout"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
) )
type DetailComponent interface { type DetailComponent interface {
+5 -5
View File
@@ -7,11 +7,11 @@ import (
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/table" "github.com/charmbracelet/bubbles/table"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/pubsub" "github.com/opencode-ai/opencode/internal/pubsub"
"github.com/kujtimiihoxha/opencode/internal/tui/layout" "github.com/opencode-ai/opencode/internal/tui/layout"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/kujtimiihoxha/opencode/internal/tui/util" "github.com/opencode-ai/opencode/internal/tui/util"
) )
type TableComponent interface { type TableComponent interface {
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
) )
type Container interface { type Container interface {
+4 -40
View File
@@ -1,16 +1,15 @@
package layout package layout
import ( import (
"bytes"
"strings" "strings"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" chAnsi "github.com/charmbracelet/x/ansi"
"github.com/kujtimiihoxha/opencode/internal/tui/util"
"github.com/mattn/go-runewidth"
"github.com/muesli/ansi" "github.com/muesli/ansi"
"github.com/muesli/reflow/truncate" "github.com/muesli/reflow/truncate"
"github.com/muesli/termenv" "github.com/muesli/termenv"
"github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/opencode-ai/opencode/internal/tui/util"
) )
// Most of this code is borrowed from // Most of this code is borrowed from
@@ -117,42 +116,7 @@ func PlaceOverlay(
// cutLeft cuts printable characters from the left. // cutLeft cuts printable characters from the left.
// This function is heavily based on muesli's ansi and truncate packages. // This function is heavily based on muesli's ansi and truncate packages.
func cutLeft(s string, cutWidth int) string { func cutLeft(s string, cutWidth int) string {
var ( return chAnsi.Cut(s, cutWidth, lipgloss.Width(s))
pos int
isAnsi bool
ab bytes.Buffer
b bytes.Buffer
)
for _, c := range s {
var w int
if c == ansi.Marker || isAnsi {
isAnsi = true
ab.WriteRune(c)
if ansi.IsTerminator(c) {
isAnsi = false
if bytes.HasSuffix(ab.Bytes(), []byte("[0m")) {
ab.Reset()
}
}
} else {
w = runewidth.RuneWidth(c)
}
if pos >= cutWidth {
if b.Len() == 0 {
if ab.Len() > 0 {
b.Write(ab.Bytes())
}
if pos-cutWidth > 1 {
b.WriteByte(' ')
continue
}
}
b.WriteRune(c)
}
pos += w
}
return b.String()
} }
func max(a, b int) int { func max(a, b int) int {
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
) )
type SplitPaneLayout interface { type SplitPaneLayout interface {
+16 -24
View File
@@ -5,22 +5,21 @@ import (
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/kujtimiihoxha/opencode/internal/app" "github.com/opencode-ai/opencode/internal/app"
"github.com/kujtimiihoxha/opencode/internal/session" "github.com/opencode-ai/opencode/internal/session"
"github.com/kujtimiihoxha/opencode/internal/tui/components/chat" "github.com/opencode-ai/opencode/internal/tui/components/chat"
"github.com/kujtimiihoxha/opencode/internal/tui/layout" "github.com/opencode-ai/opencode/internal/tui/layout"
"github.com/kujtimiihoxha/opencode/internal/tui/util" "github.com/opencode-ai/opencode/internal/tui/util"
) )
var ChatPage PageID = "chat" var ChatPage PageID = "chat"
type chatPage struct { type chatPage struct {
app *app.App app *app.App
editor layout.Container editor layout.Container
messages layout.Container messages layout.Container
layout layout.SplitPaneLayout layout layout.SplitPaneLayout
session session.Session session session.Session
editingMode bool
} }
type ChatKeyMap struct { type ChatKeyMap struct {
@@ -34,8 +33,8 @@ var keyMap = ChatKeyMap{
key.WithHelp("ctrl+n", "new session"), key.WithHelp("ctrl+n", "new session"),
), ),
Cancel: key.NewBinding( Cancel: key.NewBinding(
key.WithKeys("ctrl+x"), key.WithKeys("esc"),
key.WithHelp("ctrl+x", "cancel"), key.WithHelp("esc", "cancel"),
), ),
} }
@@ -65,8 +64,6 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
} }
} }
p.session = msg p.session = msg
case chat.EditorFocusMsg:
p.editingMode = bool(msg)
case tea.KeyMsg: case tea.KeyMsg:
switch { switch {
case key.Matches(msg, keyMap.NewSession): case key.Matches(msg, keyMap.NewSession):
@@ -136,11 +133,7 @@ func (p *chatPage) View() string {
func (p *chatPage) BindingKeys() []key.Binding { func (p *chatPage) BindingKeys() []key.Binding {
bindings := layout.KeyMapToSlice(keyMap) bindings := layout.KeyMapToSlice(keyMap)
if p.editingMode { bindings = append(bindings, p.messages.BindingKeys()...)
bindings = append(bindings, p.editor.BindingKeys()...)
} else {
bindings = append(bindings, p.messages.BindingKeys()...)
}
return bindings return bindings
} }
@@ -155,10 +148,9 @@ func NewChatPage(app *app.App) tea.Model {
layout.WithBorder(true, false, false, false), layout.WithBorder(true, false, false, false),
) )
return &chatPage{ return &chatPage{
app: app, app: app,
editor: editorContainer, editor: editorContainer,
messages: messagesContainer, messages: messagesContainer,
editingMode: true,
layout: layout.NewSplitPane( layout: layout.NewSplitPane(
layout.WithLeftPanel(messagesContainer), layout.WithLeftPanel(messagesContainer),
layout.WithBottomPanel(editorContainer), layout.WithBottomPanel(editorContainer),
+3 -3
View File
@@ -4,9 +4,9 @@ import (
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/tui/components/logs" "github.com/opencode-ai/opencode/internal/tui/components/logs"
"github.com/kujtimiihoxha/opencode/internal/tui/layout" "github.com/opencode-ai/opencode/internal/tui/layout"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
) )
var LogsPage PageID = "logs" var LogsPage PageID = "logs"
+16 -22
View File
@@ -6,17 +6,17 @@ import (
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/app" "github.com/opencode-ai/opencode/internal/app"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/permission" "github.com/opencode-ai/opencode/internal/permission"
"github.com/kujtimiihoxha/opencode/internal/pubsub" "github.com/opencode-ai/opencode/internal/pubsub"
"github.com/kujtimiihoxha/opencode/internal/tui/components/chat" "github.com/opencode-ai/opencode/internal/tui/components/chat"
"github.com/kujtimiihoxha/opencode/internal/tui/components/core" "github.com/opencode-ai/opencode/internal/tui/components/core"
"github.com/kujtimiihoxha/opencode/internal/tui/components/dialog" "github.com/opencode-ai/opencode/internal/tui/components/dialog"
"github.com/kujtimiihoxha/opencode/internal/tui/layout" "github.com/opencode-ai/opencode/internal/tui/layout"
"github.com/kujtimiihoxha/opencode/internal/tui/page" "github.com/opencode-ai/opencode/internal/tui/page"
"github.com/kujtimiihoxha/opencode/internal/tui/util" "github.com/opencode-ai/opencode/internal/tui/util"
) )
type keyMap struct { type keyMap struct {
@@ -30,7 +30,7 @@ type keyMap struct {
var keys = keyMap{ var keys = keyMap{
Logs: key.NewBinding( Logs: key.NewBinding(
key.WithKeys("ctrl+l"), key.WithKeys("ctrl+l"),
key.WithHelp("ctrl+L", "logs"), key.WithHelp("ctrl+l", "logs"),
), ),
Quit: key.NewBinding( Quit: key.NewBinding(
@@ -49,7 +49,7 @@ var keys = keyMap{
Commands: key.NewBinding( Commands: key.NewBinding(
key.WithKeys("ctrl+k"), key.WithKeys("ctrl+k"),
key.WithHelp("ctrl+K", "commands"), key.WithHelp("ctrl+k", "commands"),
), ),
} }
@@ -95,8 +95,6 @@ type appModel struct {
showInitDialog bool showInitDialog bool
initDialog dialog.InitDialogCmp initDialog dialog.InitDialogCmp
editingMode bool
} }
func (a appModel) Init() tea.Cmd { func (a appModel) Init() tea.Cmd {
@@ -164,8 +162,6 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
a.initDialog.SetSize(msg.Width, msg.Height) a.initDialog.SetSize(msg.Width, msg.Height)
return a, tea.Batch(cmds...) return a, tea.Batch(cmds...)
case chat.EditorFocusMsg:
a.editingMode = bool(msg)
// Status // Status
case util.InfoMsg: case util.InfoMsg:
s, cmd := a.status.Update(msg) s, cmd := a.status.Update(msg)
@@ -228,7 +224,6 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
a.app.Permissions.GrantPersistant(msg.Permission) a.app.Permissions.GrantPersistant(msg.Permission)
case dialog.PermissionDeny: case dialog.PermissionDeny:
a.app.Permissions.Deny(msg.Permission) a.app.Permissions.Deny(msg.Permission)
cmd = util.CmdHandler(chat.FocusEditorMsg(true))
} }
a.showPermissions = false a.showPermissions = false
return a, cmd return a, cmd
@@ -360,7 +355,7 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
a.showHelp = !a.showHelp a.showHelp = !a.showHelp
return a, nil return a, nil
case key.Matches(msg, helpEsc): case key.Matches(msg, helpEsc):
if !a.editingMode { if a.app.CoderAgent.IsBusy() {
if a.showQuit { if a.showQuit {
return a, nil return a, nil
} }
@@ -477,7 +472,7 @@ func (a appModel) View() string {
) )
} }
if a.editingMode { if !a.app.CoderAgent.IsBusy() {
a.status.SetHelpMsg("ctrl+? help") a.status.SetHelpMsg("ctrl+? help")
} else { } else {
a.status.SetHelpMsg("? help") a.status.SetHelpMsg("? help")
@@ -494,7 +489,7 @@ func (a appModel) View() string {
if a.currentPage == page.LogsPage { if a.currentPage == page.LogsPage {
bindings = append(bindings, logsKeyReturnKey) bindings = append(bindings, logsKeyReturnKey)
} }
if !a.editingMode { if !a.app.CoderAgent.IsBusy() {
bindings = append(bindings, helpEsc) bindings = append(bindings, helpEsc)
} }
a.help.SetBindings(bindings) a.help.SetBindings(bindings)
@@ -585,7 +580,6 @@ func New(app *app.App) tea.Model {
permissions: dialog.NewPermissionDialogCmp(), permissions: dialog.NewPermissionDialogCmp(),
initDialog: dialog.NewInitDialogCmp(), initDialog: dialog.NewInitDialogCmp(),
app: app, app: app,
editingMode: true,
commands: []dialog.Command{}, commands: []dialog.Command{},
pages: map[page.PageID]tea.Model{ pages: map[page.PageID]tea.Model{
page.ChatPage: page.NewChatPage(app), page.ChatPage: page.NewChatPage(app),
+1 -1
View File
@@ -5,7 +5,7 @@ import "runtime/debug"
// Build-time parameters set via -ldflags // Build-time parameters set via -ldflags
var Version = "unknown" var Version = "unknown"
// A user may install pug using `go install github.com/kujtimiihoxha/opencode@latest`. // A user may install pug using `go install github.com/opencode-ai/opencode@latest`.
// without -ldflags, in which case the version above is unset. As a workaround // without -ldflags, in which case the version above is unset. As a workaround
// we use the embedded build version that *is* set when using `go install` (and // we use the embedded build version that *is* set when using `go install` (and
// is only set for `go install` and not for `go build`). // is only set for `go install` and not for `go build`).
+2 -2
View File
@@ -1,8 +1,8 @@
package main package main
import ( import (
"github.com/kujtimiihoxha/opencode/cmd" "github.com/opencode-ai/opencode/cmd"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
) )
func main() { func main() {
+64 -34
View File
@@ -12,28 +12,33 @@
"model": { "model": {
"description": "Model ID for the agent", "description": "Model ID for the agent",
"enum": [ "enum": [
"gemini-2.0-flash",
"bedrock.claude-3.7-sonnet", "bedrock.claude-3.7-sonnet",
"claude-3-opus", "claude-3-haiku",
"claude-3.5-sonnet", "claude-3.7-sonnet",
"gpt-4o-mini", "claude-3.5-haiku",
"o1", "o3",
"o3-mini", "gpt-4.5-preview",
"o1-pro", "o1-pro",
"o4-mini", "o4-mini",
"claude-3-haiku",
"gpt-4o",
"o3",
"gpt-4.1-mini",
"gpt-4.5-preview",
"gemini-2.5-flash",
"claude-3.5-haiku",
"gpt-4.1", "gpt-4.1",
"gemini-2.0-flash-lite", "o3-mini",
"claude-3.7-sonnet",
"o1-mini",
"gpt-4.1-nano", "gpt-4.1-nano",
"gemini-2.5" "gpt-4o-mini",
"o1",
"gemini-2.5-flash",
"qwen-qwq",
"meta-llama/llama-4-maverick-17b-128e-instruct",
"claude-3-opus",
"gpt-4o",
"gemini-2.0-flash-lite",
"gemini-2.0-flash",
"deepseek-r1-distill-llama-70b",
"llama-3.3-70b-versatile",
"claude-3.5-sonnet",
"o1-mini",
"gpt-4.1-mini",
"gemini-2.5",
"meta-llama/llama-4-scout-17b-16e-instruct"
], ],
"type": "string" "type": "string"
}, },
@@ -67,28 +72,33 @@
"model": { "model": {
"description": "Model ID for the agent", "description": "Model ID for the agent",
"enum": [ "enum": [
"gemini-2.0-flash",
"bedrock.claude-3.7-sonnet", "bedrock.claude-3.7-sonnet",
"claude-3-opus", "claude-3-haiku",
"claude-3.5-sonnet", "claude-3.7-sonnet",
"gpt-4o-mini", "claude-3.5-haiku",
"o1", "o3",
"o3-mini", "gpt-4.5-preview",
"o1-pro", "o1-pro",
"o4-mini", "o4-mini",
"claude-3-haiku",
"gpt-4o",
"o3",
"gpt-4.1-mini",
"gpt-4.5-preview",
"gemini-2.5-flash",
"claude-3.5-haiku",
"gpt-4.1", "gpt-4.1",
"gemini-2.0-flash-lite", "o3-mini",
"claude-3.7-sonnet",
"o1-mini",
"gpt-4.1-nano", "gpt-4.1-nano",
"gemini-2.5" "gpt-4o-mini",
"o1",
"gemini-2.5-flash",
"qwen-qwq",
"meta-llama/llama-4-maverick-17b-128e-instruct",
"claude-3-opus",
"gpt-4o",
"gemini-2.0-flash-lite",
"gemini-2.0-flash",
"deepseek-r1-distill-llama-70b",
"llama-3.3-70b-versatile",
"claude-3.5-sonnet",
"o1-mini",
"gpt-4.1-mini",
"gemini-2.5",
"meta-llama/llama-4-scout-17b-16e-instruct"
], ],
"type": "string" "type": "string"
}, },
@@ -121,6 +131,26 @@
}, },
"type": "object" "type": "object"
}, },
"contextPaths": {
"default": [
".github/copilot-instructions.md",
".cursorrules",
".cursor/rules/",
"CLAUDE.md",
"CLAUDE.local.md",
"opencode.md",
"opencode.local.md",
"OpenCode.md",
"OpenCode.local.md",
"OPENCODE.md",
"OPENCODE.local.md"
],
"description": "Context paths for the application",
"items": {
"type": "string"
},
"type": "array"
},
"data": { "data": {
"description": "Storage configuration", "description": "Storage configuration",
"properties": { "properties": {
-4
View File
@@ -1,7 +1,3 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -e set -e
export DOCKER_PUSH=true
./platform/scripts/build
goreleaser build --clean --snapshot --skip validate goreleaser build --clean --snapshot --skip validate
cd sdk/js
bun run release