@@ -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 {
@@ -105,7 +105,7 @@ func MarkProjectInitialized() error {
}
func HasInitialDataConfig() bool {
- cfgPath := globalConfigData()
+ cfgPath := GlobalConfigData()
if _, err := os.Stat(cfgPath); err != nil {
return false
}
@@ -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
+}
@@ -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()
+}