diff --git a/aider/args.py b/aider/args.py index 5b3fdf07f..2d04d0709 100644 --- a/aider/args.py +++ b/aider/args.py @@ -791,8 +791,8 @@ def get_parser(default_config_files, git_root): is_config_file=True, metavar="CONFIG_FILE", help=( - "Specify the config file (default: search for .aider.conf.yml in git root, cwd" - " or home directory)" + "Specify the config file (default: search for .aider.conf.yml in git root, cwd, home" + " dir, and for conf.yml in ~/.config/aider)" ), ).complete = shtab.FILE # This is a duplicate of the argument in the preparser and is a no-op by this time of diff --git a/aider/args_formatter.py b/aider/args_formatter.py index fc4c3efac..0390976ca 100644 --- a/aider/args_formatter.py +++ b/aider/args_formatter.py @@ -93,7 +93,7 @@ class YamlHelpFormatter(argparse.HelpFormatter): ########################################################## # Sample .aider.conf.yml # This file lists *all* the valid configuration entries. -# Place in your home dir, or at the root of your git repo. +# Place in your home dir, git repo root, or $XDG_CONFIG_HOME/aider/conf.yml. ########################################################## # Note: You can only put OpenAI and Anthropic API keys in the YAML diff --git a/aider/main.py b/aider/main.py index afb3f8366..f18e140d3 100644 --- a/aider/main.py +++ b/aider/main.py @@ -305,6 +305,17 @@ def parse_lint_cmds(lint_cmds, io): def generate_search_path_list(default_file, git_root, command_line_file): files = [] files.append(Path.home() / default_file) # homedir + + xdg_config_home = os.environ.get("XDG_CONFIG_HOME") + if not xdg_config_home: + xdg_config_home = Path.home() / ".config" + else: + xdg_config_home = Path(xdg_config_home) + xdg_aider_dir = xdg_config_home / "aider" + files.append(xdg_aider_dir / default_file) + if default_file.startswith("."): + files.append(xdg_aider_dir / default_file[1:]) + if git_root: files.append(Path(git_root) / default_file) # git root files.append(default_file) @@ -465,7 +476,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F default_config_files = [] try: - default_config_files += [conf_fname.resolve()] # CWD + default_config_files.append(conf_fname.resolve()) # CWD except OSError: pass @@ -473,7 +484,27 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F git_conf = Path(git_root) / conf_fname # git root if git_conf not in default_config_files: default_config_files.append(git_conf) - default_config_files.append(Path.home() / conf_fname) # homedir + + xdg_config_home = os.environ.get("XDG_CONFIG_HOME") + if not xdg_config_home: + xdg_config_home = Path.home() / ".config" + else: + xdg_config_home = Path(xdg_config_home) + xdg_aider_dir = xdg_config_home / "aider" + + # Non-dotted version, higher precedence + xdg_conf_no_dot = xdg_aider_dir / "conf.yml" + if xdg_conf_no_dot not in default_config_files: + default_config_files.append(xdg_conf_no_dot) + + # Dotted version + xdg_conf_dot = xdg_aider_dir / conf_fname + if xdg_conf_dot not in default_config_files: + default_config_files.append(xdg_conf_dot) + + home_conf = Path.home() / conf_fname + if home_conf not in default_config_files: + default_config_files.append(home_conf) # homedir default_config_files = list(map(str, default_config_files)) parser = get_parser(default_config_files, git_root) diff --git a/aider/website/docs/config/aider_conf.md b/aider/website/docs/config/aider_conf.md index bd5ea6246..e71fdec7b 100644 --- a/aider/website/docs/config/aider_conf.md +++ b/aider/website/docs/config/aider_conf.md @@ -6,14 +6,15 @@ description: How to configure aider with a YAML config file. # YAML config file -Most of aider's options can be set in an `.aider.conf.yml` file. -Aider will look for a this file in these locations: +Most of aider's options can be set in a YAML config file. +Aider will look for config files in these locations: -- Your home directory. -- The root of your git repo. -- The current directory. +- Your home directory (`~/.aider.conf.yml`). +- Your XDG config home in `$XDG_CONFIG_HOME/aider/`. Aider will look for `conf.yml` and `.aider.conf.yml`. If `$XDG_CONFIG_HOME` is not set, it defaults to `~/.config`. +- The root of your git repo (`.aider.conf.yml`). +- The current directory (`.aider.conf.yml`). -If the files above exist, they will be loaded in that order. Files loaded last will take priority. +If the files above exist, they will be loaded in that order. Settings from files loaded later take priority. You can also specify the `--config ` parameter, which will only load the one config file. diff --git a/tests/basic/test_main.py b/tests/basic/test_main.py index c8966a536..91639b50f 100644 --- a/tests/basic/test_main.py +++ b/tests/basic/test_main.py @@ -533,8 +533,18 @@ class TestMain(TestCase): cwd_config = cwd / ".aider.conf.yml" named_config = git_dir / "named.aider.conf.yml" + # Create XDG config files + xdg_config_home = fake_home / ".config" + os.environ["XDG_CONFIG_HOME"] = str(xdg_config_home) + xdg_aider_dir = xdg_config_home / "aider" + xdg_aider_dir.mkdir(parents=True) + xdg_config_dot = xdg_aider_dir / ".aider.conf.yml" + xdg_config_no_dot = xdg_aider_dir / "conf.yml" + cwd_config.write_text("model: gpt-4-32k\nmap-tokens: 4096\n") git_config.write_text("model: gpt-4\nmap-tokens: 2048\n") + xdg_config_no_dot.write_text("model: xdg-no-dot\nmap-tokens: 600\n") + xdg_config_dot.write_text("model: xdg-dot\nmap-tokens: 500\n") home_config.write_text("model: gpt-3.5-turbo\nmap-tokens: 1024\n") named_config.write_text("model: gpt-4-1106-preview\nmap-tokens: 8192\n") @@ -555,8 +565,6 @@ class TestMain(TestCase): # Test loading from current working directory main(["--yes", "--exit"], input=DummyInput(), output=DummyOutput()) _, kwargs = MockCoder.call_args - print("kwargs:", kwargs) # Add this line for debugging - self.assertIn("main_model", kwargs, "main_model key not found in kwargs") self.assertEqual(kwargs["main_model"].name, "gpt-4-32k") self.assertEqual(kwargs["map_tokens"], 4096) @@ -567,10 +575,24 @@ class TestMain(TestCase): self.assertEqual(kwargs["main_model"].name, "gpt-4") self.assertEqual(kwargs["map_tokens"], 2048) - # Test loading from home directory + # Test loading from XDG (no dot) git_config.unlink() main(["--yes", "--exit"], input=DummyInput(), output=DummyOutput()) _, kwargs = MockCoder.call_args + self.assertEqual(kwargs["main_model"].name, "xdg-no-dot") + self.assertEqual(kwargs["map_tokens"], 600) + + # Test loading from XDG (dot) + xdg_config_no_dot.unlink() + main(["--yes", "--exit"], input=DummyInput(), output=DummyOutput()) + _, kwargs = MockCoder.call_args + self.assertEqual(kwargs["main_model"].name, "xdg-dot") + self.assertEqual(kwargs["map_tokens"], 500) + + # Test loading from home directory + xdg_config_dot.unlink() + main(["--yes", "--exit"], input=DummyInput(), output=DummyOutput()) + _, kwargs = MockCoder.call_args self.assertEqual(kwargs["main_model"].name, "gpt-3.5-turbo") self.assertEqual(kwargs["map_tokens"], 1024)