@@ -200,6 +200,28 @@ var (
203+func readConfigFile(path string) (*Config, error) {
205+ if _, err := os.Stat(path); err != nil && !os.IsNotExist(err) {
206+ // some other error occurred while checking the file
208+ } else if err == nil {
209+ // config file exists, read it
210+ file, err := os.ReadFile(path)
215+ if err := json.Unmarshal(file, cfg); err != nil {
219+ // config file does not exist, create a new one
203func loadConfig(cwd string, debug bool) (*Config, error) {
225func loadConfig(cwd string, debug bool) (*Config, error) {
204 // First read the global config file
226 // First read the global config file
205 cfgPath := ConfigPath()
227 cfgPath := ConfigPath()
@@ -248,47 +270,29 @@ func loadConfig(cwd string, debug bool) (*Config, error) {
249 slog.SetDefault(logger)
271 slog.SetDefault(logger)
251− var globalCfg *Config
273+
252− if _, err := os.Stat(cfgPath); err != nil && !os.IsNotExist(err) {
274+ priorityOrderedConfigFiles := []string{
253− // some other error occurred while checking the file
255− } else if err == nil {
256− // config file exists, read it
275+ cfgPath, // Global config file
257− file, err := os.ReadFile(cfgPath)
276+ filepath.Join(cwd, "crush.json"), // Local config file
258− if err != nil {
277+ filepath.Join(cwd, ".crush.json"), // Local config file
261− globalCfg = &Config{}
262− if err := json.Unmarshal(file, globalCfg); err != nil {
266− // config file does not exist, create a new one
267− globalCfg = &Config{}
270− var localConfig *Config
280+ configs := make([]*Config, 0)
271− // Global config loaded, now read the local config file
281+ for _, path := range priorityOrderedConfigFiles {
272− localConfigPath := filepath.Join(cwd, "crush.json")
282+ localConfig, err := readConfigFile(path)
273− if _, err := os.Stat(localConfigPath); err != nil && !os.IsNotExist(err) {
274− // some other error occurred while checking the file
276− } else if err == nil {
277− // local config file exists, read it
278− file, err := os.ReadFile(localConfigPath)
279 if err != nil {
283 if err != nil {
280− return nil, err
284+ return nil, fmt.Errorf("failed to read config file %s: %w", path, err)
282− localConfig = &Config{}
286+ if localConfig != nil {
283− if err := json.Unmarshal(file, localConfig); err != nil {
287+ // If the config file was read successfully, add it to the list
284− return nil, err
288+ configs = append(configs, localConfig)
288 // merge options
292 // merge options
289− mergeOptions(cfg, globalCfg, localConfig)
293+ mergeOptions(cfg, configs...)
291− mergeProviderConfigs(cfg, globalCfg, localConfig)
295+ mergeProviderConfigs(cfg, configs...)
292 // no providers found the app is not initialized yet
296 // no providers found the app is not initialized yet
293 if len(cfg.Providers) == 0 {
297 if len(cfg.Providers) == 0 {
294 return cfg, nil
298 return cfg, nil
@@ -310,7 +314,7 @@ func loadConfig(cwd string, debug bool) (*Config, error) {
310 cfg.Models = PreferredModels{}
314 cfg.Models = PreferredModels{}
313− mergeModels(cfg, globalCfg, localConfig)
317+ mergeModels(cfg, configs...)
315 agents := map[AgentID]Agent{
319 agents := map[AgentID]Agent{
316 AgentCoder: {
320 AgentCoder: {
@@ -340,9 +344,9 @@ func loadConfig(cwd string, debug bool) (*Config, error) {
342 cfg.Agents = agents
346 cfg.Agents = agents
343− mergeAgents(cfg, globalCfg, localConfig)
347+ mergeAgents(cfg, configs...)
344− mergeMCPs(cfg, globalCfg, localConfig)
348+ mergeMCPs(cfg, configs...)
345− mergeLSPs(cfg, globalCfg, localConfig)
349+ mergeLSPs(cfg, configs...)
347 // Validate the final configuration
351 // Validate the final configuration
348 if err := cfg.Validate(); err != nil {
352 if err := cfg.Validate(); err != nil {
@@ -457,8 +461,8 @@ func validateProvider(p provider.InferenceProvider, providerConfig ProviderConfi
457 return nil
461 return nil
460−func mergeModels(base, global, local *Config) {
464+func mergeModels(base *Config, others ...*Config) {
461− for _, cfg := range []*Config{global, local} {
465+ for _, cfg := range others {
462 if cfg == nil {
466 if cfg == nil {
@@ -472,8 +476,8 @@ func mergeModels(base, global, local *Config) {
475−func mergeOptions(base, global, local *Config) {
479+func mergeOptions(base *Config, others ...*Config) {
476− for _, cfg := range []*Config{global, local} {
480+ for _, cfg := range others {
477 if cfg == nil {
481 if cfg == nil {
@@ -506,8 +510,8 @@ func mergeOptions(base, global, local *Config) {
509−func mergeAgents(base, global, local *Config) {
513+func mergeAgents(base *Config, others ...*Config) {
510− for _, cfg := range []*Config{global, local} {
514+ for _, cfg := range others {
511 if cfg == nil {
515 if cfg == nil {
@@ -575,8 +579,8 @@ func mergeAgents(base, global, local *Config) {
578−func mergeMCPs(base, global, local *Config) {
582+func mergeMCPs(base *Config, others ...*Config) {
579− for _, cfg := range []*Config{global, local} {
583+ for _, cfg := range others {
580 if cfg == nil {
584 if cfg == nil {
@@ -584,8 +588,8 @@ func mergeMCPs(base, global, local *Config) {
587−func mergeLSPs(base, global, local *Config) {
591+func mergeLSPs(base *Config, others ...*Config) {
588− for _, cfg := range []*Config{global, local} {
592+ for _, cfg := range others {
589 if cfg == nil {
593 if cfg == nil {
@@ -593,15 +597,27 @@ func mergeLSPs(base, global, local *Config) {
596−func mergeProviderConfigs(base, global, local *Config) {
600+func mergeProviderConfigs(base *Config, others ...*Config) {
597− for _, cfg := range []*Config{global, local} {
601+ for _, cfg := range others {
598 if cfg == nil {
602 if cfg == nil {
601 for providerName, p := range cfg.Providers {
605 for providerName, p := range cfg.Providers {
602 p.ID = providerName
606 p.ID = providerName
603 if _, ok := base.Providers[providerName]; !ok {
607 if _, ok := base.Providers[providerName]; !ok {
608+ if slices.Contains(provider.KnownProviders(), providerName) {
609+ providers := Providers()
610+ for _, providerDef := range providers {
611+ if providerDef.ID == providerName {
612+ logging.Info("Using default provider config for", "provider", providerName)
613+ baseProvider := getDefaultProviderConfig(providerDef, providerDef.APIKey)
604− base.Providers[providerName] = p
614+ base.Providers[providerName] = mergeProviderConfig(providerName, baseProvider, p)
619+ base.Providers[providerName] = p
606 base.Providers[providerName] = mergeProviderConfig(providerName, base.Providers[providerName], p)
622 base.Providers[providerName] = mergeProviderConfig(providerName, base.Providers[providerName], p)
@@ -676,6 +692,40 @@ func providerDefaultConfig(providerID provider.InferenceProvider) ProviderConfig
695+func getDefaultProviderConfig(p provider.Provider, apiKey string) ProviderConfig {
696+ providerConfig := providerDefaultConfig(p.ID)
697+ providerConfig.APIKey = apiKey
698+ providerConfig.DefaultLargeModel = p.DefaultLargeModelID
699+ providerConfig.DefaultSmallModel = p.DefaultSmallModelID
700+ baseURL := p.APIEndpoint
701+ if strings.HasPrefix(baseURL, "$") {
702+ envVar := strings.TrimPrefix(baseURL, "$")
703+ baseURL = os.Getenv(envVar)
705+ providerConfig.BaseURL = baseURL
706+ for _, model := range p.Models {
707+ configModel := Model{
710+ CostPer1MIn: model.CostPer1MIn,
711+ CostPer1MOut: model.CostPer1MOut,
712+ CostPer1MInCached: model.CostPer1MInCached,
713+ CostPer1MOutCached: model.CostPer1MOutCached,
714+ ContextWindow: model.ContextWindow,
715+ DefaultMaxTokens: model.DefaultMaxTokens,
716+ CanReason: model.CanReason,
717+ SupportsImages: model.SupportsImages,
719+ // Set reasoning effort for reasoning models
720+ if model.HasReasoningEffort && model.DefaultReasoningEffort != "" {
721+ configModel.HasReasoningEffort = model.HasReasoningEffort
722+ configModel.ReasoningEffort = model.DefaultReasoningEffort
724+ providerConfig.Models = append(providerConfig.Models, configModel)
726+ return providerConfig
679func defaultConfigBasedOnEnv() *Config {
729func defaultConfigBasedOnEnv() *Config {
680 cfg := &Config{
730 cfg := &Config{
681 Options: Options{
731 Options: Options{
@@ -694,37 +744,7 @@ func defaultConfigBasedOnEnv() *Config {
694 if strings.HasPrefix(p.APIKey, "$") {
744 if strings.HasPrefix(p.APIKey, "$") {
695 envVar := strings.TrimPrefix(p.APIKey, "$")
745 envVar := strings.TrimPrefix(p.APIKey, "$")
696 if apiKey := os.Getenv(envVar); apiKey != "" {
746 if apiKey := os.Getenv(envVar); apiKey != "" {
697− providerConfig := providerDefaultConfig(p.ID)
698− providerConfig.APIKey = apiKey
699− providerConfig.DefaultLargeModel = p.DefaultLargeModelID
700− providerConfig.DefaultSmallModel = p.DefaultSmallModelID
701− baseURL := p.APIEndpoint
702− if strings.HasPrefix(baseURL, "$") {
703− envVar := strings.TrimPrefix(baseURL, "$")
704− baseURL = os.Getenv(envVar)
706− providerConfig.BaseURL = baseURL
707− for _, model := range p.Models {
708− configModel := Model{
711− CostPer1MIn: model.CostPer1MIn,
712− CostPer1MOut: model.CostPer1MOut,
713− CostPer1MInCached: model.CostPer1MInCached,
714− CostPer1MOutCached: model.CostPer1MOutCached,
715− ContextWindow: model.ContextWindow,
716− DefaultMaxTokens: model.DefaultMaxTokens,
717− CanReason: model.CanReason,
718− SupportsImages: model.SupportsImages,
720− // Set reasoning effort for reasoning models
721− if model.HasReasoningEffort && model.DefaultReasoningEffort != "" {
722− configModel.HasReasoningEffort = model.HasReasoningEffort
723− configModel.ReasoningEffort = model.DefaultReasoningEffort
725− providerConfig.Models = append(providerConfig.Models, configModel)
727− cfg.Providers[p.ID] = providerConfig
747+ cfg.Providers[p.ID] = getDefaultProviderConfig(p, apiKey)