diff --git a/cmd/substreams/enhanced_input.go b/cmd/substreams/enhanced_input.go new file mode 100644 index 000000000..7c64c2b43 --- /dev/null +++ b/cmd/substreams/enhanced_input.go @@ -0,0 +1,136 @@ +package main + +import ( + "fmt" + "regexp" + "strings" + + "github.com/charmbracelet/bubbles/textinput" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +// EnhancedInput is a custom input component with history and tab completion +type EnhancedInput struct { + textInput textinput.Model + history *InputHistory + prompt string + description string + validation *regexp.Regexp + validationErr string + submitted bool + cancelled bool +} + +// NewEnhancedInput creates a new enhanced input component +func NewEnhancedInput(prompt, description, placeholder, defaultValue string, validationRegex, validationError string) *EnhancedInput { + ti := textinput.New() + ti.Placeholder = placeholder + ti.SetValue(defaultValue) + ti.Focus() + + var validationRE *regexp.Regexp + if validationRegex != "" { + var err error + validationRE, err = regexp.Compile(validationRegex) + if err != nil { + // Log error but continue without validation + validationRE = nil + } + } + + return &EnhancedInput{ + textInput: ti, + history: globalInputHistory, + prompt: prompt, + description: description, + validation: validationRE, + validationErr: validationError, + } +} + +// Init implements tea.Model +func (e *EnhancedInput) Init() tea.Cmd { + return textinput.Blink +} + +// Update implements tea.Model +func (e *EnhancedInput) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "ctrl+c", "esc": + e.cancelled = true + return e, tea.Quit + + case "enter": + value := strings.TrimRight(e.textInput.Value(), " ") + + // Validate if validation is set + if e.validation != nil && !e.validation.MatchString(value) { + // Show validation error (could use a status message) + return e, nil + } + + e.submitted = true + e.history.Add(value) + e.history.Reset() + return e, tea.Quit + + case "up": + if value, changed := e.history.NavigateUp(e.textInput.Value()); changed { + e.textInput.SetValue(value) + // Move cursor to end + e.textInput.CursorEnd() + } + return e, nil + + case "down": + if value, changed := e.history.NavigateDown(e.textInput.Value()); changed { + e.textInput.SetValue(value) + e.textInput.CursorEnd() + } + return e, nil + + // Note: Tab completion removed - use FilePicker for file paths instead + } + } + + e.textInput, cmd = e.textInput.Update(msg) + return e, cmd +} + +// View implements tea.Model +func (e *EnhancedInput) View() string { + titleStyle := lipgloss.NewStyle().Bold(true) + descStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("240")) + + view := "" + if e.prompt != "" { + view += titleStyle.Render(e.prompt) + "\n" + } + if e.description != "" { + view += descStyle.Render(e.description) + "\n" + } + view += e.textInput.View() + "\n" + + return view +} + +// Run executes the enhanced input and returns the value and any error +func (e *EnhancedInput) Run() (string, error) { + p := tea.NewProgram(e) + finalModel, err := p.Run() + if err != nil { + return "", err + } + + finalInput := finalModel.(*EnhancedInput) + if finalInput.cancelled { + return "", fmt.Errorf("input cancelled") + } + + return finalInput.textInput.Value(), nil +} diff --git a/cmd/substreams/init.go b/cmd/substreams/init.go index 2cd5e232d..1eed4209f 100644 --- a/cmd/substreams/init.go +++ b/cmd/substreams/init.go @@ -85,6 +85,13 @@ func init() { var INIT_TRACE = false var WITH_ACCESSIBLE = false +var globalInputHistory *InputHistory + +func init() { + // Initialize input history tracker + globalInputHistory = NewInputHistory() +} + type initStateFormat struct { GeneratorID string `json:"generator"` State json.RawMessage `json:"state"` @@ -98,6 +105,83 @@ func newUserState() *UserState { return &UserState{} } +// isFilePathInput uses heuristics to determine if an input is requesting a file path +func isFilePathInput(prompt, description, placeholder string) bool { + combined := strings.ToLower(prompt + " " + description + " " + placeholder) + + // File extensions (strong indicators) + fileExtensions := []string{".json", ".sol", ".yaml", ".yml", ".abi", ".txt", ".csv", ".md"} + for _, ext := range fileExtensions { + if strings.Contains(combined, ext) { + return true + } + } + + // Word-boundary file keywords (avoid matching within words like "profile") + words := strings.Fields(combined) + fileKeywords := []string{"file", "path", "directory", "folder", "filepath", "dirpath"} + for _, word := range words { + for _, keyword := range fileKeywords { + if word == keyword { + return true + } + } + } + + // Multi-word file indicators + contractFileKeywords := []string{"contract file", "abi file", "config file", "source file"} + for _, keyword := range contractFileKeywords { + if strings.Contains(combined, keyword) { + return true + } + } + + return false +} + +// runFilePicker runs the huh FilePicker component +func runFilePicker(prompt, description, defaultPath string) (string, error) { + // Determine starting directory + startDir := "." + if defaultPath != "" { + if stat, err := os.Stat(defaultPath); err == nil && stat.IsDir() { + startDir = defaultPath + } else { + startDir = filepath.Dir(defaultPath) + } + } + + var selectedPath string + + // Create FilePicker using huh API + picker := huh.NewFilePicker(). + Title(prompt). + Description(description). + CurrentDirectory(startDir). + Value(&selectedPath) + + err := huh.NewForm(huh.NewGroup(picker)).WithTheme(huhTheme).WithAccessible(WITH_ACCESSIBLE).Run() + if err != nil { + return "", err + } + + return selectedPath, nil +} + +// runEnhancedTextInput runs enhanced text input with history +func runEnhancedTextInput(prompt, description, placeholder, defaultValue, validationRegex, validationError string) (string, error) { + enhancedInput := NewEnhancedInput( + prompt, + description, + placeholder, + defaultValue, + validationRegex, + validationError, + ) + + return enhancedInput.Run() +} + func readGeneratorState(stateFile string) (*initStateFormat, error) { stateBytes, err := os.ReadFile(stateFile) if err != nil { @@ -434,37 +518,40 @@ func runSubstreamsInitE(cmd *cobra.Command, args []string) error { case *pbconvo.SystemOutput_TextInput_: input := msg.TextInput - returnValue := input.DefaultValue + // Check if this input is requesting a file path + isFilePath := isFilePathInput(input.Prompt, input.Description, input.Placeholder) - inputField := huh.NewInput(). - Title(input.Prompt). - Description(input.Description). - Placeholder(input.Placeholder). - Value(&returnValue) + var returnValue string + var err error - if input.ValidationRegexp != "" { - validationRE, err := regexp.Compile(input.ValidationRegexp) + if isFilePath { + // Use FilePicker for file path inputs + returnValue, err = runFilePicker(input.Prompt, input.Description, input.DefaultValue) if err != nil { - return fmt.Errorf("invalid regexp received from server (%q) to validate text input: %w", msg.TextInput.ValidationRegexp, err) + return fmt.Errorf("failed taking file input: %w", err) + } + } else { + // Use enhanced text input with history for other inputs + returnValue, err = runEnhancedTextInput( + input.Prompt, + input.Description, + input.Placeholder, + input.DefaultValue, + input.ValidationRegexp, + input.ValidationErrorMessage, + ) + if err != nil { + return fmt.Errorf("failed taking text input: %w", err) } - - inputField.Validate(func(userInput string) error { - matched := validationRE.MatchString(strings.TrimRight(returnValue, " ")) - if !matched { - return errors.New(input.ValidationErrorMessage) - } - return nil - }) - } - - err := huh.NewForm(huh.NewGroup(inputField)).WithTheme(huhTheme).WithAccessible(WITH_ACCESSIBLE).Run() - if err != nil { - return fmt.Errorf("failed taking input: %w", err) } fmt.Println(gray("┃"), input.Prompt+":", bold(returnValue)) fmt.Println("") + // Record in history + globalInputHistory.Add(returnValue) + + // Send to server if err := sendFunc(&pbconvo.UserInput{ FromActionId: resp.ActionId, Entry: &pbconvo.UserInput_TextInput_{ diff --git a/cmd/substreams/init_file_detection_test.go b/cmd/substreams/init_file_detection_test.go new file mode 100644 index 000000000..1dc1844d9 --- /dev/null +++ b/cmd/substreams/init_file_detection_test.go @@ -0,0 +1,144 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsFilePathInput(t *testing.T) { + tests := []struct { + name string + prompt string + description string + placeholder string + expected bool + }{ + { + name: "ABI file prompt", + prompt: "Enter ABI file path", + description: "Path to the contract ABI file", + placeholder: "/path/to/contract.json", + expected: true, + }, + { + name: "Contract file prompt", + prompt: "Contract source file", + description: "Solidity contract file", + placeholder: "Contract.sol", + expected: true, + }, + { + name: "Directory prompt", + prompt: "Output directory", + description: "Where to save the generated files", + placeholder: "./output", + expected: true, + }, + { + name: "YAML config file", + prompt: "Configuration file", + description: "YAML configuration file path", + placeholder: "config.yaml", + expected: true, + }, + { + name: "JSON file", + prompt: "Data file", + description: "JSON data file", + placeholder: "data.json", + expected: true, + }, + { + name: "Folder path", + prompt: "Source folder", + description: "Folder containing source files", + placeholder: "./src", + expected: true, + }, + { + name: "Regular text input", + prompt: "Enter your name", + description: "Your full name", + placeholder: "John Doe", + expected: false, + }, + { + name: "Network name", + prompt: "Network", + description: "Blockchain network name", + placeholder: "ethereum", + expected: false, + }, + { + name: "Contract address", + prompt: "Contract address", + description: "Ethereum contract address", + placeholder: "0x1234...", + expected: false, + }, + { + name: "Block number", + prompt: "Starting block", + description: "Block number to start from", + placeholder: "12345", + expected: false, + }, + { + name: "Empty inputs", + prompt: "", + description: "", + placeholder: "", + expected: false, + }, + { + name: "Case insensitive file detection", + prompt: "Enter FILE Path", + description: "PATH to the file", + placeholder: "", + expected: true, + }, + { + name: "Path in description only", + prompt: "Configuration", + description: "Enter the path to your config", + placeholder: "", + expected: true, + }, + { + name: "File extension in placeholder", + prompt: "Config", + description: "Configuration", + placeholder: "example.yaml", + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := isFilePathInput(tt.prompt, tt.description, tt.placeholder) + assert.Equal(t, tt.expected, result, + "Expected %v for prompt='%s', description='%s', placeholder='%s'", + tt.expected, tt.prompt, tt.description, tt.placeholder) + }) + } +} + +func TestIsFilePathInput_EdgeCases(t *testing.T) { + // Test with keywords as part of larger words + assert.False(t, isFilePathInput("Configuration", "Configure the system", "config-value")) + + // Test with file-like but not file keywords + assert.False(t, isFilePathInput("Profile", "User profile information", "user-profile")) + + // Test with multiple file keywords + assert.True(t, isFilePathInput("ABI file path", "Contract ABI file directory", "contract.json")) + + // Test with partial matches + assert.True(t, isFilePathInput("filepath", "directory path", "")) + assert.True(t, isFilePathInput("", "", "filename.sol")) + + // Test contract-specific cases + assert.True(t, isFilePathInput("Contract file", "Select contract file", "")) + assert.False(t, isFilePathInput("Contract address", "Enter contract address", "0x123")) +} diff --git a/cmd/substreams/init_input_history.go b/cmd/substreams/init_input_history.go new file mode 100644 index 000000000..cefe06dd6 --- /dev/null +++ b/cmd/substreams/init_input_history.go @@ -0,0 +1,91 @@ +package main + +import ( + "sync" +) + +// InputHistory manages a session-based history of text inputs +type InputHistory struct { + mu sync.RWMutex + entries []string + cursor int // Current position in history (-1 means not navigating) + current string // Stores the text user was typing before navigating history +} + +// NewInputHistory creates a new input history tracker +func NewInputHistory() *InputHistory { + return &InputHistory{ + entries: make([]string, 0, 50), // Pre-allocate for efficiency + cursor: -1, + } +} + +// Add records a new input to history (called after user submits input) +func (h *InputHistory) Add(input string) { + h.mu.Lock() + defer h.mu.Unlock() + + // Don't add empty inputs or duplicates of the last entry + if input == "" || (len(h.entries) > 0 && h.entries[len(h.entries)-1] == input) { + return + } + + h.entries = append(h.entries, input) + h.cursor = -1 // Reset navigation state +} + +// NavigateUp returns the previous entry in history +// Returns (value, changed) where changed indicates if navigation occurred +func (h *InputHistory) NavigateUp(currentValue string) (string, bool) { + h.mu.Lock() + defer h.mu.Unlock() + + if len(h.entries) == 0 { + return currentValue, false + } + + // First time navigating, save current input + if h.cursor == -1 { + h.current = currentValue + h.cursor = len(h.entries) - 1 + return h.entries[h.cursor], true + } + + // Already navigating, go further back + if h.cursor > 0 { + h.cursor-- + return h.entries[h.cursor], true + } + + // At the oldest entry, no change + return h.entries[h.cursor], false +} + +// NavigateDown returns the next entry in history +// Returns (value, changed) where changed indicates if navigation occurred +func (h *InputHistory) NavigateDown(currentValue string) (string, bool) { + h.mu.Lock() + defer h.mu.Unlock() + + if h.cursor == -1 { + // Not currently navigating history + return currentValue, false + } + + if h.cursor < len(h.entries)-1 { + h.cursor++ + return h.entries[h.cursor], true + } + + // Reached the end, return to user's current input + h.cursor = -1 + return h.current, true +} + +// Reset clears navigation state (call when input is submitted or cancelled) +func (h *InputHistory) Reset() { + h.mu.Lock() + defer h.mu.Unlock() + h.cursor = -1 + h.current = "" +} diff --git a/cmd/substreams/init_input_history_test.go b/cmd/substreams/init_input_history_test.go new file mode 100644 index 000000000..610130f71 --- /dev/null +++ b/cmd/substreams/init_input_history_test.go @@ -0,0 +1,133 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestInputHistory_Add(t *testing.T) { + history := NewInputHistory() + + history.Add("first") + history.Add("second") + history.Add("second") // Duplicate should not be added + history.Add("") // Empty should not be added + + assert.Len(t, history.entries, 2) + assert.Equal(t, "first", history.entries[0]) + assert.Equal(t, "second", history.entries[1]) +} + +func TestInputHistory_NavigateUp(t *testing.T) { + history := NewInputHistory() + history.Add("first") + history.Add("second") + history.Add("third") + + // First up should return most recent + value, changed := history.NavigateUp("current") + require.True(t, changed) + assert.Equal(t, "third", value) + + // Second up should return previous + value, changed = history.NavigateUp("current") + require.True(t, changed) + assert.Equal(t, "second", value) + + // Third up should return first + value, changed = history.NavigateUp("current") + require.True(t, changed) + assert.Equal(t, "first", value) + + // Fourth up should stay at first + value, changed = history.NavigateUp("current") + assert.False(t, changed) + assert.Equal(t, "first", value) +} + +func TestInputHistory_NavigateDown(t *testing.T) { + history := NewInputHistory() + history.Add("first") + history.Add("second") + history.Add("third") + + // Navigate up to start + history.NavigateUp("current") + history.NavigateUp("current") + history.NavigateUp("current") + + // Now navigate down + value, changed := history.NavigateDown("current") + require.True(t, changed) + assert.Equal(t, "second", value) + + value, changed = history.NavigateDown("current") + require.True(t, changed) + assert.Equal(t, "third", value) + + // At the end, should return original current value + value, changed = history.NavigateDown("current") + require.True(t, changed) + assert.Equal(t, "current", value) + + // Further down should not change + value, changed = history.NavigateDown("current") + assert.False(t, changed) +} + +func TestInputHistory_Empty(t *testing.T) { + history := NewInputHistory() + + value, changed := history.NavigateUp("test") + assert.False(t, changed) + assert.Equal(t, "test", value) +} + +func TestInputHistory_Reset(t *testing.T) { + history := NewInputHistory() + history.Add("first") + history.Add("second") + + // Navigate up to set cursor + history.NavigateUp("current") + assert.Equal(t, 1, history.cursor) + assert.Equal(t, "current", history.current) + + // Reset should clear navigation state + history.Reset() + assert.Equal(t, -1, history.cursor) + assert.Equal(t, "", history.current) +} + +func TestInputHistory_ThreadSafety(t *testing.T) { + history := NewInputHistory() + + // Test concurrent access + done := make(chan bool, 2) + + go func() { + for i := 0; i < 100; i++ { + history.Add("test") + history.NavigateUp("current") + history.NavigateDown("current") + } + done <- true + }() + + go func() { + for i := 0; i < 100; i++ { + history.Add("test2") + history.NavigateUp("current2") + history.NavigateDown("current2") + } + done <- true + }() + + <-done + <-done + + // Should not panic and should have some entries + assert.True(t, len(history.entries) > 0) +} diff --git a/go.mod b/go.mod index 2895e7c27..6f9a2ee3c 100644 --- a/go.mod +++ b/go.mod @@ -39,13 +39,13 @@ require ( github.com/bobg/go-generics/v2 v2.2.2 github.com/bytecodealliance/wasmtime-go/v36 v36.0.0 github.com/cenkalti/backoff/v4 v4.2.1 - github.com/charmbracelet/bubbles v0.20.0 - github.com/charmbracelet/bubbletea v1.1.0 + github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 + github.com/charmbracelet/bubbletea v1.3.10 github.com/charmbracelet/glamour v0.7.0 - github.com/charmbracelet/huh v0.6.0 - github.com/charmbracelet/huh/spinner v0.0.0-20240806005253-b7436a76999a - github.com/charmbracelet/lipgloss v1.0.0 - github.com/charmbracelet/x/ansi v0.4.2 + github.com/charmbracelet/huh v0.8.0 + github.com/charmbracelet/huh/spinner v0.0.0-20251215014908-6f7d32faaff3 + github.com/charmbracelet/lipgloss v1.1.0 + github.com/charmbracelet/x/ansi v0.10.1 github.com/docker/cli v29.0.4+incompatible github.com/dustin/go-humanize v1.0.1 github.com/golang-cz/textcase v1.2.1 @@ -56,7 +56,7 @@ require ( github.com/mattn/go-isatty v0.0.20 github.com/mitchellh/go-testing-interface v1.14.1 github.com/muesli/reflow v0.3.0 - github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a + github.com/muesli/termenv v0.16.0 github.com/orcaman/concurrent-map/v2 v2.0.1 github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 @@ -99,12 +99,14 @@ require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0 // indirect github.com/alecthomas/chroma/v2 v2.8.0 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/bits-and-blooms/bitset v1.12.0 // indirect + github.com/bits-and-blooms/bitset v1.22.0 // indirect github.com/bobg/go-generics/v3 v3.5.0 // indirect - github.com/catppuccin/go v0.2.0 // indirect + github.com/catppuccin/go v0.3.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect + github.com/charmbracelet/x/cellbuf v0.0.13 // indirect github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect - github.com/charmbracelet/x/term v0.2.0 // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect github.com/envoyproxy/go-control-plane v0.14.0 // indirect github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect @@ -128,6 +130,7 @@ require ( github.com/streamingfast/validator v0.0.0-20231124184318-71ec8080e4ae // indirect github.com/thedevsaddam/govalidator v1.9.6 // indirect github.com/tidwall/match v1.1.1 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect diff --git a/go.sum b/go.sum index d16b01b33..a6dfe8505 100644 --- a/go.sum +++ b/go.sum @@ -128,15 +128,16 @@ github.com/aws/aws-sdk-go v1.49.6 h1:yNldzF5kzLBRvKlKz1S0bkvc2+04R1kt13KfBWQBfFA github.com/aws/aws-sdk-go v1.49.6/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= -github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= +github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY= +github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bits-and-blooms/bitset v1.12.0 h1:U/q1fAF7xXRhFCrhROzIfffYnu+dlS38vCZtmFVPHmA= github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4= +github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc= github.com/blendle/zapdriver v1.3.2-0.20200203083823-9200777f8a3d h1:fSlGu5ePbkjBidXuj2O5j9EcYrVB5Cr6/wdkYyDgxZk= github.com/blendle/zapdriver v1.3.2-0.20200203083823-9200777f8a3d/go.mod h1:yCBkgASmKHgUOFjK9h1sOytUVgA+JkQjqj3xYP4AdWY= @@ -150,8 +151,8 @@ github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZ github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/bytecodealliance/wasmtime-go/v36 v36.0.0 h1:AEapAro5dYIryZtIfS29QonBXAblVMCKEPH6fs7fOEE= github.com/bytecodealliance/wasmtime-go/v36 v36.0.0/go.mod h1:wysMJXQcKNOgby61jR/8+7VDuQfId27t9FrUQRwDdVo= -github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA= -github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc= +github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY= +github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= @@ -159,26 +160,38 @@ github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F9 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= -github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= -github.com/charmbracelet/bubbletea v1.1.0 h1:FjAl9eAL3HBCHenhz/ZPjkKdScmaS5SK69JAK2YJK9c= -github.com/charmbracelet/bubbletea v1.1.0/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4= +github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 h1:JFgG/xnwFfbezlUnFMJy0nusZvytYysV4SCS2cYbvws= +github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7/go.mod h1:ISC1gtLcVilLOf23wvTfoQuYbW2q0JevFxPfUzZ9Ybw= +github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= +github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= github.com/charmbracelet/glamour v0.7.0 h1:2BtKGZ4iVJCDfMF229EzbeR1QRKLWztO9dMtjmqZSng= github.com/charmbracelet/glamour v0.7.0/go.mod h1:jUMh5MeihljJPQbJ/wf4ldw2+yBP59+ctV36jASy7ps= -github.com/charmbracelet/huh v0.6.0 h1:mZM8VvZGuE0hoDXq6XLxRtgfWyTI3b2jZNKh0xWmax8= -github.com/charmbracelet/huh v0.6.0/go.mod h1:GGNKeWCeNzKpEOh/OJD8WBwTQjV3prFAtQPpLv+AVwU= -github.com/charmbracelet/huh/spinner v0.0.0-20240806005253-b7436a76999a h1:SnIdR+7ApTLK5Dc3DKQf6GGbb3TsnNAKtp183DhZyp8= -github.com/charmbracelet/huh/spinner v0.0.0-20240806005253-b7436a76999a/go.mod h1:9VssyY5pUozMRmDYlLYV20QMMcA2sHg3qnaB6PvdIm8= -github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= -github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo= -github.com/charmbracelet/x/ansi v0.4.2 h1:0JM6Aj/g/KC154/gOP4vfxun0ff6itogDYk41kof+qk= -github.com/charmbracelet/x/ansi v0.4.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= -github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b h1:MnAMdlwSltxJyULnrYbkZpp4k58Co7Tah3ciKhSNo0Q= -github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= +github.com/charmbracelet/huh v0.8.0 h1:Xz/Pm2h64cXQZn/Jvele4J3r7DDiqFCNIVteYukxDvY= +github.com/charmbracelet/huh v0.8.0/go.mod h1:5YVc+SlZ1IhQALxRPpkGwwEKftN/+OlJlnJYlDRFqN4= +github.com/charmbracelet/huh/spinner v0.0.0-20251215014908-6f7d32faaff3 h1:KUeWGoKnmyrLaDIa0smE6pK5eFMZWNIxPGweQR12iLg= +github.com/charmbracelet/huh/spinner v0.0.0-20251215014908-6f7d32faaff3/go.mod h1:OMqKat/mm9a/qOnpuNOPyYO9bPzRNnmzLnRZT5KYltg= +github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= +github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= +github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= +github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= +github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= +github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U= +github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ= +github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA= +github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0= +github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= +github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4= github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ= -github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0= -github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0= +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/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY= +github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= +github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI= +github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.0 h1:+eqR0HfOetur4tgnC8ftU5imRnhi4te+BadWS95c5AM= github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= @@ -195,6 +208,8 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0= github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -434,8 +449,8 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 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/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg= -github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ= +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/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -590,6 +605,8 @@ github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhso github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9/go.mod h1:q+QjxYvZ+fpjMXqs+XEriussHjSYqeXVnAdSV1tkMYk= +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/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=