diff --git a/internal/config/config.go b/internal/config/config.go index a6c72fab2b5fe103c2f6d9c475d851435a466724..4d0012b66f6caab7ade4f205803c2443870360d9 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -299,7 +299,7 @@ func UpdatePreferredModel(modelType SelectedModelType, model SelectedModel) erro } func (c *Config) SetConfigField(key string, value any) error { - configPath := globalConfigData() + configPath := GlobalConfigData() // read the data data, err := os.ReadFile(configPath) if err != nil { diff --git a/internal/config/init.go b/internal/config/init.go index 4c8176e40e8a012824f49e3558d7e47e52d1d3a2..12b30efd75f88d438e0734571cbb5c634ba231bc 100644 --- a/internal/config/init.go +++ b/internal/config/init.go @@ -105,7 +105,7 @@ func MarkProjectInitialized() error { } func HasInitialDataConfig() bool { - cfgPath := globalConfigData() + cfgPath := GlobalConfigData() if _, err := os.Stat(cfgPath); err != nil { return false } diff --git a/internal/config/load.go b/internal/config/load.go index c39b896d72270d2bec12b5ca90c1d6ae7eee435c..1864a36c95bd6adcf3959f34cdf3958201312a94 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -37,7 +37,7 @@ func Load(workingDir string, debug bool) (*Config, error) { // uses default config paths configPaths := []string{ globalConfig(), - globalConfigData(), + GlobalConfigData(), filepath.Join(workingDir, fmt.Sprintf("%s.json", appName)), filepath.Join(workingDir, fmt.Sprintf(".%s.json", appName)), } @@ -510,9 +510,9 @@ func globalConfig() string { return filepath.Join(os.Getenv("HOME"), ".config", appName, fmt.Sprintf("%s.json", appName)) } -// globalConfigData returns the path to the main data directory for the application. +// GlobalConfigData returns the path to the main data directory for the application. // this config is used when the app overrides configurations instead of updating the global config. -func globalConfigData() string { +func GlobalConfigData() string { xdgDataHome := os.Getenv("XDG_DATA_HOME") if xdgDataHome != "" { return filepath.Join(xdgDataHome, appName, fmt.Sprintf("%s.json", appName)) @@ -531,3 +531,14 @@ func globalConfigData() string { return filepath.Join(os.Getenv("HOME"), ".local", "share", appName, fmt.Sprintf("%s.json", appName)) } + +func HomeDir() string { + homeDir := os.Getenv("HOME") + if homeDir == "" { + homeDir = os.Getenv("USERPROFILE") // For Windows compatibility + } + if homeDir == "" { + homeDir = os.Getenv("HOMEPATH") // Fallback for some environments + } + return homeDir +} diff --git a/internal/tui/components/dialogs/models/apikey.go b/internal/tui/components/dialogs/models/apikey.go new file mode 100644 index 0000000000000000000000000000000000000000..860e7e3a0fd406997d6b76a781acf9e909404557 --- /dev/null +++ b/internal/tui/components/dialogs/models/apikey.go @@ -0,0 +1,88 @@ +package models + +import ( + "fmt" + "strings" + + "github.com/charmbracelet/bubbles/v2/textinput" + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/crush/internal/config" + "github.com/charmbracelet/crush/internal/tui/styles" + "github.com/charmbracelet/lipgloss/v2" +) + +type APIKeyInput struct { + input textinput.Model + width int + height int +} + +func NewAPIKeyInput() *APIKeyInput { + t := styles.CurrentTheme() + + ti := textinput.New() + ti.Placeholder = "Enter your API key..." + ti.SetWidth(50) + ti.SetVirtualCursor(false) + ti.Prompt = "> " + ti.SetStyles(t.S().TextInput) + ti.Focus() + + return &APIKeyInput{ + input: ti, + width: 60, + } +} + +func (a *APIKeyInput) Init() tea.Cmd { + return textinput.Blink +} + +func (a *APIKeyInput) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + a.width = msg.Width + a.height = msg.Height + } + + var cmd tea.Cmd + a.input, cmd = a.input.Update(msg) + return a, cmd +} + +func (a *APIKeyInput) View() tea.View { + t := styles.CurrentTheme() + + title := t.S().Base. + Foreground(t.Secondary). + Bold(true). + Render("Enter your Anthropic API Key") + + inputView := a.input.View() + + dataPath := config.GlobalConfigData() + dataPath = strings.Replace(dataPath, config.HomeDir(), "~", 1) + helpText := t.S().Muted. + Render(fmt.Sprintf("This will be written to the global configuration: %s", dataPath)) + + content := lipgloss.JoinVertical( + lipgloss.Left, + title, + "", + inputView, + "", + helpText, + ) + + view := tea.NewView(content) + cursor := a.input.Cursor() + if cursor != nil { + cursor.Y += 2 // Adjust for title and spacing + } + view.SetCursor(cursor) + return view +} + +func (a *APIKeyInput) Value() string { + return a.input.Value() +}