diff --git a/.dockerignore b/.dockerignore index f8b9278001b..efd7df1b89a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,7 +6,7 @@ # Recursively allow files under subtree !/.github/** -!/aider/** +!/cecli/** !/benchmark/** !/docker/** !/requirements/** diff --git a/.github/ISSUE_TEMPLATE/issue.yml b/.github/ISSUE_TEMPLATE/issue.yml index 4795b2831b1..ae6e01314cc 100644 --- a/.github/ISSUE_TEMPLATE/issue.yml +++ b/.github/ISSUE_TEMPLATE/issue.yml @@ -1,5 +1,5 @@ name: Question or bug report -description: Submit a question or bug report to help us improve aider +description: Submit a question or bug report to help us improve cecli labels: [] body: - type: textarea @@ -11,9 +11,9 @@ body: - type: textarea attributes: label: Version and model info - description: Please include aider version, model being used (`gpt-4-xxx`, etc) and any other switches or config settings that are active. + description: Please include cecli version, model being used (`gemini-3-xxx`, etc) and any other switches or config settings that are active. placeholder: | - Aider v0.XX.Y + cecli v0.XX.Y Model: gpt-N-... using ???? edit format Git repo: .git with ### files Repo-map: using #### tokens diff --git a/.github/workflows/check_pypi_version.yml b/.github/workflows/check_pypi_version.yml index 859020b239a..874e831f15c 100644 --- a/.github/workflows/check_pypi_version.yml +++ b/.github/workflows/check_pypi_version.yml @@ -1,9 +1,5 @@ name: Check PyPI Version -# Check to be sure `pip install aider-chat` installs the most recently published version. -# If dependencies get yanked, it may render the latest version uninstallable. -# See https://github.com/Aider-AI/aider/issues/3699 for example. - on: schedule: # Run once a day at midnight UTC @@ -26,23 +22,23 @@ jobs: - name: Install aider-ce run: pip install aider-ce - - name: Get installed aider version + - name: Get installed cecli version id: installed_version run: | set -x # Enable debugging output - aider_version_output=$(aider-ce --version) + cecli_version_output=$(aider-ce --version) if [ $? -ne 0 ]; then - echo "Error: 'aider --version' command failed." + echo "Error: 'cecli --version' command failed." exit 1 fi - echo "Raw aider --version output: $aider_version_output" + echo "Raw cecli --version output: $cecli_version_output" # Extract version number (format X.Y.Z) - version_num=$(echo "$aider_version_output" | grep -oP '\d+\.\d+\.\d+') + version_num=$(echo "$cecli_version_output" | grep -oP '\d+\.\d+\.\d+') # Check if grep found anything if [ -z "$version_num" ]; then - echo "Error: Could not extract version number using grep -oP '\d+\.\d+\.\d+' from output: $aider_version_output" + echo "Error: Could not extract version number using grep -oP '\d+\.\d+\.\d+' from output: $cecli_version_output" exit 1 fi @@ -80,7 +76,7 @@ jobs: echo "Installed version: ${{ steps.installed_version.outputs.version }}" echo "Latest tag version: ${{ steps.latest_tag.outputs.tag }}" if [ "${{ steps.installed_version.outputs.version }}" != "${{ steps.latest_tag.outputs.tag }}" ]; then - echo "Error: Installed aider version (${{ steps.installed_version.outputs.version }}) does not match the latest tag (${{ steps.latest_tag.outputs.tag }})." + echo "Error: Installed cecli version (${{ steps.installed_version.outputs.version }}) does not match the latest tag (${{ steps.latest_tag.outputs.tag }})." exit 1 fi echo "Versions match." diff --git a/.github/workflows/docker-build-test.yml b/.github/workflows/docker-build-test.yml index 328fcbeb22d..46b2d3b870b 100644 --- a/.github/workflows/docker-build-test.yml +++ b/.github/workflows/docker-build-test.yml @@ -3,7 +3,7 @@ name: Docker Build Test on: push: paths-ignore: - - 'aider/website/**' + - 'cecli/website/**' - 'README.md' - 'HISTORY.md' - '.github/workflows/*' @@ -12,7 +12,7 @@ on: - main pull_request: paths-ignore: - - 'aider/website/**' + - 'cecli/website/**' - 'README.md' - 'HISTORY.md' - '.github/workflows/*' diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 4454e832fd9..f0255751789 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -11,7 +11,7 @@ on: branches: - "main" paths: - - "aider/website/**" + - "cecli/website/**" - ".github/workflows/pages.yml" # Allows you to run this workflow manually from the Actions tab @@ -34,7 +34,7 @@ jobs: runs-on: ubuntu-latest defaults: run: - working-directory: aider/website + working-directory: cecli/website steps: - name: Checkout uses: actions/checkout@v4 @@ -46,7 +46,7 @@ jobs: ruby-version: '3.3' # Not needed with a .ruby-version file bundler-cache: true # runs 'bundle install' and caches installed gems automatically cache-version: 0 # Increment this number if you need to re-download cached gems - working-directory: '${{ github.workspace }}/aider/website' + working-directory: '${{ github.workspace }}/cecli/website' - name: Setup Pages id: pages uses: actions/configure-pages@v3 @@ -58,7 +58,7 @@ jobs: - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: - path: "aider/website/_site" + path: "cecli/website/_site" # Deployment job deploy: @@ -84,4 +84,4 @@ jobs: - name: Run linkchecker run: | - linkchecker --ignore-url='.+\.(mp4|mov|avi)' https://aider.chat + linkchecker --ignore-url='.+\.(mp4|mov|avi)' https://cecli.dev diff --git a/.github/workflows/ubuntu-tests.yml b/.github/workflows/ubuntu-tests.yml index f84933f6b9d..fe33e4ccaae 100644 --- a/.github/workflows/ubuntu-tests.yml +++ b/.github/workflows/ubuntu-tests.yml @@ -3,7 +3,7 @@ name: Ubuntu Python Tests on: push: paths-ignore: - - 'aider/website/**' + - 'cecli/website/**' - 'README.md' - 'HISTORY.md' - '.github/workflows/*' @@ -12,7 +12,7 @@ on: - main pull_request: paths-ignore: - - 'aider/website/**' + - 'cecli/website/**' - 'README.md' - 'HISTORY.md' - '.github/workflows/*' @@ -57,7 +57,5 @@ jobs: ".[help,playwright]" - name: Run tests - env: - AIDER_ANALYTICS: false run: | pytest diff --git a/.github/workflows/windows-tests.yml b/.github/workflows/windows-tests.yml index 8b809a17405..6235b02565b 100644 --- a/.github/workflows/windows-tests.yml +++ b/.github/workflows/windows-tests.yml @@ -3,7 +3,7 @@ name: Windows Python Tests on: push: paths-ignore: - - 'aider/website/**' + - 'cecli/website/**' - 'README.md' - 'HISTORY.md' - '.github/workflows/*' @@ -12,7 +12,7 @@ on: - main pull_request: paths-ignore: - - 'aider/website/**' + - 'cecli/website/**' - 'README.md' - 'HISTORY.md' - '.github/workflows/*' @@ -45,7 +45,5 @@ jobs: uv pip install --system pytest pytest-asyncio pytest-mock -r requirements/requirements.in -r requirements/requirements-help.in -r requirements/requirements-playwright.in '.[help,playwright]' - name: Run tests - env: - AIDER_ANALYTICS: false run: | pytest diff --git a/.github/workflows/windows_check_pypi_version.yml b/.github/workflows/windows_check_pypi_version.yml index 6273feae843..ad867d4a6e9 100644 --- a/.github/workflows/windows_check_pypi_version.yml +++ b/.github/workflows/windows_check_pypi_version.yml @@ -1,9 +1,5 @@ name: Windows Check PyPI Version -# Check to be sure `pip install aider-chat` installs the most recently published version on Windows. -# If dependencies get yanked, it may render the latest version uninstallable. -# See https://github.com/Aider-AI/aider/issues/3699 for example. - on: schedule: # Run once a day at 1 AM UTC (offset from Ubuntu check) @@ -29,22 +25,22 @@ jobs: - name: Install aider-ce run: pip install aider-ce - - name: Get installed aider version + - name: Get installed cecli version id: installed_version run: | - Write-Host "Running 'aider --version'..." - $aider_version_output = aider-ce --version + Write-Host "Running 'cecli --version'..." + $cecli_version_output = aider-ce --version if ($LASTEXITCODE -ne 0) { - Write-Error "Error: 'aider --version' command failed." + Write-Error "Error: 'cecli --version' command failed." exit 1 } - Write-Host "Raw aider --version output: $aider_version_output" + Write-Host "Raw cecli --version output: $cecli_version_output" # Extract version number (format X.Y.Z) using PowerShell regex - $match = [regex]::Match($aider_version_output, '\d+\.\d+\.\d+') + $match = [regex]::Match($cecli_version_output, '\d+\.\d+\.\d+') if (-not $match.Success) { - Write-Error "Error: Could not extract version number using regex '\d+\.\d+\.\d+' from output: $aider_version_output" + Write-Error "Error: Could not extract version number using regex '\d+\.\d+\.\d+' from output: $cecli_version_output" exit 1 } $version_num = $match.Value @@ -84,7 +80,7 @@ jobs: Write-Host "Installed version: ${{ steps.installed_version.outputs.version }}" Write-Host "Latest tag version: ${{ steps.latest_tag.outputs.tag }}" if ("${{ steps.installed_version.outputs.version }}" -ne "${{ steps.latest_tag.outputs.tag }}") { - Write-Error "Error: Installed aider version (${{ steps.installed_version.outputs.version }}) does not match the latest tag (${{ steps.latest_tag.outputs.tag }})." + Write-Error "Error: Installed cecli version (${{ steps.installed_version.outputs.version }}) does not match the latest tag (${{ steps.latest_tag.outputs.tag }})." exit 1 } Write-Host "Versions match." diff --git a/.gitignore b/.gitignore index cbd58d37e8a..ebb5c7a78f0 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ # Recursively allow files under subtree !/.github/** -!/aider/** +!/cecli/** !/benchmark/** !/docker/** !/requirements/** @@ -34,5 +34,8 @@ .aider* aider/__version__.py aider/_version.py +.cecli* +cecli/__version__.py +cecli/_version.py *.pyc env/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 07d1c7ed07a..1d2f29ef1fc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: rev: v2.2.6 hooks: - id: codespell - args: ["--skip", "aider/website/docs/languages.md"] + args: ["--skip", "cecli/website/docs/languages.md"] additional_dependencies: - tomli - repo: local @@ -26,7 +26,7 @@ repos: - id: filter-model-metadata name: Filter model metadata to chat mode only entry: node scripts/filter-chat-mode.js - args: ["aider/resources/model-metadata.json"] + args: ["cecli/resources/model-metadata.json"] language: system - files: ^aider/resources/model-metadata\.json$ + files: ^cecli/resources/model-metadata\.json$ pass_filenames: false diff --git a/MANIFEST.in b/MANIFEST.in index c3ebc1dde4d..09cfe029620 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -# This needs to sync with aider/help_pats.py +# This needs to sync with cecli/help_pats.py include requirements/requirements.in include requirements/requirements-dev.in @@ -7,19 +7,19 @@ include requirements/requirements-playwright.in global-exclude .DS_Store -recursive-exclude aider/website/examples * -recursive-exclude aider/website/_posts * +recursive-exclude cecli/website/examples * +recursive-exclude cecli/website/_posts * -exclude aider/website/HISTORY.md -exclude aider/website/docs/benchmarks*.md -exclude aider/website/docs/ctags.md -exclude aider/website/docs/unified-diffs.md +exclude cecli/website/HISTORY.md +exclude cecli/website/docs/benchmarks*.md +exclude cecli/website/docs/ctags.md +exclude cecli/website/docs/unified-diffs.md -exclude aider/website/install.ps1 -exclude aider/website/install.sh +exclude cecli/website/install.ps1 +exclude cecli/website/install.sh -recursive-exclude aider/website/docs/leaderboards * -recursive-exclude aider/website/assets * -recursive-exclude aider/website *.js -recursive-exclude aider/website *.html -recursive-exclude aider/website *.yml +recursive-exclude cecli/website/docs/leaderboards * +recursive-exclude cecli/website/assets * +recursive-exclude cecli/website *.js +recursive-exclude cecli/website *.html +recursive-exclude cecli/website *.yml diff --git a/aider/commands.py b/aider/commands.py index 603b2142b08..e69de29bb2d 100644 --- a/aider/commands.py +++ b/aider/commands.py @@ -1,237 +0,0 @@ -import asyncio -import re -import sys -from pathlib import Path - -from aider.repo import ANY_GIT_ERROR - -from .commands.utils.registry import CommandRegistry - - -class SwitchCoder(Exception): - def __init__(self, placeholder=None, **kwargs): - self.kwargs = kwargs - self.placeholder = placeholder - - -class Commands: - scraper = None - - def clone(self): - return Commands( - self.io, - None, - voice_language=self.voice_language, - voice_input_device=self.voice_input_device, - voice_format=self.voice_format, - verify_ssl=self.verify_ssl, - args=self.args, - parser=self.parser, - verbose=self.verbose, - editor=self.editor, - original_read_only_fnames=self.original_read_only_fnames, - ) - - def __init__( - self, - io, - coder, - voice_language=None, - voice_input_device=None, - voice_format=None, - verify_ssl=True, - args=None, - parser=None, - verbose=False, - editor=None, - original_read_only_fnames=None, - ): - self.io = io - self.coder = coder - self.parser = parser - self.args = args - self.verbose = verbose - - self.verify_ssl = verify_ssl - if voice_language == "auto": - voice_language = None - - self.voice_language = voice_language - self.voice_format = voice_format - self.voice_input_device = voice_input_device - - self.help = None - self.editor = editor - - # Store the original read-only filenames provided via args.read - self.original_read_only_fnames = set(original_read_only_fnames or []) - self.cmd_running_event = asyncio.Event() - self.cmd_running_event.set() # Initially set, meaning no command is running - - def is_command(self, inp): - return inp[0] in "/!" - - def is_run_command(self, inp): - return inp and ( - inp[0] in "!" or inp[:5] == "/lint" or inp[:5] == "/test" or inp[:4] == "/run" - ) - - def is_test_command(self, inp): - return inp and (inp[:5] == "/lint" or inp[:5] == "/test") - - def get_raw_completions(self, cmd): - assert cmd.startswith("/") - cmd = cmd[1:] - cmd = cmd.replace("-", "_") - - raw_completer = getattr(self, f"completions_raw_{cmd}", None) - return raw_completer - - def get_completions(self, cmd): - assert cmd.startswith("/") - cmd = cmd[1:] - - # Get completions from command system - command_class = CommandRegistry.get_command(cmd) - if command_class: - return command_class.get_completions(self.io, self.coder, "") - - # No completions available - return [] - - def get_commands(self): - # Get commands from registry - registry_commands = CommandRegistry.list_commands() - commands = [f"/{cmd}" for cmd in registry_commands] - return sorted(commands) - - async def do_run(self, cmd_name, args, **kwargs): - # Execute command using registry - command_class = CommandRegistry.get_command(cmd_name) - if not command_class: - self.io.tool_output(f"Error: Command {cmd_name} not found.") - return - - self.cmd_running_event.clear() # Command is running - try: - # Generate a spreadable kwargs dict with all relevant Commands attributes - kwargs.update( - { - "original_read_only_fnames": self.original_read_only_fnames, - "voice_language": self.voice_language, - "voice_format": self.voice_format, - "voice_input_device": self.voice_input_device, - "verify_ssl": self.verify_ssl, - "parser": self.parser, - "verbose": self.verbose, - "editor": self.editor, - "system_args": self.args, - } - ) - - return await CommandRegistry.execute( - cmd_name, - self.io, - self.coder, - args, - **kwargs, - ) - except ANY_GIT_ERROR as err: - self.io.tool_error(f"Unable to complete {cmd_name}: {err}") - return - except SwitchCoder as e: - raise e - except Exception as e: - self.io.tool_error(f"Error executing command {cmd_name}: {str(e)}") - return - finally: - self.cmd_running_event.set() # Command finished - if self.coder.tui and self.coder.tui(): - self.coder.tui().refresh() - - def matching_commands(self, inp): - words = inp.strip().split() - if not words: - return - - first_word = words[0] - rest_inp = inp[len(words[0]) :].strip() - - all_commands = self.get_commands() - matching_commands = [cmd for cmd in all_commands if cmd.startswith(first_word)] - return matching_commands, first_word, rest_inp - - async def run(self, inp): - if inp.startswith("!"): - return await self.do_run("run", inp[1:]) - - res = self.matching_commands(inp) - if res is None: - return - matching_commands, first_word, rest_inp = res - if len(matching_commands) == 1: - command = matching_commands[0][1:] - return await self.do_run(command, rest_inp) - elif first_word in matching_commands: - command = first_word[1:] - return await self.do_run(command, rest_inp) - elif len(matching_commands) > 1: - self.io.tool_error(f"Ambiguous command: {', '.join(matching_commands)}") - else: - self.io.tool_error(f"Invalid command: {first_word}") - - def get_help_md(self): - "Show help about all commands in markdown" - - res = """ -|Command|Description| -|:------|:----------| -""" - commands = sorted(self.get_commands()) - for cmd in commands: - cmd_name = cmd[1:] # Remove leading '/' - command_class = CommandRegistry.get_command(cmd_name) - if command_class: - description = command_class.DESCRIPTION - res += f"| **{cmd}** | {description} |\n" - else: - res += f"| **{cmd}** | |\n" - - res += "\n" - return res - - def _get_session_directory(self): - """Get the session storage directory, creating it if needed""" - session_dir = Path(self.coder.root) / ".aider" / "sessions" - session_dir.mkdir(parents=True, exist_ok=True) - return session_dir - - def _get_session_file_path(self, session_name): - """Get the full path for a session file""" - session_dir = self._get_session_directory() - # Sanitize the session name to be filesystem-safe - safe_name = re.sub(r"[^a-zA-Z0-9_.-]", "_", session_name) - ext = "" if safe_name[-5:] == ".json" else ".json" - - return session_dir / f"{safe_name}{ext}" - - -def parse_quoted_filenames(args): - filenames = re.findall(r"\"(.+?)\"|(\S+)", args) - filenames = [name for sublist in filenames for name in sublist if name] - return filenames - - -def get_help_md(): - md = Commands(None, None).get_help_md() - return md - - -def main(): - md = get_help_md() - print(md) - - -if __name__ == "__main__": - status = main() - sys.exit(status) diff --git a/aider/helpers/file_searcher.py b/aider/helpers/file_searcher.py deleted file mode 100644 index 11a3f656286..00000000000 --- a/aider/helpers/file_searcher.py +++ /dev/null @@ -1,203 +0,0 @@ -""" -File search utilities for aider. - -This module provides functions for searching and resolving file paths -relative to various directories (git root, home folder, .aider, .cecli, etc.). -""" - -from pathlib import Path -from typing import List, Optional - - -def generate_search_path_list( - default_file: str, git_root: Optional[str], command_line_file: Optional[str] -) -> List[str]: - """ - Generate a list of file paths to search for configuration files. - - The search order is: - 1. Home directory (~/default_file) - 2. Git root directory (git_root/default_file) if git_root is provided - 3. Current directory (default_file) - 4. Command line specified file (command_line_file) if provided - - Args: - default_file: The default filename to search for - git_root: The git root directory (optional) - command_line_file: A file specified on the command line (optional) - - Returns: - List of resolved file paths in search order (first to last) - """ - files = [] - files.append(Path.home() / default_file) # homedir - if git_root: - files.append(Path(git_root) / default_file) # git root - files.append(default_file) - if command_line_file: - files.append(command_line_file) - - resolved_files = [] - for fn in files: - try: - resolved_files.append(Path(fn).expanduser().resolve()) - except OSError: - pass - - files = resolved_files - files.reverse() - uniq = [] - for fn in files: - if fn not in uniq: - uniq.append(fn) - uniq.reverse() - files = uniq - files = list(map(str, files)) - files = list(dict.fromkeys(files)) - - return files - - -def resolve_file_path( - filename: str, - relative_to: str = "auto", - git_root: Optional[str] = None, - search_dirs: Optional[List[str]] = None, -) -> Optional[Path]: - """ - Resolve a file path relative to various directories. - - Args: - filename: The filename to resolve - relative_to: Where to resolve the file from. Options: - - "auto": Try multiple locations (git_root, home, .aider, .cecli, cwd) - - "git": Resolve relative to git root - - "home": Resolve relative to home directory - - "cwd": Resolve relative to current working directory - - "aider": Resolve relative to .aider directory in git root or home - - "cecli": Resolve relative to .cecli directory in git root or home - git_root: The git root directory (optional, required for some modes) - search_dirs: Additional directories to search (optional) - - Returns: - Resolved Path object if found, None otherwise - """ - if relative_to == "auto": - # Try multiple locations in order of preference - locations = [] - - # 1. Git root (if available) - if git_root: - locations.append(Path(git_root) / filename) - - # 2. Home directory - locations.append(Path.home() / filename) - - # 3. .aider directories - if git_root: - locations.append(Path(git_root) / ".aider" / filename) - locations.append(Path.home() / ".aider" / filename) - - # 4. .cecli directories - if git_root: - locations.append(Path(git_root) / ".cecli" / filename) - locations.append(Path.home() / ".cecli" / filename) - - # 5. Current working directory - locations.append(Path.cwd() / filename) - - # 6. Additional search directories - if search_dirs: - for dir_path in search_dirs: - locations.append(Path(dir_path) / filename) - - # Try each location - for location in locations: - try: - resolved = location.expanduser().resolve() - if resolved.exists(): - return resolved - except OSError: - continue - - return None - - elif relative_to == "git": - if not git_root: - raise ValueError("git_root is required when relative_to='git'") - return (Path(git_root) / filename).expanduser().resolve() - - elif relative_to == "home": - return (Path.home() / filename).expanduser().resolve() - - elif relative_to == "cwd": - return (Path.cwd() / filename).expanduser().resolve() - - elif relative_to == "aider": - # Try git root first, then home - if git_root: - aider_path = Path(git_root) / ".aider" / filename - try: - resolved = aider_path.expanduser().resolve() - if resolved.exists(): - return resolved - except OSError: - pass - - # Fall back to home directory - return (Path.home() / ".aider" / filename).expanduser().resolve() - - elif relative_to == "cecli": - # Try git root first, then home - if git_root: - cecli_path = Path(git_root) / ".cecli" / filename - try: - resolved = cecli_path.expanduser().resolve() - if resolved.exists(): - return resolved - except OSError: - pass - - # Fall back to home directory - return (Path.home() / ".cecli" / filename).expanduser().resolve() - - else: - raise ValueError(f"Invalid relative_to value: {relative_to}") - - -def find_config_file( - config_name: str, - git_root: Optional[str] = None, - command_line_file: Optional[str] = None, - config_dirs: Optional[List[str]] = None, -) -> Optional[Path]: - """ - Find a configuration file using the standard search path. - - This is a higher-level function that uses generate_search_path_list - to find configuration files. - - Args: - config_name: The configuration filename (e.g., ".env", ".aider.conf.yml") - git_root: The git root directory (optional) - command_line_file: A file specified on the command line (optional) - config_dirs: Additional directories to search (optional) - - Returns: - Path to the first existing configuration file found, or None - """ - # Generate standard search path - search_paths = generate_search_path_list(config_name, git_root, command_line_file) - - # Add additional config directories if provided - if config_dirs: - for dir_path in config_dirs: - search_paths.append(str(Path(dir_path) / config_name)) - - # Check each path - for file_path in search_paths: - path_obj = Path(file_path) - if path_obj.exists(): - return path_obj.resolve() - - return None diff --git a/aider/main.py b/aider/main.py index 1f4b8386a92..e69de29bb2d 100644 --- a/aider/main.py +++ b/aider/main.py @@ -1,1575 +0,0 @@ -import os - -try: - if not os.getenv("CECLI_DEFAULT_TLS") and not os.getenv("AIDER_CE_DEFAULT_TLS"): - import truststore - - truststore.inject_into_ssl() -except Exception as e: - print(e) - pass - -import asyncio -import json -import os -import re -import sys -import threading -import time -import traceback -import webbrowser -from dataclasses import fields -from pathlib import Path - -try: - import git -except ImportError: - git = None - -import importlib_resources -import shtab -from dotenv import load_dotenv - -if sys.platform == "win32": # Windows asyncio fix. set_event_loop_policy deprecated in 3.16 - if hasattr(asyncio, "set_event_loop_policy"): - asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) -from prompt_toolkit.enums import EditingMode - -from aider import __version__, models, urls, utils -from aider.args import get_parser -from aider.coders import Coder -from aider.coders.base_coder import UnknownEditFormat -from aider.commands import Commands, SwitchCoder -from aider.deprecated_args import handle_deprecated_model_args -from aider.format_settings import format_settings, scrub_sensitive_info -from aider.helpers.copypaste import ClipboardWatcher -from aider.helpers.file_searcher import generate_search_path_list -from aider.history import ChatSummary -from aider.io import InputOutput -from aider.llm import litellm # noqa: F401; properly init litellm on launch -from aider.mcp import load_mcp_servers -from aider.models import ModelSettings -from aider.onboarding import offer_openrouter_oauth, select_default_model -from aider.repo import ANY_GIT_ERROR, GitRepo -from aider.report import report_uncaught_exceptions, set_args_error_data -from aider.versioncheck import check_version, install_from_main_branch, install_upgrade -from aider.watch import FileWatcher - -from .dump import dump # noqa: F401 - - -def convert_yaml_to_json_string(value): - """ - Convert YAML dict/list values to JSON strings for compatibility. - - configargparse.YAMLConfigFileParser converts YAML to Python objects, - but some arguments expect JSON strings. This function handles: - - Direct dict/list objects - - String representations of dicts/lists (Python literals) - - Already JSON strings (passed through unchanged) - - Args: - value: The value to convert - - Returns: - str: JSON string if value is a dict/list, otherwise the original value - """ - if value is None: - return None - - if isinstance(value, (dict, list)): - return json.dumps(value) - - if isinstance(value, str): - # configargparse might convert dict to string representation - # Try to parse it as a Python literal - try: - import ast - - parsed = ast.literal_eval(value) - if isinstance(parsed, (dict, list)): - return json.dumps(parsed) - except (SyntaxError, ValueError): - # If it's not a Python literal, assume it's already JSON - pass - - return value - - -def check_config_files_for_yes(config_files): - found = False - for config_file in config_files: - if Path(config_file).exists(): - try: - with open(config_file, "r") as f: - for line in f: - if line.strip().startswith("yes:"): - print("Configuration error detected.") - print(f"The file {config_file} contains a line starting with 'yes:'") - print("Please replace 'yes:' with 'yes-always:' in this file.") - found = True - except Exception: - pass - return found - - -def get_git_root(): - """Try and guess the git repo, since the conf.yml can be at the repo root""" - try: - repo = git.Repo(search_parent_directories=True) - return repo.working_tree_dir - except (git.InvalidGitRepositoryError, FileNotFoundError): - return None - - -def guessed_wrong_repo(io, git_root, fnames, git_dname): - """After we parse the args, we can determine the real repo. Did we guess wrong?""" - - try: - check_repo = Path(GitRepo(io, fnames, git_dname).root).resolve() - except (OSError,) + ANY_GIT_ERROR: - return - - # we had no guess, rely on the "true" repo result - if not git_root: - return str(check_repo) - - git_root = Path(git_root).resolve() - if check_repo == git_root: - return - - return str(check_repo) - - -def validate_tui_args(args): - """Validate that incompatible flags aren't used with --tui""" - if not args.tui: - return - - incompatible = [] - if args.vim: - incompatible.append("--vim") - if not args.fancy_input: - incompatible.append("--no-fancy-input") - - if incompatible: - print(f"Error: --tui is incompatible with: {', '.join(incompatible)}") - print("Remove these flags or use standard CLI mode.") - sys.exit(1) - - -async def make_new_repo(git_root, io): - try: - repo = git.Repo.init(git_root) - await check_gitignore(git_root, io, False) - except ANY_GIT_ERROR as err: # issue #1233 - io.tool_error(f"Unable to create git repo in {git_root}") - io.tool_output(str(err)) - return - - io.tool_output(f"Git repository created in {git_root}") - return repo - - -async def setup_git(git_root, io): - if git is None: - return - - try: - cwd = Path.cwd() - except OSError: - cwd = None - - repo = None - - if git_root: - try: - repo = git.Repo(git_root) - except ANY_GIT_ERROR: - pass - elif cwd == Path.home(): - io.tool_warning( - "You should probably run aider in your project's directory, not your home dir." - ) - return - elif cwd and await io.confirm_ask( - "No git repo found, create one to track aider's changes (recommended)?", acknowledge=True - ): - git_root = str(cwd.resolve()) - repo = await make_new_repo(git_root, io) - - if not repo: - return - - try: - user_name = repo.git.config("--get", "user.name") or None - except git.exc.GitCommandError: - user_name = None - - try: - user_email = repo.git.config("--get", "user.email") or None - except git.exc.GitCommandError: - user_email = None - - if user_name and user_email: - return repo.working_tree_dir - - with repo.config_writer() as git_config: - if not user_name: - git_config.set_value("user", "name", "Your Name") - io.tool_warning('Update git name with: git config user.name "Your Name"') - if not user_email: - git_config.set_value("user", "email", "you@example.com") - io.tool_warning('Update git email with: git config user.email "you@example.com"') - - return repo.working_tree_dir - - -async def check_gitignore(git_root, io, ask=True): - if not git_root: - return - - try: - repo = git.Repo(git_root) - patterns_to_add = [] - - if not repo.ignored(".aider"): - patterns_to_add.append(".aider*") - - env_path = Path(git_root) / ".env" - if env_path.exists() and not repo.ignored(".env"): - patterns_to_add.append(".env") - - if not patterns_to_add: - return - - gitignore_file = Path(git_root) / ".gitignore" - if gitignore_file.exists(): - try: - content = io.read_text(gitignore_file) - if content is None: - return - if not content.endswith("\n"): - content += "\n" - except OSError as e: - io.tool_error(f"Error when trying to read {gitignore_file}: {e}") - return - else: - content = "" - except ANY_GIT_ERROR: - return - - if ask: - io.tool_output("You can skip this check with --no-gitignore") - if not await io.confirm_ask( - f"Add {', '.join(patterns_to_add)} to .gitignore (recommended)?", - acknowledge=True, - ): - return - - content += "\n".join(patterns_to_add) + "\n" - - try: - io.write_text(gitignore_file, content) - io.tool_output(f"Added {', '.join(patterns_to_add)} to .gitignore") - except OSError as e: - io.tool_error(f"Error when trying to write to {gitignore_file}: {e}") - io.tool_output( - "Try running with appropriate permissions or manually add these patterns to .gitignore:" - ) - for pattern in patterns_to_add: - io.tool_output(f" {pattern}") - - -def parse_lint_cmds(lint_cmds, io): - err = False - res = dict() - for lint_cmd in lint_cmds: - if re.match(r"^[a-z]+:.*", lint_cmd): - pieces = lint_cmd.split(":") - lang = pieces[0] - cmd = lint_cmd[len(lang) + 1 :] - lang = lang.strip() - else: - lang = None - cmd = lint_cmd - - cmd = cmd.strip() - - if cmd: - res[lang] = cmd - else: - io.tool_error(f'Unable to parse --lint-cmd "{lint_cmd}"') - io.tool_output('The arg should be "language: cmd --args ..."') - io.tool_output('For example: --lint-cmd "python: flake8 --select=E9"') - err = True - if err: - return - return res - - -def register_models(git_root, model_settings_fname, io, verbose=False): - model_settings_files = generate_search_path_list( - ".aider.model.settings.yml", git_root, model_settings_fname - ) - - try: - files_loaded = models.register_models(model_settings_files) - if len(files_loaded) > 0: - if verbose: - io.tool_output("Loaded model settings from:") - for file_loaded in files_loaded: - io.tool_output(f" - {file_loaded}") # noqa: E221 - elif verbose: - io.tool_output("No model settings files loaded") - - if ( - model_settings_fname - and model_settings_fname not in files_loaded - and model_settings_fname != ".aider.model.settings.yml" - ): - io.tool_warning(f"Model Settings File Not Found: {model_settings_fname}") - except Exception as e: - io.tool_error(f"Error loading aider model settings: {e}") - return 1 - - if verbose: - io.tool_output("Searched for model settings files:") - for file in model_settings_files: - io.tool_output(f" - {file}") - - return None - - -def load_dotenv_files(git_root, dotenv_fname, encoding="utf-8"): - # Standard .env file search path - dotenv_files = generate_search_path_list( - ".env", - git_root, - dotenv_fname, - ) - - # Explicitly add the OAuth keys file to the beginning of the list - oauth_keys_file = Path.home() / ".aider" / "oauth-keys.env" - if oauth_keys_file.exists(): - # Insert at the beginning so it's loaded first (and potentially overridden) - dotenv_files.insert(0, str(oauth_keys_file.resolve())) - # Remove duplicates if it somehow got included by generate_search_path_list - dotenv_files = list(dict.fromkeys(dotenv_files)) - - loaded = [] - for fname in dotenv_files: - try: - if Path(fname).exists(): - load_dotenv(fname, override=True, encoding=encoding) - loaded.append(fname) - except OSError as e: - print(f"OSError loading {fname}: {e}") - except Exception as e: - print(f"Error loading {fname}: {e}") - return loaded - - -def register_litellm_models(git_root, model_metadata_fname, io, verbose=False): - model_metadata_files = [] - - # Add the resource file path - resource_metadata = importlib_resources.files("aider.resources").joinpath("model-metadata.json") - model_metadata_files.append(str(resource_metadata)) - - model_metadata_files += generate_search_path_list( - ".aider.model.metadata.json", git_root, model_metadata_fname - ) - - try: - model_metadata_files_loaded = models.register_litellm_models(model_metadata_files) - if len(model_metadata_files_loaded) > 0 and verbose: - io.tool_output("Loaded model metadata from:") - for model_metadata_file in model_metadata_files_loaded: - io.tool_output(f" - {model_metadata_file}") # noqa: E221 - - if ( - model_metadata_fname - and model_metadata_fname not in model_metadata_files_loaded - and model_metadata_fname != ".aider.model.metadata.json" - ): - io.tool_warning(f"Model Metadata File Not Found: {model_metadata_fname}") - except Exception as e: - io.tool_error(f"Error loading model metadata models: {e}") - return 1 - - -def load_model_overrides(git_root, model_overrides_fname, io, verbose=False): - """Load model tag overrides from a YAML file.""" - from pathlib import Path - - import yaml - - model_overrides_files = generate_search_path_list( - ".aider.model.overrides.yml", git_root, model_overrides_fname - ) - - overrides = {} - files_loaded = [] - - for fname in model_overrides_files: - try: - if Path(fname).exists(): - with open(fname, "r") as f: - content = yaml.safe_load(f) - if content: - # Merge overrides, later files override earlier ones - for model_name, tags in content.items(): - if model_name not in overrides: - overrides[model_name] = {} - overrides[model_name].update(tags) - files_loaded.append(fname) - except Exception as e: - io.tool_error(f"Error loading model overrides from {fname}: {e}") - - if len(files_loaded) > 0 and verbose: - io.tool_output("Loaded model overrides from:") - for file_loaded in files_loaded: - io.tool_output(f" - {file_loaded}") - - if ( - model_overrides_fname - and model_overrides_fname not in files_loaded - and model_overrides_fname != ".aider.model.overrides.yml" - ): - io.tool_warning(f"Model Overrides File Not Found: {model_overrides_fname}") - - return overrides - - -def load_model_overrides_from_string(model_overrides_str, io): - """Load model tag overrides from a JSON/YAML string.""" - import json - - import yaml - - overrides = {} - - if not model_overrides_str: - return overrides - - try: - # First try to parse as JSON - try: - content = json.loads(model_overrides_str) - except json.JSONDecodeError: - # If JSON fails, try YAML - content = yaml.safe_load(model_overrides_str) - - if content and isinstance(content, dict): - for model_name, tags in content.items(): - if model_name not in overrides: - overrides[model_name] = {} - overrides[model_name].update(tags) - - return overrides - except Exception as e: - io.tool_error(f"Error parsing model overrides string: {e}") - return {} - - -async def sanity_check_repo(repo, io): - if not repo: - return True - - if not repo.repo.working_tree_dir: - io.tool_error("The git repo does not seem to have a working tree?") - return False - - bad_ver = False - try: - repo.get_tracked_files() - if not repo.git_repo_error: - return True - error_msg = str(repo.git_repo_error) - except UnicodeDecodeError as exc: - error_msg = ( - "Failed to read the Git repository. This issue is likely caused by a path encoded " - f'in a format different from the expected encoding "{sys.getfilesystemencoding()}".\n' - f"Internal error: {str(exc)}" - ) - except ANY_GIT_ERROR as exc: - error_msg = str(exc) - bad_ver = "version in (1, 2)" in error_msg - except AssertionError as exc: - error_msg = str(exc) - bad_ver = True - - if bad_ver: - io.tool_error("Aider only works with git repos with version number 1 or 2.") - io.tool_output("You may be able to convert your repo: git update-index --index-version=2") - io.tool_output("Or run aider --no-git to proceed without using git.") - await io.offer_url( - urls.git_index_version, "Open documentation url for more info?", acknowledge=True - ) - return False - - io.tool_error("Unable to read git repository, it may be corrupt?") - io.tool_output(error_msg) - return False - - -PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) -log_file = None -file_excludelist = { - "get_bottom_toolbar": True, - "": True, - "is_active": True, - "auto_save_session": True, - "input_task": True, - "output_task": True, - "check_output_queue": True, - "_animate_spinner": True, - "handle_output_message": True, - "update_spinner": True, -} - - -def custom_tracer(frame, event, arg): - try: - import os - except Exception: - return None - - global log_file - if not log_file: - os.makedirs(".aider/logs/", exist_ok=True) - log_file = open(".aider/logs/debug.log", "w", buffering=1) - - # Get the absolute path of the file where the code is executing - filename = os.path.abspath(frame.f_code.co_filename) - - # --- THE FILTERING LOGIC --- - # Only proceed if the file path is INSIDE the project root - if not filename.startswith(PROJECT_ROOT): - return None # Returning None means no local trace function for this scope - - if filename.endswith("repo.py"): - return None - - # If it's your code, trace the call - if event == "call": - func_name = frame.f_code.co_name - line_no = frame.f_lineno - - if func_name not in file_excludelist: - log_file.write( - f"-> CALL: {func_name}() in {os.path.basename(filename)}:{line_no} -" - f" {time.time()}\n" - ) - - if event == "return": - func_name = frame.f_code.co_name - line_no = frame.f_lineno - - if func_name not in file_excludelist: - log_file.write( - f"<- RETURN: {func_name}() in {os.path.basename(filename)}:{line_no} -" - f" {time.time()}\n" - ) - - # Must return the trace function (or a local one) for subsequent events - return custom_tracer - - -def main(argv=None, input=None, output=None, force_git_root=None, return_coder=False): - # Asyncio run workaround for Windows in Python 3.12+. Required from 3.16+ - if sys.platform == "win32": - if sys.version_info >= (3, 12) and hasattr(asyncio, "SelectorEventLoop"): - return asyncio.run( - main_async(argv, input, output, force_git_root, return_coder), - loop_factory=asyncio.SelectorEventLoop, - ) - - return asyncio.run(main_async(argv, input, output, force_git_root, return_coder)) - - -async def main_async(argv=None, input=None, output=None, force_git_root=None, return_coder=False): - report_uncaught_exceptions() - - if argv is None: - argv = sys.argv[1:] - - if git is None: - git_root = None - elif force_git_root: - git_root = force_git_root - else: - git_root = get_git_root() - - conf_fname = Path(".aider.conf.yml") - - default_config_files = [] - try: - default_config_files += [conf_fname.resolve()] # CWD - except OSError: - pass - - if git_root: - 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 - default_config_files = list(map(str, default_config_files)) - - parser = get_parser(default_config_files, git_root) - try: - args, unknown = parser.parse_known_args(argv) - except AttributeError as e: - if all(word in str(e) for word in ["bool", "object", "has", "no", "attribute", "strip"]): - if check_config_files_for_yes(default_config_files): - return await graceful_exit(None, 1) - raise e - - if args.verbose: - print("Config files search order, if no --config:") - for file in default_config_files: - exists = "(exists)" if Path(file).exists() else "" - print(f" - {file} {exists}") - - default_config_files.reverse() - - parser = get_parser(default_config_files, git_root) - - args, unknown = parser.parse_known_args(argv) - - # Load the .env file specified in the arguments - loaded_dotenvs = load_dotenv_files(git_root, args.env_file, args.encoding) - - # Parse again to include any arguments that might have been defined in .env - args, unknown = parser.parse_known_args(argv) - set_args_error_data(args) - - if len(unknown): - print("Unknown Args: ", unknown) - - # Convert YAML dict arguments to JSON strings for compatibility - # configargparse.YAMLConfigFileParser converts YAML to Python objects, - # but some arguments expect JSON strings - if hasattr(args, "agent_config") and args.agent_config is not None: - args.agent_config = convert_yaml_to_json_string(args.agent_config) - - if hasattr(args, "tui_config") and args.tui_config is not None: - args.tui_config = convert_yaml_to_json_string(args.tui_config) - - if hasattr(args, "mcp_servers") and args.mcp_servers is not None: - args.mcp_servers = convert_yaml_to_json_string(args.mcp_servers) - - if args.debug: - global log_file - os.makedirs(".aider/logs/", exist_ok=True) - log_file = open(".aider/logs/debug.log", "w", buffering=1) - sys.settrace(custom_tracer) - - if args.shell_completions: - # Ensure parser.prog is set for shtab, though it should be by default - parser.prog = "aider" - print(shtab.complete(parser, shell=args.shell_completions)) - return await graceful_exit(None, 0) - - if git is None: - args.git = False - - if not args.verify_ssl: - import httpx - - os.environ["SSL_VERIFY"] = "" - litellm._load_litellm() - litellm._lazy_module.client_session = httpx.Client(verify=False) - litellm._lazy_module.aclient_session = httpx.AsyncClient(verify=False) - # Set verify_ssl on the model_info_manager - models.model_info_manager.set_verify_ssl(False) - - if args.timeout: - models.request_timeout = args.timeout - - if args.dark_mode: - args.user_input_color = "#32FF32" - args.tool_error_color = "#FF3333" - args.tool_warning_color = "#FFFF00" - args.assistant_output_color = "#00FFFF" - args.code_theme = "monokai" - - if args.light_mode: - args.user_input_color = "green" - args.tool_error_color = "red" - args.tool_warning_color = "#FFA500" - args.assistant_output_color = "blue" - args.code_theme = "default" - - if return_coder and args.yes_always is None: - args.yes_always = True - - if args.yes_always_commands: - args.yes_always = True - - editing_mode = EditingMode.VI if args.vim else EditingMode.EMACS - - def get_io(pretty): - return InputOutput( - pretty, - args.yes_always, - args.input_history_file, - args.chat_history_file, - input=input, - output=output, - user_input_color=args.user_input_color, - tool_output_color=args.tool_output_color, - tool_warning_color=args.tool_warning_color, - tool_error_color=args.tool_error_color, - completion_menu_color=args.completion_menu_color, - completion_menu_bg_color=args.completion_menu_bg_color, - completion_menu_current_color=args.completion_menu_current_color, - completion_menu_current_bg_color=args.completion_menu_current_bg_color, - assistant_output_color=args.assistant_output_color, - code_theme=args.code_theme, - dry_run=args.dry_run, - encoding=args.encoding, - line_endings=args.line_endings, - editingmode=editing_mode, - fancy_input=args.fancy_input, - multiline_mode=args.multiline, - notifications=args.notifications, - notifications_command=args.notifications_command, - verbose=args.verbose, - ) - - # Validate TUI arguments - validate_tui_args(args) - - # TUI mode - create TUI-specific IO - output_queue = None - input_queue = None - pre_init_io = get_io(args.pretty) - if args.tui or (args.tui is None and not args.linear_output): - try: - from aider.tui import create_tui_io - - args.tui = True - args.linear_output = True - print("Starting aider TUI...", flush=True) - io, output_queue, input_queue = create_tui_io(args, editing_mode) - except ImportError as e: - print("Error: --tui requires 'textual' package") - print("Install with: pip install aider-ce[tui]") - print(f"Import error: {e}") - sys.exit(1) - else: - io = pre_init_io - - # Only do CLI-specific initialization if not in TUI mode - if not args.tui: - try: - io.rule() - except UnicodeEncodeError as err: - if not io.pretty: - raise err - io = get_io(False) - io.tool_warning("Terminal does not support pretty output (UnicodeDecodeError)") - - # Process any environment variables set via --set-env - if args.set_env: - for env_setting in args.set_env: - try: - name, value = env_setting.split("=", 1) - os.environ[name.strip()] = value.strip() - except ValueError: - io.tool_error(f"Invalid --set-env format: {env_setting}") - io.tool_output("Format should be: ENV_VAR_NAME=value") - return await graceful_exit(None, 1) - - # Process any API keys set via --api-key - if args.api_key: - for api_setting in args.api_key: - try: - provider, key = api_setting.split("=", 1) - env_var = f"{provider.strip().upper()}_API_KEY" - os.environ[env_var] = key.strip() - except ValueError: - io.tool_error(f"Invalid --api-key format: {api_setting}") - io.tool_output("Format should be: provider=key") - return await graceful_exit(None, 1) - - if args.anthropic_api_key: - os.environ["ANTHROPIC_API_KEY"] = args.anthropic_api_key - - if args.openai_api_key: - os.environ["OPENAI_API_KEY"] = args.openai_api_key - - # Handle deprecated model shortcut args - handle_deprecated_model_args(args, io) - if args.openai_api_base: - os.environ["OPENAI_API_BASE"] = args.openai_api_base - if args.openai_api_version: - io.tool_warning( - "--openai-api-version is deprecated, use --set-env OPENAI_API_VERSION=" - ) - os.environ["OPENAI_API_VERSION"] = args.openai_api_version - if args.openai_api_type: - io.tool_warning("--openai-api-type is deprecated, use --set-env OPENAI_API_TYPE=") - os.environ["OPENAI_API_TYPE"] = args.openai_api_type - if args.openai_organization_id: - io.tool_warning( - "--openai-organization-id is deprecated, use --set-env OPENAI_ORGANIZATION=" - ) - os.environ["OPENAI_ORGANIZATION"] = args.openai_organization_id - - if args.verbose: - for fname in loaded_dotenvs: - io.tool_output(f"Loaded {fname}") - - # Expand glob patterns in files and file arguments - all_files = args.files + (args.file or []) - all_files = utils.expand_glob_patterns(all_files) - fnames = [str(Path(fn).resolve()) for fn in all_files] - - # Expand glob patterns in read arguments - read_patterns = args.read or [] - read_expanded = utils.expand_glob_patterns(read_patterns) - read_only_fnames = [] - for fn in read_expanded: - path = Path(fn).expanduser().resolve() - if path.is_dir(): - read_only_fnames.extend(str(f) for f in path.rglob("*") if f.is_file()) - else: - read_only_fnames.append(str(path)) - - if len(all_files) > 1: - good = True - for fname in all_files: - if Path(fname).is_dir(): - io.tool_error(f"{fname} is a directory, not provided alone.") - good = False - if not good: - io.tool_output( - "Provide either a single directory of a git repo, or a list of one or more files." - ) - - return await graceful_exit(None, 1) - - git_dname = None - if len(all_files) == 1: - if Path(all_files[0]).is_dir(): - if args.git: - git_dname = str(Path(all_files[0]).resolve()) - fnames = [] - else: - io.tool_error(f"{all_files[0]} is a directory, but --no-git selected.") - - return await graceful_exit(None, 1) - - # We can't know the git repo for sure until after parsing the args. - # If we guessed wrong, reparse because that changes things like - # the location of the config.yml and history files. - if args.git and not force_git_root and git is not None: - right_repo_root = guessed_wrong_repo(io, git_root, fnames, git_dname) - if right_repo_root: - return await main_async(argv, input, output, right_repo_root, return_coder=return_coder) - - if args.just_check_update: - update_available = await check_version(io, just_check=True, verbose=args.verbose) - - return await graceful_exit(None, 0 if not update_available else 1) - - if args.install_main_branch: - success = await install_from_main_branch(io) - - return await graceful_exit(None, 0 if success else 1) - - if args.upgrade: - success = await install_upgrade(io) - - return await graceful_exit(None, 0 if success else 1) - - if args.check_update: - await check_version(io, verbose=args.verbose) - - if args.verbose: - show = format_settings(parser, args) - io.tool_output(show) - - cmd_line = " ".join(sys.argv) - cmd_line = scrub_sensitive_info(args, cmd_line) - io.tool_output(cmd_line, log_only=True) - - is_first_run = is_first_run_of_new_version(io, verbose=args.verbose) - await check_and_load_imports(io, is_first_run, verbose=args.verbose) - - register_models(git_root, args.model_settings_file, io, verbose=args.verbose) - register_litellm_models(git_root, args.model_metadata_file, io, verbose=args.verbose) - - if args.list_models: - models.print_matching_models(io, args.list_models) - - return await graceful_exit(None) - - # Process any command line aliases - if args.alias: - for alias_def in args.alias: - # Split on first colon only - parts = alias_def.split(":", 1) - if len(parts) != 2: - io.tool_error(f"Invalid alias format: {alias_def}") - io.tool_output("Format should be: alias:model-name") - - return await graceful_exit(None, 1) - alias, model = parts - models.MODEL_ALIASES[alias.strip()] = model.strip() - - selected_model_name = await select_default_model(args, io) - if not selected_model_name: - # Error message is handled within select_default_model - # It might have already offered OAuth if no model/keys were found. - # If it failed here, we exit. - return await graceful_exit(None, 1) - args.model = selected_model_name # Update args with the selected model - - # Load model overrides if specified - model_overrides = {} - - # First load from file if specified - if args.model_overrides_file: - model_overrides = load_model_overrides( - git_root, args.model_overrides_file, io, verbose=args.verbose - ) - - # Then load from direct JSON/YAML string if specified (overrides file) - if args.model_overrides: - direct_overrides = load_model_overrides_from_string(args.model_overrides, io) - # Merge direct overrides with file overrides (direct takes precedence) - for model_name, tags in direct_overrides.items(): - if model_name not in model_overrides: - model_overrides[model_name] = {} - model_overrides[model_name].update(tags) - - # Build an index from full "base:suffix" names to (base_model, override_dict) - # so we don't have to parse/split user-provided model names at runtime. - override_index = {} - for base_model, suffixes in model_overrides.items(): - if not isinstance(suffixes, dict): - continue - for suffix, cfg in suffixes.items(): - if not isinstance(cfg, dict): - continue - full_name = f"{base_model}:{suffix}" - # Later entries override earlier ones - override_index[full_name] = (base_model, cfg) - - def apply_model_overrides(model_name): - """Return (effective_model_name, override_kwargs) for a given model_name. - - If model_name exactly matches a configured "base:suffix" override, we - switch to the base model and apply that override dict. Otherwise we - leave the name unchanged and return empty overrides. - """ - if not model_name: - return model_name, {} - - # Check for copy-paste prefix - prefix = "" - if model_name.startswith(models.COPY_PASTE_PREFIX): - prefix = models.COPY_PASTE_PREFIX - model_name = model_name[len(prefix) :] - - # Check if the model_name (without prefix) is in override_index - entry = override_index.get(model_name) - if not entry: - # No override found, return original name with prefix - model_name = prefix + model_name - return model_name, {} - - base_model, cfg = entry - # Re-add prefix if it was present - model_name = prefix + base_model - return model_name, cfg.copy() - - # Apply overrides (if any) to the selected models - main_model_name, main_model_overrides = apply_model_overrides(args.model) - weak_model_name, weak_model_overrides = apply_model_overrides(args.weak_model) - editor_model_name, editor_model_overrides = apply_model_overrides(args.editor_model) - - # Create weak model if specified with overrides - weak_model_obj = None - if weak_model_name: - weak_model_obj = models.Model( - weak_model_name, - weak_model=False, - verbose=args.verbose, - io=io, - override_kwargs=weak_model_overrides, - ) - - # Create editor model if specified with overrides - editor_model_obj = None - if editor_model_name: - editor_model_obj = models.Model( - editor_model_name, - editor_model=False, - verbose=args.verbose, - io=io, - override_kwargs=editor_model_overrides, - ) - - # Check if an OpenRouter model was selected/specified but the key is missing - if main_model_name.startswith("openrouter/") and not os.environ.get("OPENROUTER_API_KEY"): - io.tool_warning( - f"The specified model '{main_model_name}' requires an OpenRouter API key, which was not" - " found." - ) - # Attempt OAuth flow because the specific model needs it - if await offer_openrouter_oauth(io): - # OAuth succeeded, the key should now be in os.environ. - # Check if the key is now present after the flow. - if os.environ.get("OPENROUTER_API_KEY"): - io.tool_output( - "OpenRouter successfully connected." - ) # Inform user connection worked - else: - # This case should ideally not happen if offer_openrouter_oauth succeeded - # but check defensively. - io.tool_error( - "OpenRouter authentication seemed successful, but the key is still missing." - ) - return await graceful_exit(None, 1) - else: - # OAuth failed or was declined by the user - io.tool_error( - f"Unable to proceed without an OpenRouter API key for model '{main_model_name}'." - ) - await io.offer_url( - urls.models_and_keys, "Open documentation URL for more info?", acknowledge=True - ) - return await graceful_exit(None, 1) - - main_model = models.Model( - main_model_name, - weak_model=weak_model_obj, - editor_model=editor_model_obj, - editor_edit_format=args.editor_edit_format, - verbose=args.verbose, - io=io, - override_kwargs=main_model_overrides, - ) - - if args.copy_paste and main_model.copy_paste_transport == "api": - main_model.enable_copy_paste_mode() - - # Check if deprecated remove_reasoning is set - if main_model.remove_reasoning is not None: - io.tool_warning( - "Model setting 'remove_reasoning' is deprecated, please use 'reasoning_tag' instead." - ) - - # Set reasoning effort and thinking tokens if specified - if args.reasoning_effort is not None: - # Apply if check is disabled or model explicitly supports it - if not args.check_model_accepts_settings or ( - main_model.accepts_settings and "reasoning_effort" in main_model.accepts_settings - ): - main_model.set_reasoning_effort(args.reasoning_effort) - - if args.thinking_tokens is not None: - # Apply if check is disabled or model explicitly supports it - if not args.check_model_accepts_settings or ( - main_model.accepts_settings and "thinking_tokens" in main_model.accepts_settings - ): - main_model.set_thinking_tokens(args.thinking_tokens) - - # Show warnings about unsupported settings that are being ignored - if args.check_model_accepts_settings: - settings_to_check = [ - {"arg": args.reasoning_effort, "name": "reasoning_effort"}, - {"arg": args.thinking_tokens, "name": "thinking_tokens"}, - ] - - for setting in settings_to_check: - if setting["arg"] is not None and ( - not main_model.accepts_settings - or setting["name"] not in main_model.accepts_settings - ): - io.tool_warning( - f"Warning: {main_model.name} does not support '{setting['name']}', ignoring." - ) - io.tool_output( - f"Use --no-check-model-accepts-settings to force the '{setting['name']}'" - " setting." - ) - - if args.copy_paste and args.edit_format is None: - if main_model.edit_format in ("diff", "whole", "diff-fenced"): - main_model.edit_format = "editor-" + main_model.edit_format - - if args.verbose: - io.tool_output("Model metadata:") - io.tool_output(json.dumps(main_model.info, indent=4)) - - io.tool_output("Model settings:") - for attr in sorted(fields(ModelSettings), key=lambda x: x.name): - val = getattr(main_model, attr.name) - val = json.dumps(val, indent=4) - io.tool_output(f"{attr.name}: {val}") - - lint_cmds = parse_lint_cmds(args.lint_cmd, io) - if lint_cmds is None: - return await graceful_exit(None, 1) - - repo = None - if args.git: - try: - repo = GitRepo( - io, - fnames, - git_dname, - args.aiderignore, - models=main_model.commit_message_models(), - attribute_author=args.attribute_author, - attribute_committer=args.attribute_committer, - attribute_commit_message_author=args.attribute_commit_message_author, - attribute_commit_message_committer=args.attribute_commit_message_committer, - commit_prompt=args.commit_prompt, - subtree_only=args.subtree_only, - git_commit_verify=args.git_commit_verify, - attribute_co_authored_by=args.attribute_co_authored_by, # Pass the arg - ) - except FileNotFoundError: - pass - - if not args.skip_sanity_check_repo: - if not await sanity_check_repo(repo, io): - return await graceful_exit(None, 1) - - commands = Commands( - io, - None, - voice_language=args.voice_language, - voice_input_device=args.voice_input_device, - voice_format=args.voice_format, - verify_ssl=args.verify_ssl, - args=args, - parser=parser, - verbose=args.verbose, - editor=args.editor, - original_read_only_fnames=read_only_fnames, - ) - - summarizer = ChatSummary( - [main_model.weak_model, main_model], - args.max_chat_history_tokens or main_model.max_chat_history_tokens, - ) - - if args.cache_prompts and args.map_refresh == "auto": - args.map_refresh = "files" - - if not main_model.streaming: - if args.stream: - io.tool_warning( - f"Warning: Streaming is not supported by {main_model.name}. Disabling streaming." - " Set stream: false in config file or use --no-stream to skip this warning." - ) - args.stream = False - - if args.map_tokens is None: - map_tokens = main_model.get_repo_map_tokens() - else: - map_tokens = args.map_tokens - - if args.enable_context_compaction and ( - args.context_compaction_max_tokens is None or args.context_compaction_max_tokens < 1 - ): - max_input_tokens = main_model.info.get("max_input_tokens") - ratio = 0.8 - - if args.context_compaction_max_tokens: - ratio = args.context_compaction_max_tokens - - if max_input_tokens: - args.context_compaction_max_tokens = int(max_input_tokens * ratio) - - try: - # Load MCP servers from config string or file - mcp_servers = load_mcp_servers( - args.mcp_servers, args.mcp_servers_file, io, args.verbose, args.mcp_transport - ) - - if not mcp_servers: - mcp_servers = [] - - coder = await Coder.create( - main_model=main_model, - edit_format=args.edit_format, - io=io, - args=args, - repo=repo, - fnames=fnames, - read_only_fnames=read_only_fnames, - read_only_stubs_fnames=[], - show_diffs=args.show_diffs, - auto_commits=args.auto_commits, - dirty_commits=args.dirty_commits, - dry_run=args.dry_run, - map_tokens=map_tokens, - verbose=args.verbose, - stream=args.stream, - use_git=args.git, - restore_chat_history=args.restore_chat_history, - auto_lint=args.auto_lint, - auto_test=args.auto_test, - lint_cmds=lint_cmds, - test_cmd=args.test_cmd, - commands=commands, - summarizer=summarizer, - map_refresh=args.map_refresh, - cache_prompts=args.cache_prompts, - map_mul_no_files=args.map_multiplier_no_files, - map_max_line_length=args.map_max_line_length, - num_cache_warming_pings=args.cache_keepalive_pings, - suggest_shell_commands=args.suggest_shell_commands, - chat_language=args.chat_language, - commit_language=args.commit_language, - detect_urls=args.detect_urls, - auto_copy_context=args.copy_paste, - auto_accept_architect=args.auto_accept_architect, - mcp_servers=mcp_servers, - add_gitignore_files=args.add_gitignore_files, - enable_context_compaction=args.enable_context_compaction, - context_compaction_max_tokens=args.context_compaction_max_tokens, - context_compaction_summary_tokens=args.context_compaction_summary_tokens, - map_cache_dir=args.map_cache_dir, - repomap_in_memory=args.map_memory_cache, - linear_output=args.linear_output, - ) - - if args.show_model_warnings: - problem = await models.sanity_check_models(pre_init_io, main_model) - if problem: - pre_init_io.tool_output("You can skip this check with --no-show-model-warnings") - - try: - await pre_init_io.offer_url( - urls.model_warnings, - "Open documentation url for more info?", - acknowledge=True, - ) - pre_init_io.tool_output() - except KeyboardInterrupt: - return await graceful_exit(coder, 1) - - if args.git: - git_root = await setup_git(git_root, pre_init_io) - if args.gitignore: - await check_gitignore(git_root, pre_init_io) - - except UnknownEditFormat as err: - pre_init_io.tool_error(str(err)) - await pre_init_io.offer_url( - urls.edit_formats, "Open documentation about edit formats?", acknowledge=True - ) - - return await graceful_exit(None, 1) - - except ValueError as err: - pre_init_io.tool_error(str(err)) - - return await graceful_exit(None, 1) - - if return_coder: - return coder - - ignores = [] - if git_root: - ignores.append(str(Path(git_root) / ".gitignore")) - if args.aiderignore: - ignores.append(args.aiderignore) - - if args.watch_files: - file_watcher = FileWatcher( - coder, - gitignores=ignores, - verbose=args.verbose, - root=str(Path.cwd()) if args.subtree_only else None, - ) - coder.file_watcher = file_watcher - - if args.copy_paste: - ClipboardWatcher(coder.io, verbose=args.verbose) - - if args.show_prompts: - coder.cur_messages += [ - dict(role="user", content="Hello!"), - ] - messages = coder.format_messages().all_messages() - utils.show_messages(messages) - - return await graceful_exit(coder) - - if args.lint: - await coder.commands.do_run("lint", "") - - if args.test: - if not args.test_cmd: - io.tool_error("No --test-cmd provided.") - - return await graceful_exit(coder, 1) - await coder.commands.cmd_test(args.test_cmd) - if io.placeholder: - await coder.run(io.placeholder) - - if args.commit: - if args.dry_run: - io.tool_output("Dry run enabled, skipping commit.") - else: - await coder.commands.cmd_commit() - - if args.lint or args.test or args.commit: - return await graceful_exit(coder) - - if args.show_repo_map: - repo_map = coder.get_repo_map() - if repo_map: - io.tool_output(repo_map) - - return await graceful_exit(coder) - - if args.apply: - content = io.read_text(args.apply) - if content is None: - return await graceful_exit(coder) - coder.partial_response_content = content - # For testing #2879 - # from aider.coders.base_coder import all_fences - # coder.fence = all_fences[1] - await coder.apply_updates() - - return await graceful_exit(coder) - - if args.apply_clipboard_edits: - args.edit_format = main_model.editor_edit_format - args.message = "/paste" - - if args.show_release_notes is True: - io.tool_output(f"Opening release notes: {urls.release_notes}") - io.tool_output() - webbrowser.open(urls.release_notes) - elif args.show_release_notes is None and is_first_run: - io.tool_output() - await io.offer_url( - urls.release_notes, - "Would you like to see what's new in this version?", - allow_never=False, - acknowledge=True, - ) - - if git_root and Path.cwd().resolve() != Path(git_root).resolve(): - io.tool_warning( - "Note: in-chat filenames are always relative to the git working dir, not the current" - " working dir." - ) - - io.tool_output(f"Cur working dir: {Path.cwd()}") - io.tool_output(f"Git working dir: {git_root}") - - if args.stream and args.cache_prompts: - io.tool_warning("Cost estimates may be inaccurate when using streaming and caching.") - - if args.load: - await commands.cmd_load(args.load) - - if args.message: - io.add_to_input_history(args.message) - io.tool_output() - try: - await coder.run(with_message=args.message) - except (SwitchCoder, KeyboardInterrupt, SystemExit): - pass - - return await graceful_exit(coder) - - if args.message_file: - try: - message_from_file = io.read_text(args.message_file) - io.tool_output() - await coder.run(with_message=message_from_file) - except (SwitchCoder, KeyboardInterrupt, SystemExit): - pass - except FileNotFoundError: - io.tool_error(f"Message file not found: {args.message_file}") - - return await graceful_exit(coder, 1) - except IOError as e: - io.tool_error(f"Error reading message file: {e}") - - return await graceful_exit(coder, 1) - - return await graceful_exit(coder) - - if args.exit: - return await graceful_exit(coder) - - # Auto-load session if enabled - if args.auto_load: - try: - from aider.sessions import SessionManager - - session_manager = SessionManager(coder, io) - session_manager.load_session( - args.auto_save_session_name if args.auto_save_session_name else "auto-save" - ) - except Exception: - # Don't show errors for auto-load to avoid interrupting the user experience - pass - # TUI mode - launch Textual interface - if args.tui: - from aider.tui import launch_tui - - del pre_init_io - return_code = await launch_tui(coder, output_queue, input_queue, args) - return await graceful_exit(coder, return_code) - - # Standard CLI mode - main loop - while True: - try: - coder.ok_to_warm_cache = bool(args.cache_keepalive_pings) - await coder.run() - - return await graceful_exit(coder) - except SwitchCoder as switch: - coder.ok_to_warm_cache = False - - # Set the placeholder if provided - if hasattr(switch, "placeholder") and switch.placeholder is not None: - io.placeholder = switch.placeholder - - kwargs = dict(io=io, from_coder=coder) - kwargs.update(switch.kwargs) - if "show_announcements" in kwargs: - del kwargs["show_announcements"] - - # Disable cache warming for the new coder - kwargs["num_cache_warming_pings"] = 0 - kwargs["args"] = coder.args - - coder = await Coder.create(**kwargs) - - if switch.kwargs.get("show_announcements") is False: - coder.suppress_announcements_for_next_prompt = True - except SystemExit: - sys.settrace(None) - return await graceful_exit(coder) - - -def is_first_run_of_new_version(io, verbose=False): - """Check if this is the first run of a new version/executable combination""" - installs_file = Path.home() / ".aider" / "installs.json" - key = (__version__, sys.executable) - - # Never show notes for .dev versions - if ".dev" in __version__: - return False - - if verbose: - io.tool_output( - f"Checking imports for version {__version__} and executable {sys.executable}" - ) - io.tool_output(f"Installs file: {installs_file}") - - try: - if installs_file.exists(): - with open(installs_file, "r") as f: - installs = json.load(f) - if verbose: - io.tool_output("Installs file exists and loaded") - else: - installs = {} - if verbose: - io.tool_output("Installs file does not exist, creating new dictionary") - - is_first_run = str(key) not in installs - - if is_first_run: - installs[str(key)] = True - installs_file.parent.mkdir(parents=True, exist_ok=True) - with open(installs_file, "w") as f: - json.dump(installs, f, indent=4) - - return is_first_run - - except Exception as e: - io.tool_warning(f"Error checking version: {e}") - if verbose: - io.tool_output(f"Full exception details: {traceback.format_exc()}") - return True # Safer to assume it's a first run if we hit an error - - -async def check_and_load_imports(io, is_first_run, verbose=False): - try: - if is_first_run: - if verbose: - io.tool_output( - "First run for this version and executable, loading imports synchronously" - ) - try: - load_slow_imports(swallow=False) - except Exception as err: - io.tool_error(str(err)) - io.tool_output("Error loading required imports. Did you install aider properly?") - await io.offer_url( - urls.install_properly, "Open documentation url for more info?", acknowledge=True - ) - sys.exit(1) - - if verbose: - io.tool_output("Imports loaded and installs file updated") - else: - if verbose: - io.tool_output("Not first run, loading imports in background thread") - thread = threading.Thread(target=load_slow_imports) - thread.daemon = True - thread.start() - - except Exception as e: - io.tool_warning(f"Error in loading imports: {e}") - if verbose: - io.tool_output(f"Full exception details: {traceback.format_exc()}") - - -def load_slow_imports(swallow=True): - # These imports are deferred in various ways to - # improve startup time. - # This func is called either synchronously or in a thread - # depending on whether it's been run before for this version and executable. - - try: - import httpx # noqa: F401 - import litellm # noqa: F401 - import numpy # noqa: F401 - except Exception as e: - if not swallow: - raise e - - -async def graceful_exit(coder=None, exit_code=0): - sys.settrace(None) - - if coder: - if hasattr(coder, "_autosave_future"): - await coder._autosave_future - - for server in coder.mcp_servers: - try: - await server.exit_stack.aclose() - except Exception: - pass - - # Commenting since this can sometimes case hanging - # await asyncio.sleep(0.5) - return exit_code - - -if __name__ == "__main__": - status = main() - sys.exit(status) diff --git a/aider/urls.py b/aider/urls.py deleted file mode 100644 index 081266aeb5c..00000000000 --- a/aider/urls.py +++ /dev/null @@ -1,16 +0,0 @@ -website = "https://aider.chat/" -add_all_files = "https://aider.chat/docs/faq.html#how-can-i-add-all-the-files-to-the-chat" -edit_errors = "https://aider.chat/docs/troubleshooting/edit-errors.html" -git = "https://aider.chat/docs/git.html" -enable_playwright = "https://aider.chat/docs/install/optional.html#enable-playwright" -favicon = "https://aider.chat/assets/icons/favicon-32x32.png" -model_warnings = "https://aider.chat/docs/llms/warnings.html" -token_limits = "https://aider.chat/docs/troubleshooting/token-limits.html" -llms = "https://aider.chat/docs/llms.html" -large_repos = "https://aider.chat/docs/faq.html#can-i-use-aider-in-a-large-mono-repo" -github_issues = "https://github.com/dwash96/aider-ce/issues/new" -git_index_version = "https://github.com/Aider-AI/aider/issues/211" -install_properly = "https://aider.chat/docs/troubleshooting/imports.html" -release_notes = "https://github.com/dwash96/aider-ce/releases/latest" -edit_formats = "https://aider.chat/docs/more/edit-formats.html" -models_and_keys = "https://aider.chat/docs/troubleshooting/models-and-keys.html" diff --git a/benchmark/benchmark.py b/benchmark/benchmark.py index 8bc8bdfca8f..3c83ba0b0a6 100755 --- a/benchmark/benchmark.py +++ b/benchmark/benchmark.py @@ -33,14 +33,14 @@ from dotenv import load_dotenv from rich.console import Console -from aider.dump import dump # noqa: F401 +from cecli.dump import dump # noqa: F401 -logger = logging.getLogger("aider.benchmark") +logger = logging.getLogger("cecli.benchmark") # Cache for commit-hash -> version lookup _VERSION_CACHE = {} -BENCHMARK_DNAME = Path(os.environ.get("AIDER_BENCHMARK_DIR", "tmp.benchmarks")) +BENCHMARK_DNAME = Path(os.environ.get("CECLIBENCHMARK_DIR", "tmp.benchmarks")) EXERCISES_DIR_DEFAULT = "cecli-cat" RESULTS_DIR_DEFAULT = "cat-results" @@ -112,7 +112,7 @@ def main( replay: str = typer.Option( None, "--replay", - help="Replay previous .aider.chat.history.md responses from previous benchmark run", + help="Replay previous .cecli.dev.history.md responses from previous benchmark run", ), keywords: str = typer.Option( None, @@ -129,7 +129,7 @@ def main( cont: bool = typer.Option(False, "--cont", help="Continue the (single) matching testdir"), make_new: bool = typer.Option(False, "--new", help="Make a new dated testdir"), no_unit_tests: bool = typer.Option(False, "--no-unit-tests", help="Do not run unit tests"), - no_aider: bool = typer.Option(False, "--no-aider", help="Do not run aider"), + no_cecli: bool = typer.Option(False, "--no-cecli", help="Do not run cecli"), verbose: int = typer.Option(0, "--verbose", "-v", count=True, help="Verbose output"), quiet: bool = typer.Option(False, "--quiet", "-q", help="Quiet output"), tries: int = typer.Option(2, "--tries", "-r", help="Number of tries for running tests"), @@ -139,7 +139,7 @@ def main( None, "--num-ctx", help="Override model context window size" ), read_model_settings: str = typer.Option( - None, "--read-model-settings", help="Load aider model settings from YAML file" + None, "--read-model-settings", help="Load cecli model settings from YAML file" ), reasoning_effort: Optional[str] = typer.Option( None, @@ -170,7 +170,7 @@ def main( " match the nth character (e.g., '^.{2}[4-7]' for the 3rd char in range 4-7)." ), ), - dry: bool = typer.Option(False, "--dry", help="Run in dry mode (no aider, no tests)"), + dry: bool = typer.Option(False, "--dry", help="Run in dry mode (no cecli, no tests)"), ): # setup logging and verbosity if quiet: @@ -182,10 +182,10 @@ def main( logging.basicConfig(level=log_level, format="%(message)s") - from aider import models + from cecli import models if dry: - no_aider = True + no_cecli = True no_unit_tests = True commit_hash = "???????" else: @@ -193,8 +193,8 @@ def main( import git # Heavy import lox # Only needed for threaded runs - from aider import sendchat - from aider.coders import base_coder + from cecli import sendchat + from cecli.coders import base_coder repo = git.Repo(search_parent_directories=True) commit_hash = repo.head.object.hexsha[:7] @@ -209,11 +209,9 @@ def main( return 1 results_dir = resolved_results_dir - if not dry and "AIDER_DOCKER" not in os.environ: + if not dry and "CECLIDOCKER" not in os.environ: logger.warning("Warning: Benchmarking runs unvetted code. Run in a docker container.") - logger.warning( - "Set AIDER_DOCKER in the environment to by-pass this check at your own risk." - ) + logger.warning("Set CECLIDOCKER in the environment to by-pass this check at your own risk.") return # Check dirs exist @@ -333,7 +331,7 @@ def get_exercise_dirs(base_dir, languages=None, sets=None, hash_re=None, legacy= test_dnames = sorted(d.name for d in exercise_dirs) - resource_metadata = importlib_resources.files("aider.resources").joinpath("model-metadata.json") + resource_metadata = importlib_resources.files("cecli.resources").joinpath("model-metadata.json") model_metadata_files_loaded = models.register_litellm_models([resource_metadata]) dump(model_metadata_files_loaded) @@ -356,7 +354,7 @@ def get_exercise_dirs(base_dir, languages=None, sets=None, hash_re=None, legacy= if num_tests > 0: test_dnames = test_dnames[:num_tests] - if not no_aider: + if not no_cecli: # Don't give up when benchmarking LONG_TIMEOUT = 24 * 60 * 60 sendchat.RETRY_TIMEOUT = LONG_TIMEOUT @@ -371,7 +369,7 @@ def get_exercise_dirs(base_dir, languages=None, sets=None, hash_re=None, legacy= edit_format=edit_format, tries=tries, no_unit_tests=no_unit_tests, - no_aider=no_aider, + no_cecli=no_cecli, verbose=verbose, commit_hash=commit_hash, replay=replay, @@ -415,7 +413,7 @@ def load_results(results_dir, stats_languages=None): # BUG20251223 logger.debug(f"Globbing {results_dir} for results") - files = list(results_dir.glob("*/.aider.results.json")) + files = list(results_dir.glob("*/.cecli.results.json")) logger.debug(f"Found {len(files)} files") for fname in files: @@ -452,7 +450,7 @@ def summarize_results(results_dir, verbose, stats_languages=None): lang_to_results = load_results(results_dir, stats_languages) res = SimpleNamespace() - res.total_tests = len(list(Path(results_dir).glob("*/.aider.results.json"))) + res.total_tests = len(list(Path(results_dir).glob("*/.cecli.results.json"))) try: tries = max( @@ -648,7 +646,7 @@ def show(stat, red="red"): if variants["model"]: a_model = set(variants["model"]).pop() - command = f"aider-ce --model {a_model}" + command = f"cecli --model {a_model}" print(f" command: {command}") print(f" date: {date}") @@ -769,7 +767,7 @@ def get_versions(commit_hashes): try: version_src = subprocess.check_output( - ["git", "show", f"{short}:aider/__init__.py"], universal_newlines=True + ["git", "show", f"{short}:cecli/__init__.py"], universal_newlines=True ) match = re.search(r'__version__ = "(.*)"', version_src) ver = match.group(1) if match else None @@ -788,7 +786,7 @@ def get_replayed_content(replay_dname, test_dname): dump(replay_dname, test_dname) test_name = test_dname.name - replay_fname = replay_dname / test_name / ".aider.chat.history.md" + replay_fname = replay_dname / test_name / ".cecli.dev.history.md" dump(replay_fname) res = replay_fname.read_text() @@ -808,7 +806,7 @@ def run_test(original_dname, testdir, *args, **kwargs): logger.error(traceback.format_exc()) testdir = Path(testdir) - results_fname = testdir / ".aider.results.json" + results_fname = testdir / ".cecli.results.json" results_fname.write_text(json.dumps(dict(exception=traceback.format_exc()))) @@ -819,7 +817,7 @@ async def run_test_real( edit_format, tries, no_unit_tests, - no_aider, + no_cecli, verbose, commit_hash, replay, @@ -838,10 +836,10 @@ async def run_test_real( # Lazy imports: only needed in the actual benchmark execution path import git - import aider.prompts.utils.system as prompts - from aider import models - from aider.coders import Coder - from aider.io import InputOutput + import cecli.prompts.utils.system as prompts + from cecli import models + from cecli.coders import Coder + from cecli.io import InputOutput if not os.path.isdir(testdir): if dry: @@ -851,9 +849,9 @@ async def run_test_real( testdir = Path(testdir) - history_fname = testdir / ".aider.chat.history.md" + history_fname = testdir / ".cecli.dev.history.md" - results_fname = testdir / ".aider.results.json" + results_fname = testdir / ".cecli.results.json" if results_fname.exists(): try: res = json.loads(results_fname.read_text()) @@ -987,13 +985,13 @@ async def run_test_real( r = git.Repo.init(testdir) # Set a local identity to avoid commit failures in clean containers with r.config_writer() as cw: - cw.set_value("user", "name", "aider-benchmark") - cw.set_value("user", "email", "aider-benchmark@example.com") + cw.set_value("user", "name", "cecli-benchmark") + cw.set_value("user", "email", "cecli-benchmark@example.com") # Add existing files (solution set and any current files) r.index.add( [str(p.relative_to(testdir)) for p in testdir.rglob("*") if p.is_file()] ) - r.index.commit("Initial commit for aider benchmark") + r.index.commit("Initial commit for cecli benchmark") except Exception as e: logger.debug(f"Warning: failed to initialize git repo in {testdir}: {e}") @@ -1036,7 +1034,7 @@ async def run_test_real( for i in range(tries): start = time.time() - if no_aider: + if no_cecli: pass elif replay: response = get_replayed_content(replay, testdir) @@ -1052,7 +1050,7 @@ async def run_test_real( dur += time.time() - start - if not no_aider: + if not no_cecli: pat = r"^[+]? *[#].* [.][.][.] " # Count the number of lines that match pat in response dump(response) @@ -1176,8 +1174,8 @@ def run_unit_tests(original_dname, testdir, history_fname, test_files): ".py": ["pytest"], ".rs": ["cargo", "test", "--", "--include-ignored"], ".go": ["go", "test", "./..."], - ".js": ["/aider/benchmark/npm-test.sh"], - ".cpp": ["/aider/benchmark/cpp-test.sh"], + ".js": ["/cecli/benchmark/npm-test.sh"], + ".cpp": ["/cecli/benchmark/cpp-test.sh"], ".java": ["./gradlew", "test"], } diff --git a/benchmark/benchmark_classic.py b/benchmark/benchmark_classic.py index da013589884..b6e79240b69 100755 --- a/benchmark/benchmark_classic.py +++ b/benchmark/benchmark_classic.py @@ -29,12 +29,12 @@ from dotenv import load_dotenv from rich.console import Console -from aider.dump import dump # noqa: F401 +from cecli.dump import dump # noqa: F401 # Cache for commit-hash -> version lookup _VERSION_CACHE = {} -BENCHMARK_DNAME = Path(os.environ.get("AIDER_BENCHMARK_DIR", "tmp.benchmarks")) +BENCHMARK_DNAME = Path(os.environ.get("CECLIBENCHMARK_DIR", "tmp.benchmarks")) EXERCISES_DIR_DEFAULT = "polyglot-benchmark" @@ -180,7 +180,7 @@ def main( replay: str = typer.Option( None, "--replay", - help="Replay previous .aider.chat.history.md responses from previous benchmark run", + help="Replay previous .cecli.dev.history.md responses from previous benchmark run", ), keywords: str = typer.Option( None, "--keywords", "-k", help="Only run tests that contain keywords (comma sep)" @@ -191,7 +191,7 @@ def main( cont: bool = typer.Option(False, "--cont", help="Continue the (single) matching testdir"), make_new: bool = typer.Option(False, "--new", help="Make a new dated testdir"), no_unit_tests: bool = typer.Option(False, "--no-unit-tests", help="Do not run unit tests"), - no_aider: bool = typer.Option(False, "--no-aider", help="Do not run aider"), + no_cecli: bool = typer.Option(False, "--no-cecli", help="Do not run cecli"), verbose: bool = typer.Option(False, "--verbose", "-v", help="Verbose output"), stats_only: bool = typer.Option( False, "--stats", "-s", help="Do not run tests, just collect stats on completed tests" @@ -209,7 +209,7 @@ def main( None, "--num-ctx", help="Override model context window size" ), read_model_settings: str = typer.Option( - None, "--read-model-settings", help="Load aider model settings from YAML file" + None, "--read-model-settings", help="Load cecli model settings from YAML file" ), reasoning_effort: Optional[str] = typer.Option( None, "--reasoning-effort", help="Set reasoning effort for models that support it" @@ -259,15 +259,15 @@ def main( import importlib_resources # Used for model metadata registration import lox # Only needed for threaded runs - from aider import models, sendchat - from aider.coders import base_coder + from cecli import models, sendchat + from cecli.coders import base_coder repo = git.Repo(search_parent_directories=True) commit_hash = repo.head.object.hexsha[:7] if repo.is_dirty(): commit_hash += "-dirty" - if "AIDER_DOCKER" not in os.environ: + if "CECLIDOCKER" not in os.environ: print("Warning: benchmarking runs unvetted code from GPT, run in a docker container") return @@ -338,7 +338,7 @@ def get_exercise_dirs(base_dir, languages=None): test_dnames = sorted(str(d.relative_to(original_dname)) for d in exercise_dirs) - resource_metadata = importlib_resources.files("aider.resources").joinpath("model-metadata.json") + resource_metadata = importlib_resources.files("cecli.resources").joinpath("model-metadata.json") model_metadata_files_loaded = models.register_litellm_models([resource_metadata]) dump(model_metadata_files_loaded) @@ -381,7 +381,7 @@ def get_exercise_dirs(base_dir, languages=None): edit_format, tries, no_unit_tests, - no_aider, + no_cecli, verbose, commit_hash, replay, @@ -409,7 +409,7 @@ def get_exercise_dirs(base_dir, languages=None): edit_format, tries, no_unit_tests, - no_aider, + no_cecli, verbose, commit_hash, replay, @@ -460,7 +460,7 @@ def show_diffs(dirnames): print() print(testcase) for outcome, dirname in zip(all_outcomes, dirnames): - print(outcome, f"{dirname}/{testcase}/.aider.chat.history.md") + print(outcome, f"{dirname}/{testcase}/.cecli.dev.history.md") changed = set(testcases) - unchanged print() @@ -475,9 +475,9 @@ def load_results(dirname, stats_languages=None): if stats_languages: languages = [lang.strip().lower() for lang in stats_languages.split(",")] - glob_patterns = [f"{lang}/exercises/practice/*/.aider.results.json" for lang in languages] + glob_patterns = [f"{lang}/exercises/practice/*/.cecli.results.json" for lang in languages] else: - glob_patterns = ["*/exercises/practice/*/.aider.results.json"] + glob_patterns = ["*/exercises/practice/*/.cecli.results.json"] for pattern in glob_patterns: for fname in dirname.glob(pattern): @@ -682,7 +682,7 @@ def show(stat, red="red"): if variants["model"]: a_model = set(variants["model"]).pop() - command = f"aider-ce --model {a_model}" + command = f"cecli --model {a_model}" print(f" command: {command}") print(f" date: {date}") @@ -803,7 +803,7 @@ def get_versions(commit_hashes): try: version_src = subprocess.check_output( - ["git", "show", f"{short}:aider/__init__.py"], universal_newlines=True + ["git", "show", f"{short}:cecli/__init__.py"], universal_newlines=True ) match = re.search(r'__version__ = "(.*)"', version_src) ver = match.group(1) if match else None @@ -822,7 +822,7 @@ def get_replayed_content(replay_dname, test_dname): dump(replay_dname, test_dname) test_name = test_dname.name - replay_fname = replay_dname / test_name / ".aider.chat.history.md" + replay_fname = replay_dname / test_name / ".cecli.dev.history.md" dump(replay_fname) res = replay_fname.read_text() @@ -842,7 +842,7 @@ def run_test(original_dname, testdir, *args, **kwargs): traceback.print_exc() testdir = Path(testdir) - results_fname = testdir / ".aider.results.json" + results_fname = testdir / ".cecli.results.json" results_fname.write_text(json.dumps(dict(exception=traceback.format_exc()))) @@ -853,7 +853,7 @@ def run_test_real( edit_format, tries, no_unit_tests, - no_aider, + no_cecli, verbose, commit_hash, replay, @@ -870,10 +870,10 @@ def run_test_real( # Lazy imports: only needed in the actual benchmark execution path import git - import aider.prompts.utils.system as prompts - from aider import models - from aider.coders import Coder - from aider.io import InputOutput + import cecli.prompts.utils.system as prompts + from cecli import models + from cecli.coders import Coder + from cecli.io import InputOutput if not os.path.isdir(testdir): print("Not a dir:", testdir) @@ -881,9 +881,9 @@ def run_test_real( testdir = Path(testdir) - history_fname = testdir / ".aider.chat.history.md" + history_fname = testdir / ".cecli.dev.history.md" - results_fname = testdir / ".aider.results.json" + results_fname = testdir / ".cecli.results.json" if results_fname.exists(): try: res = json.loads(results_fname.read_text()) @@ -1005,11 +1005,11 @@ def run_test_real( r = git.Repo.init(testdir) # Set a local identity to avoid commit failures in clean containers with r.config_writer() as cw: - cw.set_value("user", "name", "aider-benchmark") - cw.set_value("user", "email", "aider-benchmark@example.com") + cw.set_value("user", "name", "cecli-benchmark") + cw.set_value("user", "email", "cecli-benchmark@example.com") # Add existing files (solution set and any current files) r.index.add([str(p.relative_to(testdir)) for p in testdir.rglob("*") if p.is_file()]) - r.index.commit("Initial commit for aider benchmark") + r.index.commit("Initial commit for cecli benchmark") except Exception as e: if verbose: print(f"Warning: failed to initialize git repo in {testdir}: {e}") @@ -1053,7 +1053,7 @@ def run_test_real( for i in range(tries): start = time.time() - if no_aider: + if no_cecli: pass elif replay: response = get_replayed_content(replay, testdir) @@ -1069,7 +1069,7 @@ def run_test_real( dur += time.time() - start - if not no_aider: + if not no_cecli: pat = r"^[+]? *[#].* [.][.][.] " # Count the number of lines that match pat in response dump(response) @@ -1192,8 +1192,8 @@ def run_unit_tests(original_dname, testdir, history_fname, test_files): ".py": ["pytest"], ".rs": ["cargo", "test", "--", "--include-ignored"], ".go": ["go", "test", "./..."], - ".js": ["/aider/benchmark/npm-test.sh"], - ".cpp": ["/aider/benchmark/cpp-test.sh"], + ".js": ["/cecli/benchmark/npm-test.sh"], + ".cpp": ["/cecli/benchmark/cpp-test.sh"], ".java": ["./gradlew", "test"], } diff --git a/benchmark/over_time.py b/benchmark/over_time.py index efe07be6e18..ba7d6eda15d 100644 --- a/benchmark/over_time.py +++ b/benchmark/over_time.py @@ -133,7 +133,7 @@ def plot_model_series(self, ax: plt.Axes, models: List[ModelData]): def set_labels_and_style(self, ax: plt.Axes): ax.set_xlabel("Model release date", fontsize=18, color="#555") ax.set_ylabel( - "Aider code editing benchmark,\npercent completed correctly", fontsize=18, color="#555" + "cecli code editing benchmark,\npercent completed correctly", fontsize=18, color="#555" ) ax.set_title("LLM code editing skill by model release date", fontsize=20) ax.set_ylim(30, 90) @@ -141,8 +141,8 @@ def set_labels_and_style(self, ax: plt.Axes): plt.tight_layout(pad=1.0) def save_and_display(self, fig: plt.Figure): - plt.savefig("aider/website/assets/models-over-time.png") - plt.savefig("aider/website/assets/models-over-time.svg") + plt.savefig("cecli/website/assets/models-over-time.png") + plt.savefig("cecli/website/assets/models-over-time.svg") imgcat(fig) def plot(self, yaml_file: str): @@ -155,13 +155,13 @@ def plot(self, yaml_file: str): def main(): plotter = BenchmarkPlotter() - models = plotter.load_data("aider/website/_data/edit_leaderboard.yml") + models = plotter.load_data("cecli/website/_data/edit_leaderboard.yml") # Print release dates and model names for model in sorted(models, key=lambda x: x.release_date): print(f"{model.release_date}: {model.name}") - plotter.plot("aider/website/_data/edit_leaderboard.yml") + plotter.plot("cecli/website/_data/edit_leaderboard.yml") if __name__ == "__main__": diff --git a/benchmark/plots.py b/benchmark/plots.py index 55ee33a209d..33de2867f59 100644 --- a/benchmark/plots.py +++ b/benchmark/plots.py @@ -2,7 +2,7 @@ import numpy as np from imgcat import imgcat -from aider.dump import dump # noqa: F401 +from cecli.dump import dump # noqa: F401 def plot_timing(df): diff --git a/benchmark/problem_stats.py b/benchmark/problem_stats.py index 202942f1358..d830198a05e 100755 --- a/benchmark/problem_stats.py +++ b/benchmark/problem_stats.py @@ -8,14 +8,14 @@ import yaml -from aider.dump import dump # noqa +from cecli.dump import dump # noqa HARD_SET_NUM = 3 # Number of models that defines the hard set threshold def get_dirs_from_leaderboard(): # Load the leaderboard data - with open("aider/website/_data/polyglot_leaderboard.yml") as f: + with open("cecli/website/_data/polyglot_leaderboard.yml") as f: leaderboard = yaml.safe_load(f) return [(entry["dirname"], entry["model"]) for entry in leaderboard] @@ -34,7 +34,7 @@ def load_results(dirname): parse_errors = [] # Track which exercises had parse errors for this model # Look in language subdirectories under exercises/practice - for fname in benchmark_dir.glob("*/exercises/practice/*/.aider.results.json"): + for fname in benchmark_dir.glob("*/exercises/practice/*/.cecli.results.json"): error = False try: results = json.loads(fname.read_text()) @@ -92,7 +92,7 @@ def analyze_exercise_solutions(dirs=None, topn=None, copy_hard_set=False): ( entry["pass_rate_2"] for entry in yaml.safe_load( - open("aider/website/_data/polyglot_leaderboard.yml") + open("cecli/website/_data/polyglot_leaderboard.yml") ) if entry["dirname"] == dirname ), diff --git a/benchmark/refactor_tools.py b/benchmark/refactor_tools.py index 03812b0c178..b1ae841aea7 100755 --- a/benchmark/refactor_tools.py +++ b/benchmark/refactor_tools.py @@ -6,7 +6,7 @@ import sys from pathlib import Path -from aider.dump import dump # noqa: F401 +from cecli.dump import dump # noqa: F401 class ParentNodeTransformer(ast.NodeTransformer): diff --git a/benchmark/rungrid.py b/benchmark/rungrid.py index f2dd53dd554..9fb462fcba3 100755 --- a/benchmark/rungrid.py +++ b/benchmark/rungrid.py @@ -3,7 +3,7 @@ import subprocess import sys -from aider.dump import dump # noqa: F401 +from cecli.dump import dump # noqa: F401 def main(): diff --git a/benchmark/swe_bench.py b/benchmark/swe_bench.py index 56021e9ced2..8048b7e2310 100644 --- a/benchmark/swe_bench.py +++ b/benchmark/swe_bench.py @@ -5,7 +5,7 @@ from imgcat import imgcat from matplotlib import rc -from aider.dump import dump # noqa: F401 +from cecli.dump import dump # noqa: F401 def plot_swe_bench(data_file, is_lite): @@ -52,13 +52,13 @@ def plot_swe_bench(data_file, is_lite): spine.set_linewidth(0.5) if is_lite: - colors = ["#17965A" if "Aider" in model else "#b3d1e6" for model in models] + colors = ["#17965A" if "cecli" in model else "#b3d1e6" for model in models] else: - colors = ["#1A75C2" if "Aider" in model else "#b3d1e6" for model in models] + colors = ["#1A75C2" if "cecli" in model else "#b3d1e6" for model in models] bars = [] for model, pass_rate, color in zip(models, pass_rates, colors): - alpha = 0.9 if "Aider" in model else 0.3 + alpha = 0.9 if "cecli" in model else 0.3 hatch = "" # if is_lite: # hatch = "///" if "(570)" in model else "" @@ -66,15 +66,15 @@ def plot_swe_bench(data_file, is_lite): bars.append(bar[0]) for label in ax.get_xticklabels(): - if "Aider" in str(label): + if "cecli" in str(label): label.set_fontfamily("Helvetica Bold") for model, bar in zip(models, bars): yval = bar.get_height() y = yval - 1 va = "top" - color = "#eee" if "Aider" in model else "#555" - fontfamily = "Helvetica Bold" if "Aider" in model else "Helvetica" + color = "#eee" if "cecli" in model else "#555" + fontfamily = "Helvetica Bold" if "cecli" in model else "Helvetica" ax.text( bar.get_x() + bar.get_width() / 2, y, @@ -92,7 +92,7 @@ def plot_swe_bench(data_file, is_lite): yval = bar.get_height() y = yval - 2.5 va = "top" - color = "#eee" if "Aider" in model else "#555" + color = "#eee" if "cecli" in model else "#555" ax.text( bar.get_x() + bar.get_width() / 2, y, diff --git a/benchmark/test_benchmark.py b/benchmark/test_benchmark.py index 9f548fd7df9..bb16f6e950e 100644 --- a/benchmark/test_benchmark.py +++ b/benchmark/test_benchmark.py @@ -22,7 +22,7 @@ def test_cleanup_test_output_lines(self): FAIL: test_cleanup_test_output (test_benchmark.TestCleanupTestOutput.test_cleanup_test_output) ---------------------------------------------------------------------- Traceback (most recent call last): - File "/Users/gauthier/Projects/aider/benchmark/test_benchmark.py", line 14, in test_cleanup_test_output + File "/Users/gauthier/Projects/cecli/benchmark/test_benchmark.py", line 14, in test_cleanup_test_output self.assertEqual(cleanup_test_output(output), expected) AssertionError: 'OK' != 'OKx' - OK @@ -35,7 +35,7 @@ def test_cleanup_test_output_lines(self): FAIL: test_cleanup_test_output (test_benchmark.TestCleanupTestOutput.test_cleanup_test_output) ---- Traceback (most recent call last): - File "/Users/gauthier/Projects/aider/benchmark/test_benchmark.py", line 14, in test_cleanup_test_output + File "/Users/gauthier/Projects/cecli/benchmark/test_benchmark.py", line 14, in test_cleanup_test_output self.assertEqual(cleanup_test_output(output), expected) AssertionError: 'OK' != 'OKx' - OK diff --git a/aider/__init__.py b/cecli/__init__.py similarity index 91% rename from aider/__init__.py rename to cecli/__init__.py index 44ef36f9856..67dfd72b26a 100644 --- a/aider/__init__.py +++ b/cecli/__init__.py @@ -4,7 +4,7 @@ safe_version = __version__ try: - from aider._version import __version__ + from cecli._version import __version__ except Exception: __version__ = safe_version + "+import" diff --git a/aider/__main__.py b/cecli/__main__.py similarity index 100% rename from aider/__main__.py rename to cecli/__main__.py diff --git a/aider/args.py b/cecli/args.py similarity index 93% rename from aider/args.py rename to cecli/args.py index 029b1f12736..2a5caab0a54 100644 --- a/aider/args.py +++ b/cecli/args.py @@ -8,18 +8,18 @@ import configargparse import shtab -from aider import __version__ -from aider.args_formatter import ( +from cecli import __version__ +from cecli.args_formatter import ( DotEnvFormatter, MarkdownHelpFormatter, YamlHelpFormatter, ) -from aider.deprecated_args import add_deprecated_model_args +from cecli.deprecated_args import add_deprecated_model_args from .dump import dump # noqa: F401 -def resolve_aiderignore_path(path_str, git_root=None): +def resolve_cecli_ignore_path(path_str, git_root=None): path = Path(path_str) if path.is_absolute(): return str(path) @@ -34,21 +34,21 @@ def default_env_file(git_root): def get_parser(default_config_files, git_root): parser = configargparse.ArgumentParser( - description="aider is AI pair programming in your terminal", + description="cecli is AI pair programming in your terminal", add_config_file_help=True, default_config_files=default_config_files, config_file_parser_class=configargparse.YAMLConfigFileParser, - auto_env_var_prefix="AIDER_", + auto_env_var_prefix="CECLI", ) # List of valid edit formats for argparse validation & shtab completion. # Dynamically gather them from the registered coder classes so the list # stays in sync if new formats are added. - from aider import coders as _aider_coders + from cecli import coders as _cecli_coders edit_format_choices = sorted( { c.edit_format - for c in _aider_coders.__all__ + for c in _cecli_coders.__all__ if hasattr(c, "edit_format") and c.edit_format is not None } ) @@ -104,13 +104,13 @@ def get_parser(default_config_files, git_root): group.add_argument( "--model-settings-file", metavar="MODEL_SETTINGS_FILE", - default=".aider.model.settings.yml", - help="Specify a file with aider model settings for unknown models", + default=".cecli.model.settings.yml", + help="Specify a file with cecli model settings for unknown models", ).complete = shtab.FILE group.add_argument( "--model-metadata-file", metavar="MODEL_METADATA_FILE", - default=".aider.model.metadata.json", + default=".cecli.model.metadata.json", help="Specify a file with context window and costs for unknown models", ).complete = shtab.FILE group.add_argument( @@ -131,7 +131,7 @@ def get_parser(default_config_files, git_root): group.add_argument( "--model-overrides-file", metavar="MODEL_OVERRIDES_FILE", - default=".aider.model.overrides.yml", + default=".cecli.model.overrides.yml", help=( "Specify a file with model tag overrides (e.g., gpt-4o:high -> reasoning_effort: high)" ), @@ -399,7 +399,7 @@ def get_parser(default_config_files, git_root): dest="map_cache_dir", default=".", help=( - "Directory for the repository map cache .aider.tags.cache.v3" + "Directory for the repository map cache .cecli.tags.cache.v#" " (default: current directory)" ), ) @@ -412,10 +412,10 @@ def get_parser(default_config_files, git_root): ########## group = parser.add_argument_group("History Files") default_input_history_file = ( - os.path.join(git_root, ".aider.input.history") if git_root else ".aider.input.history" + os.path.join(git_root, ".cecli/input.history") if git_root else ".cecli/input.history" ) default_chat_history_file = ( - os.path.join(git_root, ".aider.chat.history.md") if git_root else ".aider.chat.history.md" + os.path.join(git_root, ".cecli/chat.history") if git_root else ".cecli/chat.history" ) group.add_argument( "--input-history-file", @@ -553,24 +553,24 @@ def get_parser(default_config_files, git_root): "--gitignore", action=argparse.BooleanOptionalAction, default=True, - help="Enable/disable adding .aider* to .gitignore (default: True)", + help="Enable/disable adding .cecli* to .gitignore (default: True)", ) group.add_argument( "--add-gitignore-files", action=argparse.BooleanOptionalAction, default=False, - help="Enable/disable the addition of files listed in .gitignore to Aider's editing scope.", + help="Enable/disable the addition of files listed in .gitignore to cecli's editing scope.", ) - default_aiderignore_file = ( - os.path.join(git_root, ".aiderignore") if git_root else ".aiderignore" + default_cecli_ignore_file = ( + os.path.join(git_root, "cecli.ignore") if git_root else "cecli.ignore" ) group.add_argument( - "--aiderignore", - metavar="AIDERIGNORE", - type=lambda path_str: resolve_aiderignore_path(path_str, git_root), - default=default_aiderignore_file, - help="Specify the aider ignore file (default: .aiderignore in git root)", + "--cecli-ignore", + metavar="CECLIIGNORE", + type=lambda path_str: resolve_cecli_ignore_path(path_str, git_root), + default=default_cecli_ignore_file, + help="Specify the cecli ignore file (default: .cecli_ignore in git root)", ).complete = shtab.FILE group.add_argument( "--subtree-only", @@ -595,7 +595,7 @@ def get_parser(default_config_files, git_root): action=argparse.BooleanOptionalAction, default=None, help=( - "Attribute aider code changes in the git author name (default: True). If explicitly set" + "Attribute cecli code changes in the git author name (default: True). If explicitly set" " to True, overrides --attribute-co-authored-by precedence." ), ) @@ -604,28 +604,28 @@ def get_parser(default_config_files, git_root): action=argparse.BooleanOptionalAction, default=None, help=( - "Attribute aider commits in the git committer name (default: True). If explicitly set" - " to True, overrides --attribute-co-authored-by precedence for aider edits." + "Attribute cecli commits in the git committer name (default: True). If explicitly set" + " to True, overrides --attribute-co-authored-by precedence for cecli edits." ), ) group.add_argument( "--attribute-commit-message-author", action=argparse.BooleanOptionalAction, default=False, - help="Prefix commit messages with 'aider: ' if aider authored the changes (default: False)", + help="Prefix commit messages with 'cecli: ' if cecli authored the changes (default: False)", ) group.add_argument( "--attribute-commit-message-committer", action=argparse.BooleanOptionalAction, default=False, - help="Prefix all commit messages with 'aider: ' (default: False)", + help="Prefix all commit messages with 'cecli: ' (default: False)", ) group.add_argument( "--attribute-co-authored-by", action=argparse.BooleanOptionalAction, default=True, help=( - "Attribute aider edits using the Co-authored-by trailer in the commit message" + "Attribute cecli edits using the Co-authored-by trailer in the commit message" " (default: True). If True, this takes precedence over default --attribute-author and" " --attribute-committer behavior unless they are explicitly set to True." ), @@ -716,7 +716,7 @@ def get_parser(default_config_files, git_root): group.add_argument( "--check-update", action=argparse.BooleanOptionalAction, - help="Check for new aider versions on launch", + help="Check for new cecli versions on launch", default=True, ) group.add_argument( @@ -735,7 +735,7 @@ def get_parser(default_config_files, git_root): "--upgrade", "--update", action="store_true", - help="Upgrade aider to the latest version from PyPI", + help="Upgrade cecli to the latest version from PyPI", default=False, ) group.add_argument( @@ -769,7 +769,7 @@ def get_parser(default_config_files, git_root): "--copy-paste", action=argparse.BooleanOptionalAction, default=False, - help="Enable automatic copy/paste of chat between aider and web UI (default: False)", + help="Enable automatic copy/paste of chat between cecli and web UI (default: False)", ) group.add_argument( "--apply", @@ -927,7 +927,7 @@ 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" + "Specify the config file (default: search for .cecli.conf.yml in git root, cwd" " or home directory)" ), ).complete = shtab.FILE @@ -998,7 +998,7 @@ def get_parser(default_config_files, git_root): choices=supported_shells_list, help=( "Print shell completion script for the specified SHELL and exit. Supported shells:" - f" {', '.join(supported_shells_list)}. Example: aider --shell-completions bash" + f" {', '.join(supported_shells_list)}. Example: cecli --shell-completions bash" ), ) @@ -1012,7 +1012,7 @@ def get_parser(default_config_files, git_root): def get_md_help(): os.environ["COLUMNS"] = "70" - sys.argv = ["aider"] + sys.argv = ["cecli"] parser = get_parser([], None) # This instantiates all the action.env_var values @@ -1025,7 +1025,7 @@ def get_md_help(): def get_sample_yaml(): os.environ["COLUMNS"] = "100" - sys.argv = ["aider"] + sys.argv = ["cecli"] parser = get_parser([], None) # This instantiates all the action.env_var values @@ -1038,7 +1038,7 @@ def get_sample_yaml(): def get_sample_dotenv(): os.environ["COLUMNS"] = "120" - sys.argv = ["aider"] + sys.argv = ["cecli"] parser = get_parser([], None) # This instantiates all the action.env_var values @@ -1069,7 +1069,7 @@ def main(): print(f"Supported shells are: {', '.join(shtab.SUPPORTED_SHELLS)}", file=sys.stderr) sys.exit(1) parser = get_parser([], None) - parser.prog = "aider" # Set the program name on the parser + parser.prog = "cecli" # Set the program name on the parser print(shtab.complete(parser, shell=shell)) else: print("Error: Please specify a shell for completion.", file=sys.stderr) diff --git a/aider/args_formatter.py b/cecli/args_formatter.py similarity index 97% rename from aider/args_formatter.py rename to cecli/args_formatter.py index fc4c3efac08..01b9bc94094 100644 --- a/aider/args_formatter.py +++ b/cecli/args_formatter.py @@ -1,6 +1,6 @@ import argparse -from aider import urls +from cecli import urls from .dump import dump # noqa: F401 @@ -18,9 +18,9 @@ def _format_usage(self, usage, actions, groups, prefix): def _format_text(self, text): return f""" ########################################################## -# Sample aider .env file. +# Sample cecli .env file. # Place at the root of your git repo. -# Or use `aider --env ` to specify. +# Or use `cecli --env ` to specify. ########################################################## ################# @@ -91,14 +91,14 @@ def _format_usage(self, usage, actions, groups, prefix): def _format_text(self, text): return """ ########################################################## -# Sample .aider.conf.yml +# Sample .cecli.conf.yml # This file lists *all* the valid configuration entries. # Place in your home dir, or at the root of your git repo. ########################################################## # Note: You can only put OpenAI and Anthropic API keys in the YAML # config file. Keys for all APIs can be stored in a .env file -# https://aider.chat/docs/config/dotenv.html +# https://cecli.dev/docs/config/dotenv.html """ diff --git a/aider/change_tracker.py b/cecli/change_tracker.py similarity index 100% rename from aider/change_tracker.py rename to cecli/change_tracker.py diff --git a/aider/coders/__init__.py b/cecli/coders/__init__.py similarity index 100% rename from aider/coders/__init__.py rename to cecli/coders/__init__.py diff --git a/aider/coders/agent_coder.py b/cecli/coders/agent_coder.py similarity index 73% rename from aider/coders/agent_coder.py rename to cecli/coders/agent_coder.py index 6f91ec1d5b6..553d1d74b95 100644 --- a/aider/coders/agent_coder.py +++ b/cecli/coders/agent_coder.py @@ -8,33 +8,23 @@ import re import time import traceback - -# Add necessary imports if not already present from collections import Counter, defaultdict from datetime import datetime from pathlib import Path from litellm import experimental_mcp_client -from aider import urls, utils - -# Import the change tracker -from aider.change_tracker import ChangeTracker - -# Import similarity functions for tool usage analysis -from aider.helpers.similarity import ( +from cecli import urls, utils +from cecli.change_tracker import ChangeTracker +from cecli.helpers.similarity import ( cosine_similarity, create_bigram_vector, normalize_vector, ) - -# Import skills helper for skills -from aider.helpers.skills import SkillsManager -from aider.mcp.server import LocalServer -from aider.repo import ANY_GIT_ERROR - -# Import tool modules for registry -from aider.tools import TOOL_MODULES +from cecli.helpers.skills import SkillsManager +from cecli.mcp.server import LocalServer +from cecli.repo import ANY_GIT_ERROR +from cecli.tools import TOOL_MODULES from .base_coder import ChatChunks, Coder from .editblock_coder import do_replace, find_original_update_blocks, find_similar_lines @@ -47,19 +37,13 @@ class AgentCoder(Coder): prompt_format = "agent" def __init__(self, *args, **kwargs): - # Dictionary to track recently removed files self.recently_removed = {} - - # Tool usage history - self.tool_usage_history = [] # Stores lists of tools used in each round + self.tool_usage_history = [] self.tool_usage_retries = 10 - self.last_round_tools = [] # Tools used in the current round - - # Similarity tracking for tool usage - self.tool_call_vectors = [] # Store vectors for individual tool calls - self.tool_similarity_threshold = 0.99 # High threshold for exact matches - self.max_tool_vector_history = 10 # Keep history of 10 rounds - + self.last_round_tools = [] + self.tool_call_vectors = [] + self.tool_similarity_threshold = 0.99 + self.max_tool_vector_history = 10 self.read_tools = { "viewfilesatglob", "viewfilesmatching", @@ -79,54 +63,24 @@ def __init__(self, *args, **kwargs): "replacetext", "undochange", } - - # Configuration parameters - self.max_tool_calls = 10000 # Maximum number of tool calls per response - - # Context management parameters - # Will be overridden by agent_config if provided - self.large_file_token_threshold = ( - 25000 # Files larger than this in tokens are considered large - ) - - # Enable context management by default only in agent mode - self.context_management_enabled = True # Enabled by default for agent mode - # Skills configuration - self.skills_manager = None # Will be initialized later - - # Initialize change tracker for granular editing + self.max_tool_calls = 10000 + self.large_file_token_threshold = 25000 + self.context_management_enabled = True + self.skills_manager = None self.change_tracker = ChangeTracker() - - # Initialize tool registry self.args = kwargs.get("args") self.tool_registry = self._build_tool_registry() - - # Track files added during current exploration self.files_added_in_exploration = set() - - # Counter for tool calls self.tool_call_count = 0 - - # Set high max reflections to allow many exploration rounds - # This controls how many automatic iterations the LLM can do self.max_reflections = 15 - - # Enable enhanced context blocks by default self.use_enhanced_context = True - - # Caching efficiency attributes self._last_edited_file = None self._cur_message_divider = None - - # Initialize empty token tracking dictionary and cache structures - # but don't populate yet to avoid startup delay self.allowed_context_blocks = set() self.context_block_tokens = {} self.context_blocks_cache = {} self.tokens_calculated = False - self.skip_cli_confirmations = False - self.agent_finished = False self._get_agent_config() super().__init__(*args, **kwargs) @@ -140,11 +94,7 @@ def _build_tool_registry(self): dict: Mapping of normalized tool names to tool modules """ registry = {} - - # Add tools that have been imported tool_modules = TOOL_MODULES - - # Process agent configuration if provided agent_config = self._get_agent_config() tools_includelist = agent_config.get( "tools_includelist", agent_config.get("tools_whitelist", []) @@ -152,36 +102,23 @@ def _build_tool_registry(self): tools_excludelist = agent_config.get( "tools_excludelist", agent_config.get("tools_blacklist", []) ) - if "skills" not in self.allowed_context_blocks or not agent_config.get("skills_paths"): tools_excludelist.append("loadskill") tools_excludelist.append("removeskill") - - # Always include essential tools regardless of includelist/excludelist essential_tools = {"contextmanager", "replacetext", "finished"} for module in tool_modules: if hasattr(module, "Tool"): tool_class = module.Tool tool_name = tool_class.NORM_NAME - - # Check if tool should be included based on configuration should_include = True - - # If includelist is specified, only include tools in includelist if tools_includelist: should_include = tool_name in tools_includelist - - # Always include essential tools if tool_name in essential_tools: should_include = True - - # Exclude tools in excludelist (unless they're essential) if tool_name in tools_excludelist and tool_name not in essential_tools: should_include = False - if should_include: registry[tool_name] = tool_class - return registry def _get_agent_config(self): @@ -192,8 +129,6 @@ def _get_agent_config(self): dict: Agent configuration with defaults for missing values """ config = {} - - # Check if agent_config is provided via args if ( hasattr(self, "args") and self.args @@ -205,15 +140,12 @@ def _get_agent_config(self): except (json.JSONDecodeError, TypeError) as e: self.io.tool_warning(f"Failed to parse agent-config JSON: {e}") return {} - - # Set defaults for missing values if "large_file_token_threshold" not in config: config["large_file_token_threshold"] = 25000 if "tools_includelist" not in config: config["tools_includelist"] = [] if "tools_excludelist" not in config: config["tools_excludelist"] = [] - if "include_context_blocks" in config: self.allowed_context_blocks = set(config["include_context_blocks"]) else: @@ -226,38 +158,30 @@ def _get_agent_config(self): "todo_list", "skills", } - if "exclude_context_blocks" in config: for context_block in config["exclude_context_blocks"]: try: self.allowed_context_blocks.remove(context_block) except KeyError: pass - - # Apply configuration to instance self.large_file_token_threshold = config["large_file_token_threshold"] self.skip_cli_confirmations = config.get( "skip_cli_confirmations", config.get("yolo", False) ) - if "skills" in self.allowed_context_blocks: - # Skills configuration if "skills_paths" not in config: config["skills_paths"] = [] if "skills_includelist" not in config: config["skills_includelist"] = [] if "skills_excludelist" not in config: config["skills_excludelist"] = [] - self._initialize_skills_manager(config) - return config def _initialize_skills_manager(self, config): """ Initialize the skills manager with the configured directory paths and filters. """ - try: git_root = str(self.repo.root) if self.repo else None self.skills_manager = SkillsManager( @@ -265,57 +189,44 @@ def _initialize_skills_manager(self, config): include_list=config.get("skills_includelist", []), exclude_list=config.get("skills_excludelist", []), git_root=git_root, - coder=self, # Pass reference to the coder instance + coder=self, ) - except Exception as e: self.io.tool_warning(f"Failed to initialize skills manager: {str(e)}") def show_announcements(self): super().show_announcements() - - # Find and log available skills skills = self.skills_manager.find_skills() if skills: skills_list = [] for skill in skills: skills_list.append(skill.name) - joined_skills = ", ".join(skills_list) self.io.tool_output(f"Available Skills: {joined_skills}") def get_local_tool_schemas(self): """Returns the JSON schemas for all local tools using the tool registry.""" schemas = [] - - # Get schemas from the tool registry for tool_module in self.tool_registry.values(): if hasattr(tool_module, "SCHEMA"): schemas.append(tool_module.SCHEMA) - return schemas async def initialize_mcp_tools(self): await super().initialize_mcp_tools() - server_name = "Local" - if server_name not in [name for name, _ in self.mcp_tools]: local_tools = self.get_local_tool_schemas() if not local_tools: return - local_server_config = {"name": server_name} local_server = LocalServer(local_server_config) - if not self.mcp_servers: self.mcp_servers = [] if not any(isinstance(s, LocalServer) for s in self.mcp_servers): self.mcp_servers.append(local_server) - if not self.mcp_tools: self.mcp_tools = [] - if server_name not in [name for name, _ in self.mcp_tools]: self.mcp_tools.append((local_server.name, local_tools)) @@ -325,8 +236,6 @@ async def _execute_local_tool_calls(self, tool_calls_list): tool_name = tool_call.function.name result_message = "" try: - # Arguments can be a stream of JSON objects. - # We need to parse them and run a tool call for each. args_string = tool_call.function.arguments.strip() parsed_args_list = [] if args_string: @@ -339,76 +248,55 @@ async def _execute_local_tool_calls(self, tool_calls_list): f"Could not parse JSON chunk for tool {tool_name}: {chunk}" ) continue - if not parsed_args_list and not args_string: - parsed_args_list.append({}) # For tool calls with no arguments - + parsed_args_list.append({}) all_results_content = [] norm_tool_name = tool_name.lower() - tasks = [] - - # Use the tool registry for execution if norm_tool_name in self.tool_registry: tool_module = self.tool_registry[norm_tool_name] for params in parsed_args_list: - # Use the process_response function from the tool module result = tool_module.process_response(self, params) - # Handle async functions if asyncio.iscoroutine(result): tasks.append(result) else: tasks.append(asyncio.to_thread(lambda: result)) - else: - # Handle MCP tools for tools not in registry - if self.mcp_tools: - for server_name, server_tools in self.mcp_tools: - if any( - t.get("function", {}).get("name") == norm_tool_name - for t in server_tools - ): - server = next( - (s for s in self.mcp_servers if s.name == server_name), None - ) - if server: - for params in parsed_args_list: - tasks.append( - self._execute_mcp_tool(server, norm_tool_name, params) - ) - break - else: - all_results_content.append(f"Error: Unknown tool name '{tool_name}'") + elif self.mcp_tools: + for server_name, server_tools in self.mcp_tools: + if any( + t.get("function", {}).get("name") == norm_tool_name + for t in server_tools + ): + server = next( + (s for s in self.mcp_servers if s.name == server_name), None + ) + if server: + for params in parsed_args_list: + tasks.append( + self._execute_mcp_tool(server, norm_tool_name, params) + ) + break else: all_results_content.append(f"Error: Unknown tool name '{tool_name}'") - + else: + all_results_content.append(f"Error: Unknown tool name '{tool_name}'") if tasks: task_results = await asyncio.gather(*tasks) all_results_content.extend(str(res) for res in task_results) - result_message = "\n\n".join(all_results_content) - except Exception as e: result_message = f"Error executing {tool_name}: {e}" - self.io.tool_error( - f"Error during {tool_name} execution: {e}\n{traceback.format_exc()}" - ) - + self.io.tool_error(f"""Error during {tool_name} execution: {e} +{traceback.format_exc()}""") tool_responses.append( - { - "role": "tool", - "tool_call_id": tool_call.id, - "content": result_message, - } + {"role": "tool", "tool_call_id": tool_call.id, "content": result_message} ) return tool_responses async def _execute_mcp_tool(self, server, tool_name, params): """Helper to execute a single MCP tool call, created from legacy format.""" - # This is a simplified, synchronous wrapper around async logic - # It's duplicating logic from BaseCoder for legacy tool support. async def _exec_async(): - # Construct a ToolCall object-like structure to be compatible with mcp_client function_dict = {"name": tool_name, "arguments": json.dumps(params)} tool_call_dict = { "id": f"mcp-tool-call-{time.time()}", @@ -418,18 +306,16 @@ async def _exec_async(): try: session = await server.connect() call_result = await experimental_mcp_client.call_openai_tool( - session=session, - openai_tool=tool_call_dict, + session=session, openai_tool=tool_call_dict ) - content_parts = [] if call_result.content: for item in call_result.content: - if hasattr(item, "resource"): # EmbeddedResource + if hasattr(item, "resource"): resource = item.resource - if hasattr(resource, "text"): # TextResourceContents + if hasattr(resource, "text"): content_parts.append(resource.text) - elif hasattr(resource, "blob"): # BlobResourceContents + elif hasattr(resource, "blob"): try: decoded_blob = base64.b64decode(resource.blob).decode("utf-8") content_parts.append(decoded_blob) @@ -439,15 +325,13 @@ async def _exec_async(): content_parts.append( f"[embedded binary resource: {name} ({mime_type})]" ) - elif hasattr(item, "text"): # TextContent + elif hasattr(item, "text"): content_parts.append(item.text) - return "".join(content_parts) - except Exception as e: - self.io.tool_warning( - f"Executing {tool_name} on {server.name} failed: \n Error: {e}\n" - ) + self.io.tool_warning(f"""Executing {tool_name} on {server.name} failed: + Error: {e} +""") return f"Error executing tool call {tool_name}: {e}" return await _exec_async() @@ -463,25 +347,15 @@ def _calculate_context_block_tokens(self, force=False): Args: force: If True, recalculate tokens even if already calculated """ - # Skip if already calculated and not forced if hasattr(self, "tokens_calculated") and self.tokens_calculated and not force: return - - # Clear existing token counts self.context_block_tokens = {} - - # Initialize the cache for context blocks if needed if not hasattr(self, "context_blocks_cache"): self.context_blocks_cache = {} - if not self.use_enhanced_context: return - try: - # First, clear the cache to force regeneration of all blocks self.context_blocks_cache = {} - - # Generate all context blocks and calculate token counts block_types = [ "environment_info", "directory_structure", @@ -490,7 +364,6 @@ def _calculate_context_block_tokens(self, force=False): "skills", "loaded_skills", ] - for block_type in block_types: if block_type in self.allowed_context_blocks: block_content = self._generate_context_block(block_type) @@ -498,12 +371,8 @@ def _calculate_context_block_tokens(self, force=False): self.context_block_tokens[block_type] = self.main_model.token_count( block_content ) - - # Mark as calculated self.tokens_calculated = True except Exception: - # Silently handle errors during calculation - # This prevents errors in token counting from breaking the main functionality pass def _generate_context_block(self, block_name): @@ -512,7 +381,6 @@ def _generate_context_block(self, block_name): This is a helper method for get_cached_context_block. """ content = None - if block_name == "environment_info": content = self.get_environment_info() elif block_name == "directory_structure": @@ -529,11 +397,8 @@ def _generate_context_block(self, block_name): content = self.get_skills_context() elif block_name == "loaded_skills": content = self.get_skills_content() - - # Cache the result if it's not None if content is not None: self.context_blocks_cache[block_name] = content - return content def get_cached_context_block(self, block_name): @@ -543,15 +408,10 @@ def get_cached_context_block(self, block_name): This will ensure tokens are calculated if they haven't been yet. """ - # Make sure tokens have been calculated at least once if not hasattr(self, "tokens_calculated") or not self.tokens_calculated: self._calculate_context_block_tokens() - - # Return from cache if available if hasattr(self, "context_blocks_cache") and block_name in self.context_blocks_cache: return self.context_blocks_cache[block_name] - - # Otherwise generate and cache the block return self._generate_context_block(block_name) def get_context_symbol_outline(self): @@ -561,76 +421,55 @@ def get_context_symbol_outline(self): """ if not self.use_enhanced_context or not self.repo_map: return None - try: result = '\n' result += "## Symbol Outline (Current Context)\n\n" - result += ( - "Code definitions (classes, functions, methods, etc.) found in files currently in" - " chat context.\n\n" - ) + result += """Code definitions (classes, functions, methods, etc.) found in files currently in chat context. +""" files_to_outline = list(self.abs_fnames) + list(self.abs_read_only_fnames) if not files_to_outline: result += "No files currently in context.\n" result += "" return result - all_tags_by_file = defaultdict(list) has_symbols = False - - # Use repo_map which should be initialized in BaseCoder if not self.repo_map: self.io.tool_warning("RepoMap not initialized, cannot generate symbol outline.") - return None # Or return a message indicating repo map is unavailable - + return None for abs_fname in sorted(files_to_outline): rel_fname = self.get_rel_fname(abs_fname) try: - # Call get_tags_raw directly to bypass cache and ensure freshness tags = list(self.repo_map.get_tags_raw(abs_fname, rel_fname)) if tags: all_tags_by_file[rel_fname].extend(tags) has_symbols = True except Exception as e: self.io.tool_warning(f"Could not get symbols for {rel_fname}: {e}") - if not has_symbols: result += "No symbols found in the current context files.\n" else: for rel_fname in sorted(all_tags_by_file.keys()): tags = sorted(all_tags_by_file[rel_fname], key=lambda t: (t.line, t.name)) - definition_tags = [] for tag in tags: - # Use specific_kind first if available, otherwise fall back to kind kind_to_check = tag.specific_kind or tag.kind - # Check if the kind represents a definition using the set from RepoMap if ( kind_to_check and kind_to_check.lower() in self.repo_map.definition_kinds ): definition_tags.append(tag) - if definition_tags: result += f"### {rel_fname}\n" - # Simple list format for now, could be enhanced later (e.g., indentation for scope) for tag in definition_tags: - # Display line number if available line_info = f", line {tag.line + 1}" if tag.line >= 0 else "" - # Display the specific kind (which we checked) - kind_to_check = tag.specific_kind or tag.kind # Recalculate for safety + kind_to_check = tag.specific_kind or tag.kind result += f"- {tag.name} ({kind_to_check}{line_info})\n" - result += "\n" # Add space between files - + result += "\n" result += "" - return result.strip() # Remove trailing newline if any - + return result.strip() except Exception as e: self.io.tool_error(f"Error generating symbol outline: {str(e)}") - # Optionally include traceback for debugging if verbose - # if self.verbose: - # self.io.tool_error(traceback.format_exc()) return None def format_chat_chunks(self): @@ -644,14 +483,10 @@ def format_chat_chunks(self): This approach preserves prefix caching while providing fresh context information. """ - # If enhanced context blocks are not enabled, use the base implementation if not self.use_enhanced_context: return super().format_chat_chunks() - - # Build chunks from scratch to avoid duplication with enhanced context blocks self.choose_fence() main_sys = self.fmt_system_prompt(self.gpt_prompts.main_system) - example_messages = [] if self.main_model.examples_as_sys_msg: if self.gpt_prompts.example_messages: @@ -664,10 +499,7 @@ def format_chat_chunks(self): else: for msg in self.gpt_prompts.example_messages: example_messages.append( - dict( - role=msg["role"], - content=self.fmt_system_prompt(msg["content"]), - ) + dict(role=msg["role"], content=self.fmt_system_prompt(msg["content"])) ) if self.gpt_prompts.example_messages: example_messages += [ @@ -680,10 +512,8 @@ def format_chat_chunks(self): ), dict(role="assistant", content="Ok."), ] - if self.gpt_prompts.system_reminder: main_sys += "\n" + self.fmt_system_prompt(self.gpt_prompts.system_reminder) - chunks = ChatChunks( chunk_ordering=[ "system", @@ -700,107 +530,71 @@ def format_chat_chunks(self): "reminder", ] ) - if self.main_model.use_system_prompt: - chunks.system = [ - dict(role="system", content=main_sys), - ] + chunks.system = [dict(role="system", content=main_sys)] else: chunks.system = [ dict(role="user", content=main_sys), dict(role="assistant", content="Ok."), ] - chunks.examples = example_messages - self.summarize_end() cur_messages_list = list(self.cur_messages) cur_messages_pre = [] cur_messages_post = cur_messages_list chunks.readonly_files = self.get_readonly_files_messages() - - # Handle the dictionary structure from get_chat_files_messages() chat_files_result = self.get_chat_files_messages() chunks.chat_files = chat_files_result.get("chat_files", []) chunks.edit_files = chat_files_result.get("edit_files", []) edit_file_names = chat_files_result.get("edit_file_names", set()) - - # Update edit file tracking for caching efficiency divider = self._update_edit_file_tracking(edit_file_names) if divider is not None: - # Split cur_messages using the divider if divider > 0 and divider < len(cur_messages_list): cur_messages_pre = cur_messages_list[:divider] cur_messages_post = cur_messages_list[divider:] - chunks.repo = self.get_repo_messages() chunks.done = list(self.done_messages) + cur_messages_pre - - # Add reminder if needed if self.gpt_prompts.system_reminder: reminder_message = [ dict( role="system", content=self.fmt_system_prompt(self.gpt_prompts.system_reminder) - ), + ) ] else: reminder_message = [] - chunks.cur = cur_messages_post chunks.reminder = [] - - # Make sure token counts are updated - using centralized method - # This also populates the context block cache self._calculate_context_block_tokens() - - # Initialize chunk sections chunks.static = [] chunks.pre_message = [] chunks.post_message = [] - - # 1. Add relatively static blocks BEFORE done_messages - # These blocks change less frequently and can be part of the cacheable prefix static_blocks = [] - - # 2. Add dynamic blocks AFTER chat_files - # These blocks change with the current files in context pre_message_blocks = [] post_message_blocks = [] - if "environment_info" in self.allowed_context_blocks: block = self.get_cached_context_block("environment_info") static_blocks.append(block) - if "directory_structure" in self.allowed_context_blocks: block = self.get_cached_context_block("directory_structure") static_blocks.append(block) - if "skills" in self.allowed_context_blocks: block = self._generate_context_block("skills") static_blocks.append(block) - if "symbol_outline" in self.allowed_context_blocks: block = self.get_cached_context_block("symbol_outline") pre_message_blocks.append(block) - if "git_status" in self.allowed_context_blocks: block = self.get_cached_context_block("git_status") pre_message_blocks.append(block) - if "todo_list" in self.allowed_context_blocks: block = self.get_cached_context_block("todo_list") pre_message_blocks.append(block) - if "skills" in self.allowed_context_blocks: block = self._generate_context_block("loaded_skills") pre_message_blocks.append(block) - if "context_summary" in self.allowed_context_blocks: - # Context summary needs special handling because it depends on other blocks block = self.get_context_summary() pre_message_blocks.insert(0, block) - - # Add tool usage context if there are repetitive tools if hasattr(self, "tool_usage_history") and self.tool_usage_history: repetitive_tools = self._get_repetitive_tools() if repetitive_tools: @@ -811,47 +605,35 @@ def format_chat_chunks(self): write_context = self._generate_write_context() if write_context: post_message_blocks.append(write_context) - if static_blocks: for block in static_blocks: if block: chunks.static.append(dict(role="system", content=block)) - if pre_message_blocks: for block in pre_message_blocks: if block: chunks.pre_message.append(dict(role="system", content=block)) - if post_message_blocks: for block in post_message_blocks: if block: chunks.post_message.append(dict(role="system", content=block)) - - # Use accurate token counting method that considers enhanced context blocks base_messages = chunks.all_messages() messages_tokens = self.main_model.token_count(base_messages) reminder_tokens = self.main_model.token_count(reminder_message) cur_tokens = self.main_model.token_count(chunks.cur) - if None not in (messages_tokens, reminder_tokens, cur_tokens): total_tokens = messages_tokens - # Only add tokens for reminder and cur if they're not already included - # in the messages_tokens calculation if not chunks.reminder: total_tokens += reminder_tokens if not chunks.cur: total_tokens += cur_tokens else: - # add the reminder anyway total_tokens = 0 - if chunks.cur: final = chunks.cur[-1] else: final = None - max_input_tokens = self.main_model.info.get("max_input_tokens") or 0 - # Add the reminder prompt if we still have room to include it. if ( not max_input_tokens or total_tokens < max_input_tokens @@ -860,17 +642,14 @@ def format_chat_chunks(self): if self.main_model.reminder == "sys": chunks.reminder = reminder_message elif self.main_model.reminder == "user" and final and final["role"] == "user": - # stuff it into the user message new_content = ( final["content"] + "\n\n" + self.fmt_system_prompt(self.gpt_prompts.system_reminder) ) chunks.cur[-1] = dict(role=final["role"], content=new_content) - if self.verbose: self._log_chunks(chunks) - return chunks def _update_edit_file_tracking(self, edit_file_names): @@ -884,24 +663,15 @@ def _update_edit_file_tracking(self, edit_file_names): kept_messages = 8 if not edit_file_names: self._cur_message_divider = 0 - - # Get the most recently edited file from the edit_file_names set - # We assume the first file in the sorted set is the most recent sorted_edit_files = sorted(edit_file_names) current_edited_file = sorted_edit_files[0] if sorted_edit_files else None - - # Check if the last edited file has changed if current_edited_file != self._last_edited_file: - # Store the new last edited file self._last_edited_file = current_edited_file - - # Calculate divider: current index minus last n messages cur_messages_list = list(self.cur_messages) if len(cur_messages_list) > kept_messages: self._cur_message_divider = len(cur_messages_list) - kept_messages else: self._cur_message_divider = 0 - return self._cur_message_divider def get_context_summary(self): @@ -911,29 +681,21 @@ def get_context_summary(self): """ if not self.use_enhanced_context: return None - - # If context_summary is already in the cache, return it if hasattr(self, "context_blocks_cache") and "context_summary" in self.context_blocks_cache: return self.context_blocks_cache["context_summary"] - try: - # Make sure token counts are updated before generating the summary if not hasattr(self, "context_block_tokens") or not self.context_block_tokens: self._calculate_context_block_tokens() - result = '\n' result += "## Current Context Overview\n\n" max_input_tokens = self.main_model.info.get("max_input_tokens") or 0 if max_input_tokens: result += f"Model context limit: {max_input_tokens:,} tokens\n\n" - total_file_tokens = 0 editable_tokens = 0 readonly_tokens = 0 editable_files = [] readonly_files = [] - - # Editable files if self.abs_fnames: result += "### Editable Files\n\n" for fname in sorted(self.abs_fnames): @@ -946,21 +708,18 @@ def get_context_summary(self): size_indicator = ( "🔴 Large" if tokens > 5000 - else ("🟡 Medium" if tokens > 1000 else "🟢 Small") + else "🟡 Medium" if tokens > 1000 else "🟢 Small" ) editable_files.append( f"- {rel_fname}: {tokens:,} tokens ({size_indicator})" ) if editable_files: result += "\n".join(editable_files) + "\n\n" - result += ( - f"**Total editable: {len(editable_files)} files," - f" {editable_tokens:,} tokens**\n\n" - ) + result += f"""**Total editable: {len(editable_files)} files, {editable_tokens:,} tokens** + +""" else: result += "No editable files in context\n\n" - - # Read-only files if self.abs_read_only_fnames: result += "### Read-Only Files\n\n" for fname in sorted(self.abs_read_only_fnames): @@ -973,41 +732,34 @@ def get_context_summary(self): size_indicator = ( "🔴 Large" if tokens > 5000 - else ("🟡 Medium" if tokens > 1000 else "🟢 Small") + else "🟡 Medium" if tokens > 1000 else "🟢 Small" ) readonly_files.append( f"- {rel_fname}: {tokens:,} tokens ({size_indicator})" ) if readonly_files: result += "\n".join(readonly_files) + "\n\n" - result += ( - f"**Total read-only: {len(readonly_files)} files," - f" {readonly_tokens:,} tokens**\n\n" - ) + result += f"""**Total read-only: {len(readonly_files)} files, {readonly_tokens:,} tokens** + +""" else: result += "No read-only files in context\n\n" - - # Use the pre-calculated context block tokens extra_tokens = sum(self.context_block_tokens.values()) total_tokens = total_file_tokens + extra_tokens - result += f"**Total files usage: {total_file_tokens:,} tokens**\n\n" result += f"**Additional context usage: {extra_tokens:,} tokens**\n\n" result += f"**Total context usage: {total_tokens:,} tokens**" if max_input_tokens: - percentage = (total_tokens / max_input_tokens) * 100 + percentage = total_tokens / max_input_tokens * 100 result += f" ({percentage:.1f}% of limit)" if percentage > 80: result += "\n\n⚠️ **Context is getting full!**\n" result += "- Remove non-essential files via the `ContextManager` tool.\n" result += "- Keep only essential files in context for best performance" result += "\n" - - # Cache the result if not hasattr(self, "context_blocks_cache"): self.context_blocks_cache = {} self.context_blocks_cache["context_summary"] = result - return result except Exception as e: self.io.tool_error(f"Error generating context summary: {str(e)}") @@ -1020,25 +772,16 @@ def get_environment_info(self): """ if not self.use_enhanced_context: return None - try: - # Get current date in ISO format current_date = datetime.now().strftime("%Y-%m-%d") - - # Get platform information platform_info = platform.platform() - - # Get language preference language = self.chat_language or locale.getlocale()[0] or "en-US" - result = '\n' result += "## Environment Information\n\n" result += f"- Working directory: {self.root}\n" result += f"- Current date: {current_date}\n" result += f"- Platform: {platform_info}\n" result += f"- Language preference: {language}\n" - - # Add git repo information if available if self.repo: try: rel_repo_dir = self.repo.get_rel_repo_dir() @@ -1048,8 +791,6 @@ def get_environment_info(self): result += "- Git repository: active but details unavailable\n" else: result += "- Git repository: none\n" - - # Add enabled features information features = [] if self.context_management_enabled: features.append("context management") @@ -1057,7 +798,6 @@ def get_environment_info(self): features.append("enhanced context blocks") if features: result += f"- Enabled features: {', '.join(features)}\n" - result += "" return result except Exception as e: @@ -1070,38 +810,26 @@ async def process_tool_calls(self, tool_call_response): """ self.agent_finished = False await self.auto_save_session() - - # Clear last round tools and start tracking new round self.last_round_tools = [] - if self.partial_response_tool_calls: for tool_call in self.partial_response_tool_calls: tool_name = tool_call.get("function", {}).get("name") - if tool_name: self.last_round_tools.append(tool_name) - - # Create and store vector for this tool call - # Remove id property if present before stringifying tool_call_copy = tool_call.copy() if "id" in tool_call_copy: del tool_call_copy["id"] - tool_call_str = str(tool_call_copy) # Convert entire tool call to string + tool_call_str = str(tool_call_copy) tool_vector = create_bigram_vector((tool_call_str,)) tool_vector_norm = normalize_vector(tool_vector) self.tool_call_vectors.append(tool_vector_norm) - - # Add the completed round to history if self.last_round_tools: self.tool_usage_history += self.last_round_tools self.tool_usage_history = list(filter(None, self.tool_usage_history)) - if len(self.tool_usage_history) > self.tool_usage_retries: self.tool_usage_history.pop(0) - if len(self.tool_call_vectors) > self.max_tool_vector_history: self.tool_call_vectors.pop(0) - return await super().process_tool_calls(tool_call_response) async def reply_completed(self): @@ -1116,15 +844,12 @@ async def reply_completed(self): iteratively discover and analyze relevant files before providing a final answer to the user's question. """ - # Legacy tool call processing for use_granular_editing=False content = self.partial_response_content if not content or not content.strip(): if len(self.tool_usage_history) > self.tool_usage_retries: self.tool_usage_history = [] return True - original_content = content # Keep the original response - - # Process tool commands: returns content with tool calls removed, results, flag if any tool calls were found + original_content = content ( processed_content, result_messages, @@ -1132,147 +857,88 @@ async def reply_completed(self): content_before_last_separator, tool_names_this_turn, ) = await self._process_tool_commands(content) - if self.agent_finished: self.tool_usage_history = [] if self.files_edited_by_tools: _ = await self.auto_commit(self.files_edited_by_tools) return False - - # Since we are no longer suppressing, the partial_response_content IS the final content. - # We might want to update it to the processed_content (without tool calls) if we don't - # want the raw tool calls to remain in the final assistant message history. - # Let's update it for cleaner history. self.partial_response_content = processed_content.strip() - - # Process implicit file mentions using the content *after* tool calls were removed self._process_file_mentions(processed_content) - - # Check if the content contains the SEARCH/REPLACE markers has_search = "<<<<<<< SEARCH" in self.partial_response_content has_divider = "=======" in self.partial_response_content has_replace = ">>>>>>> REPLACE" in self.partial_response_content edit_match = has_search and has_divider and has_replace - - # Check if there's a '---' line - if yes, SEARCH/REPLACE blocks can only appear before it separator_marker = "\n---\n" if separator_marker in original_content and edit_match: - # Check if the edit blocks are only in the part before the last '---' line has_search_before = "<<<<<<< SEARCH" in content_before_last_separator has_divider_before = "=======" in content_before_last_separator has_replace_before = ">>>>>>> REPLACE" in content_before_last_separator edit_match = has_search_before and has_divider_before and has_replace_before - if edit_match: self.io.tool_output("Detected edit blocks, applying changes within Agent...") edited_files = await self._apply_edits_from_response() - # If _apply_edits_from_response set a reflected_message (due to errors), - # return False to trigger a reflection loop. if self.reflected_message: return False - - # If edits were successfully applied and we haven't exceeded reflection limits, - # set up for another iteration (similar to tool calls) if edited_files and self.num_reflections < self.max_reflections: - # Get the original user question from the most recent user message if self.cur_messages and len(self.cur_messages) >= 1: for msg in reversed(self.cur_messages): if msg["role"] == "user": original_question = msg["content"] break else: - # Default if no user message found original_question = ( "Please continue your exploration and provide a final answer." ) - - # Construct the message for the next turn - next_prompt = ( - "I have applied the edits you suggested. " - f"The following files were modified: {', '.join(edited_files)}. " - "Let me continue working on your request.\n\n" - f"Your original question was: {original_question}" - ) - + next_prompt = f""" +I have applied the edits you suggested. +The following files were modified: {', '.join(edited_files)}. Let me continue working on your request. +Your original question was: {original_question}""" self.reflected_message = next_prompt self.io.tool_output("Continuing after applying edits...") - return False # Indicate that we need another iteration - - # If any tool calls were found and we haven't exceeded reflection limits, set up for another iteration - # This is implicit continuation when any tool calls are present, rather than requiring Continue explicitly + return False if tool_calls_found and self.num_reflections < self.max_reflections: - # Reset tool counter for next iteration self.tool_call_count = 0 - - # Clear exploration files for the next round self.files_added_in_exploration = set() - - # Get the original user question from the most recent user message if self.cur_messages and len(self.cur_messages) >= 1: for msg in reversed(self.cur_messages): if msg["role"] == "user": original_question = msg["content"] break else: - # Default if no user message found original_question = ( "Please continue your exploration and provide a final answer." ) - - # Construct the message for the next turn, including tool results next_prompt_parts = [] next_prompt_parts.append( - "I have processed the results of the previous tool calls. " - "Let me analyze them and continue working towards your request." + "I have processed the results of the previous tool calls. Let me analyze them" + " and continue working towards your request." ) - if result_messages: next_prompt_parts.append("\nResults from previous tool calls:") - # result_messages already have [Result (...): ...] format next_prompt_parts.extend(result_messages) - next_prompt_parts.append( - "\nBased on these results and the updated file context, I will proceed." - ) + next_prompt_parts.append(""" +Based on these results and the updated file context, I will proceed.""") else: - next_prompt_parts.append( - "\nNo specific results were returned from the previous tool calls, but the" - " file context may have been updated. I will proceed based on the current" - " context." - ) - + next_prompt_parts.append(""" +No specific results were returned from the previous tool calls, but the file context may have been updated. +I will proceed based on the current context.""") next_prompt_parts.append(f"\nYour original question was: {original_question}") - self.reflected_message = "\n".join(next_prompt_parts) - self.io.tool_output("Continuing exploration...") - return False # Indicate that we need another iteration - else: - # Exploration finished for this turn. - # Append results to the content that will be stored in history. - if result_messages: - results_block = "\n\n" + "\n".join(result_messages) - # Append results to the cleaned content - self.partial_response_content += results_block - - # After applying edits OR determining no edits were needed (and no reflection needed), - # the turn is complete. Reset counters and finalize history. - - # Auto-commit any files edited by granular tools + return False + elif result_messages: + results_block = "\n\n" + "\n".join(result_messages) + self.partial_response_content += results_block if self.files_edited_by_tools: saved_message = await self.auto_commit(self.files_edited_by_tools) if not saved_message and hasattr(self.gpt_prompts, "files_content_gpt_edits_no_repo"): saved_message = self.gpt_prompts.files_content_gpt_edits_no_repo self.move_back_cur_messages(saved_message) - self.tool_call_count = 0 self.files_added_in_exploration = set() self.files_edited_by_tools = set() - # Move cur_messages to done_messages - self.move_back_cur_messages( - None - ) # Pass None as we handled commit message earlier if needed - - return False # Always Loop Until the Finished Tool is Called + self.move_back_cur_messages(None) + return False async def _execute_tool_with_registry(self, norm_tool_name, params): """ @@ -1285,23 +951,17 @@ async def _execute_tool_with_registry(self, norm_tool_name, params): Returns: str: Result message """ - # Check if tool exists in registry if norm_tool_name in self.tool_registry: tool_module = self.tool_registry[norm_tool_name] try: - # Use the process_response function from the tool module result = tool_module.process_response(self, params) - # Handle async functions if asyncio.iscoroutine(result): result = await result return result except Exception as e: - self.io.tool_error( - f"Error during {norm_tool_name} execution: {e}\n{traceback.format_exc()}" - ) + self.io.tool_error(f"""Error during {norm_tool_name} execution: {e} +{traceback.format_exc()}""") return f"Error executing {norm_tool_name}: {str(e)}" - - # Handle MCP tools for tools not in registry if self.mcp_tools: for server_name, server_tools in self.mcp_tools: if any(t.get("function", {}).get("name") == norm_tool_name for t in server_tools): @@ -1310,7 +970,6 @@ async def _execute_tool_with_registry(self, norm_tool_name, params): return await self._execute_mcp_tool(server, norm_tool_name, params) else: return f"Error: Could not find server instance for {server_name}" - return f"Error: Unknown tool name '{norm_tool_name}'" def _convert_concatenated_json_to_tool_calls(self, content): @@ -1324,16 +983,12 @@ def _convert_concatenated_json_to_tool_calls(self, content): str: Content with concatenated JSON converted to tool call format, or original content if no JSON found """ try: - # Use split_concatenated_json to detect and split concatenated JSON objects json_chunks = utils.split_concatenated_json(content) - - # If we found multiple JSON objects, convert them to tool call format if len(json_chunks) >= 1: tool_calls = [] for chunk in json_chunks: try: json_obj = json.loads(chunk) - # Check if this looks like a tool call JSON object if ( isinstance(json_obj, dict) and "name" in json_obj @@ -1341,12 +996,9 @@ def _convert_concatenated_json_to_tool_calls(self, content): ): tool_name = json_obj["name"] arguments = json_obj["arguments"] - - # Convert arguments dictionary to keyword arguments string kw_args = [] for key, value in arguments.items(): if isinstance(value, str): - # Escape quotes and wrap in quotes escaped_value = value.replace('"', '\\"') kw_args.append(f'{key}="{escaped_value}"') elif isinstance(value, bool): @@ -1354,30 +1006,18 @@ def _convert_concatenated_json_to_tool_calls(self, content): elif value is None: kw_args.append(f"{key}=None") else: - # For numbers and other types, use repr for safe representation kw_args.append(f"{key}={repr(value)}") - - # Join keyword arguments kw_args_str = ", ".join(kw_args) - - # Convert to [tool_call(ToolName, key1="value1", key2="value2")] format tool_call = f"[tool_call({tool_name}, {kw_args_str})]" tool_calls.append(tool_call) else: - # Not a tool call JSON, keep as is tool_calls.append(chunk) except json.JSONDecodeError: - # Invalid JSON, keep as is tool_calls.append(chunk) - - # If we found any tool calls, replace the content if any(call.startswith("[tool_") for call in tool_calls): return "".join(tool_calls) - except Exception as e: - # If anything goes wrong, return original content self.io.tool_warning(f"Error converting concatenated JSON to tool calls: {str(e)}") - return content async def _process_tool_commands(self, content): @@ -1393,85 +1033,54 @@ async def _process_tool_commands(self, content): Also returns the content before the last separator for SEARCH/REPLACE block validation. """ result_messages = [] - modified_content = content # Start with original content + modified_content = content tool_calls_found = False call_count = 0 max_calls = self.max_tool_calls tool_names = [] - - # Check if content contains concatenated JSON and convert to tool call format content = self._convert_concatenated_json_to_tool_calls(content) - - # Check if there's a '---' separator and only process tool calls after the LAST one separator_marker = "---" content_parts = content.split(separator_marker) - - # If there's no separator, treat the entire content as before the separator - # But only return immediately if no tool calls were found in the JSON conversion if len(content_parts) == 1: - # Check if we have any tool calls in the content after JSON conversion - # If we have tool calls, we should process them even without a separator - tool_call_pattern = r"\[tool_call\([^\]]+\)\]" + tool_call_pattern = "\\[tool_call\\([^\\]]+\\)\\]" if re.search(tool_call_pattern, content): - # We have tool calls, so continue processing content_before_separator = "" content_after_separator = content else: - # No tool calls found, return the original content return content, result_messages, False, content, tool_names - - # Take everything before the last separator (including intermediate separators) content_before_separator = separator_marker.join(content_parts[:-1]) - # Take only what comes after the last separator content_after_separator = content_parts[-1] - # Find tool calls using a more robust method, but only in the content after separator processed_content = content_before_separator + separator_marker last_index = 0 - - # Support any [tool_...(...)] format - tool_call_pattern = re.compile(r"\[tool_.*?\(", re.DOTALL) - end_marker = "]" # The parenthesis balancing finds the ')', we just need the final ']' - + tool_call_pattern = re.compile("\\[tool_.*?\\(", re.DOTALL) + end_marker = "]" while True: match = tool_call_pattern.search(content_after_separator, last_index) if not match: processed_content += content_after_separator[last_index:] break - start_pos = match.start() start_marker = match.group(0) - - # Check for escaped tool call: \[tool_... - # Count preceding backslashes to handle \\ backslashes = 0 p = start_pos - 1 while p >= 0 and content_after_separator[p] == "\\": backslashes += 1 p -= 1 - if backslashes % 2 == 1: - # Odd number of backslashes means it's escaped. Treat as text. - # We append up to the end of the marker and continue searching. processed_content += content_after_separator[ last_index : start_pos + len(start_marker) ] last_index = start_pos + len(start_marker) continue - - # Append content before the (non-escaped) tool call processed_content += content_after_separator[last_index:start_pos] - scan_start_pos = start_pos + len(start_marker) paren_level = 1 in_single_quotes = False in_double_quotes = False escaped = False end_paren_pos = -1 - - # Scan to find the matching closing parenthesis, respecting quotes for i in range(scan_start_pos, len(content_after_separator)): char = content_after_separator[i] - if escaped: escaped = False elif char == "\\": @@ -1487,33 +1096,24 @@ async def _process_tool_commands(self, content): if paren_level == 0: end_paren_pos = i break - - # Check for the end marker after the closing parenthesis, skipping whitespace expected_end_marker_start = end_paren_pos + 1 actual_end_marker_start = -1 end_marker_found = False - if end_paren_pos != -1: # Only search if we found a closing parenthesis + if end_paren_pos != -1: for j in range(expected_end_marker_start, len(content_after_separator)): if not content_after_separator[j].isspace(): actual_end_marker_start = j - # Check if the found character is the end marker ']' if content_after_separator[actual_end_marker_start] == end_marker: end_marker_found = True - break # Stop searching after first non-whitespace char - + break if not end_marker_found: - # Try to extract the tool name for better error message tool_name = "unknown" try: - # Look for the first comma after the tool call start - partial_content = content_after_separator[ - scan_start_pos : scan_start_pos + 100 - ] # Limit to avoid huge strings + partial_content = content_after_separator[scan_start_pos : scan_start_pos + 100] comma_pos = partial_content.find(",") if comma_pos > 0: tool_name = partial_content[:comma_pos].strip() else: - # If no comma, look for opening parenthesis or first whitespace space_pos = partial_content.find(" ") paren_pos = partial_content.find("(") if space_pos > 0 and (paren_pos < 0 or space_pos < paren_pos): @@ -1521,68 +1121,46 @@ async def _process_tool_commands(self, content): elif paren_pos > 0: tool_name = partial_content[:paren_pos].strip() except Exception: - pass # Silently fail if we can't extract the name - - # Malformed call: couldn't find matching ')' or the subsequent ']' + pass self.io.tool_warning( f"Malformed tool call for '{tool_name}'. Missing closing parenthesis or" " bracket. Skipping." ) - # Append the start marker itself to processed content so it's not lost processed_content += start_marker - last_index = scan_start_pos # Continue searching after the marker + last_index = scan_start_pos continue - - # Found a potential tool call - # Adjust full_match_str and last_index based on the actual end marker ']' position - full_match_str = content_after_separator[ - start_pos : actual_end_marker_start + 1 - ] # End marker ']' is 1 char + full_match_str = content_after_separator[start_pos : actual_end_marker_start + 1] inner_content = content_after_separator[scan_start_pos:end_paren_pos].strip() - last_index = actual_end_marker_start + 1 # Move past the processed call (including ']') - + last_index = actual_end_marker_start + 1 call_count += 1 if call_count > max_calls: self.io.tool_warning( f"Exceeded maximum tool calls ({max_calls}). Skipping remaining calls." ) - # Don't append the skipped call to processed_content - continue # Skip processing this call - + continue tool_calls_found = True tool_name = None params = {} result_message = None - - # Mark that we found at least one tool call (assuming it passes validation) tool_calls_found = True - try: - # Pre-process inner_content to handle non-identifier tool names by quoting them. - # This allows ast.parse to succeed on names like 'resolve-library-id'. if inner_content: parts = inner_content.split(",", 1) potential_tool_name = parts[0].strip() - is_string = ( - potential_tool_name.startswith("'") and potential_tool_name.endswith("'") - ) or (potential_tool_name.startswith('"') and potential_tool_name.endswith('"')) - + potential_tool_name.startswith("'") + and potential_tool_name.endswith("'") + or potential_tool_name.startswith('"') + and potential_tool_name.endswith('"') + ) if not potential_tool_name.isidentifier() and not is_string: - # It's not a valid identifier and not a string, so quote it. - # Use json.dumps to handle escaping correctly. quoted_tool_name = json.dumps(potential_tool_name) if len(parts) > 1: inner_content = quoted_tool_name + ", " + parts[1] else: inner_content = quoted_tool_name - - # Wrap the inner content to make it parseable as a function call - # Example: ToolName, key="value" becomes f(ToolName, key="value") parse_str = f"f({inner_content})" parsed_ast = ast.parse(parse_str) - - # Validate AST structure if ( not isinstance(parsed_ast, ast.Module) or not parsed_ast.body @@ -1592,11 +1170,8 @@ async def _process_tool_commands(self, content): call_node = parsed_ast.body[0].value if not isinstance(call_node, ast.Call): raise ValueError("Expected a Call node") - - # Extract tool name (should be the first positional argument) if not call_node.args: raise ValueError("Tool name not found or invalid") - tool_name_node = call_node.args[0] if isinstance(tool_name_node, ast.Name): tool_name = tool_name_node.id @@ -1606,34 +1181,25 @@ async def _process_tool_commands(self, content): tool_name = tool_name_node.value else: raise ValueError("Tool name must be an identifier or a string literal") - tool_names.append(tool_name) - - # Extract keyword arguments for keyword in call_node.keywords: key = keyword.arg value_node = keyword.value - # Extract value based on AST node type if isinstance(value_node, ast.Constant): value = value_node.value - # Check if this is a multiline string and trim whitespace if isinstance(value, str) and "\n" in value: - # Get the source line(s) for this node to check if it's a triple-quoted string lineno = value_node.lineno if hasattr(value_node, "lineno") else 0 end_lineno = ( value_node.end_lineno if hasattr(value_node, "end_lineno") else lineno ) - if end_lineno > lineno: # It's a multiline string - # Trim exactly one leading and one trailing newline if present + if end_lineno > lineno: if value.startswith("\n"): value = value[1:] if value.endswith("\n"): value = value[:-1] - elif isinstance( - value_node, ast.Name - ): # Handle unquoted values like True/False/None or variables + elif isinstance(value_node, ast.Name): id_val = value_node.id.lower() if id_val == "true": value = True @@ -1642,15 +1208,11 @@ async def _process_tool_commands(self, content): elif id_val == "none": value = None else: - value = value_node.id # Keep as string if it's something else - # Add more types if needed (e.g., ast.List, ast.Dict) + value = value_node.id else: - # Attempt to reconstruct the source for complex types, or raise error try: - # Note: ast.unparse requires Python 3.9+ - # If using older Python, might need a different approach or limit supported types value = ast.unparse(value_node) - except AttributeError: # Handle case where ast.unparse is not available + except AttributeError: raise ValueError( f"Unsupported argument type for key '{key}': {type(value_node)}" ) @@ -1658,8 +1220,6 @@ async def _process_tool_commands(self, content): raise ValueError( f"Could not unparse value for key '{key}': {unparse_e}" ) - - # Check for suppressed values (e.g., "...") suppressed_arg_values = ["..."] if isinstance(value, str) and value in suppressed_arg_values: self.io.tool_warning( @@ -1667,49 +1227,30 @@ async def _process_tool_commands(self, content): f" '{tool_name}'" ) continue - params[key] = value - except (SyntaxError, ValueError) as e: result_message = f"Error parsing tool call '{inner_content}': {e}" self.io.tool_error(f"Failed to parse tool call: {full_match_str}\nError: {e}") - # Don't append the malformed call to processed_content result_messages.append(f"[Result (Parse Error): {result_message}]") - continue # Skip execution - except Exception as e: # Catch any other unexpected parsing errors + continue + except Exception as e: result_message = f"Unexpected error parsing tool call '{inner_content}': {e}" - self.io.tool_error( - f"Unexpected error during parsing: {full_match_str}\nError:" - f" {e}\n{traceback.format_exc()}" - ) + self.io.tool_error(f"""Unexpected error during parsing: {full_match_str} +Error: {e} +{traceback.format_exc()}""") result_messages.append(f"[Result (Parse Error): {result_message}]") continue - - # Execute the tool using the registry try: - # Normalize tool name for case-insensitive matching norm_tool_name = tool_name.lower() - - # Use the tool registry for execution result_message = await self._execute_tool_with_registry(norm_tool_name, params) - except Exception as e: result_message = f"Error executing {tool_name}: {str(e)}" - self.io.tool_error( - f"Error during {tool_name} execution: {e}\n{traceback.format_exc()}" - ) - + self.io.tool_error(f"""Error during {tool_name} execution: {e} +{traceback.format_exc()}""") if result_message: result_messages.append(f"[Result ({tool_name}): {result_message}]") - - # Note: We don't add the tool call string back to processed_content - - # Update internal counter self.tool_call_count += call_count - - # Return the content with tool calls removed modified_content = processed_content - return ( modified_content, result_messages, @@ -1733,20 +1274,12 @@ def _get_repetitive_tools(self): as that suggests progress is being made. """ history_len = len(self.tool_usage_history) - - # Not enough history to detect a pattern if history_len < 5: return set() - - # Check for similarity-based repetition similarity_repetitive_tools = self._get_repetitive_tools_by_similarity() - - # Flatten the tool usage history for count-based analysis all_tools = [] for round_tools in self.tool_usage_history: all_tools.extend(round_tools) - - # If the last round contained a write tool, we're likely making progress. if self.last_round_tools: last_round_has_write = any( tool.lower() in self.write_tools for tool in self.last_round_tools @@ -1754,25 +1287,17 @@ def _get_repetitive_tools(self): if last_round_has_write: self.tool_usage_history = [] return similarity_repetitive_tools if len(similarity_repetitive_tools) else set() - - # If all tools in history are read tools, return all of them if all(tool.lower() in self.read_tools for tool in all_tools): return set(all_tools) - - # Check for any read tool used more than 5 times across rounds tool_counts = Counter(all_tools) count_repetitive_tools = { tool for tool, count in tool_counts.items() if count >= 5 and tool.lower() in self.read_tools } - - # Combine both detection methods repetitive_tools = count_repetitive_tools.union(similarity_repetitive_tools) - if repetitive_tools: return repetitive_tools - return set() def _get_repetitive_tools_by_similarity(self): @@ -1787,20 +1312,12 @@ def _get_repetitive_tools_by_similarity(self): """ if not self.tool_usage_history or len(self.tool_call_vectors) < 2: return set() - - # Get the latest tool call vector latest_vector = self.tool_call_vectors[-1] - - # Check similarity against historical vectors (excluding the latest) for i, historical_vector in enumerate(self.tool_call_vectors[:-1]): similarity = cosine_similarity(latest_vector, historical_vector) - - # If similarity is high enough, flag as repetitive if similarity >= self.tool_similarity_threshold: - # Return the tool name from the corresponding position in history if i < len(self.tool_usage_history): return {self.tool_usage_history[i]} - return set() def _generate_tool_context(self, repetitive_tools): @@ -1809,40 +1326,30 @@ def _generate_tool_context(self, repetitive_tools): """ if not self.tool_usage_history: return "" - context_parts = [''] - - # Add turn and tool call statistics context_parts.append("## Turn and Tool Call Statistics") context_parts.append(f"- Current turn: {self.num_reflections + 1}") context_parts.append(f"- Total tool calls this turn: {self.num_tool_calls}") context_parts.append("\n\n") - - # Add recent tool usage history context_parts.append("## Recent Tool Usage History") if len(self.tool_usage_history) > 10: recent_history = self.tool_usage_history[-10:] context_parts.append("(Showing last 10 tools)") else: recent_history = self.tool_usage_history - for i, tool in enumerate(recent_history, 1): context_parts.append(f"{i}. {tool}") context_parts.append("\n\n") - if repetitive_tools: - context_parts.append( - "**Instruction:**\nYou have used the following tool(s) repeatedly:" - ) - + context_parts.append("""**Instruction:** +You have used the following tool(s) repeatedly:""") context_parts.append("### DO NOT USE THE FOLLOWING TOOLS/FUNCTIONS") - for tool in repetitive_tools: context_parts.append(f"- `{tool}`") context_parts.append( "Your exploration appears to be stuck in a loop. Please try a different approach." - " Use the `Thinking` tool to clarify your intentions and new approach to" - " what you are currently attempting to accomplish." + " Use the `Thinking` tool to clarify your intentions and new approach to what you" + " are currently attempting to accomplish." ) context_parts.append("\n") context_parts.append("**Suggestions for alternative approaches:**") @@ -1865,7 +1372,6 @@ def _generate_tool_context(self, repetitive_tools): "You most likely have enough context for a subset of the necessary changes." ) context_parts.append("Please prioritize file editing over further exploration.") - context_parts.append("") return "\n".join(context_parts) @@ -1879,14 +1385,12 @@ def _generate_write_context(self): '', "A file was just edited.", ( - " Do not just modify comments" - " and/or logging statements with placeholder information." + " Do not just modify comments and/or logging statements with placeholder" + " information." ), "Make sure that something of value was done.", ] - return "\n".join(context_parts) - return "" async def _apply_edits_from_response(self): @@ -1896,54 +1400,36 @@ async def _apply_edits_from_response(self): """ edited_files = set() try: - # 1. Get edits (logic from EditBlockCoder.get_edits) - # Use the current partial_response_content which contains the LLM response - # including the edit blocks but excluding the tool calls. edits = list( find_original_update_blocks( - self.partial_response_content, - self.fence, - self.get_inchat_relative_files(), + self.partial_response_content, self.fence, self.get_inchat_relative_files() ) ) - # Separate shell commands from file edits self.shell_commands += [edit[1] for edit in edits if edit[0] is None] edits = [edit for edit in edits if edit[0] is not None] - - # 2. Prepare edits (check permissions, commit dirty files) prepared_edits = [] seen_paths = dict() - self.need_commit_before_edits = set() # Reset before checking - + self.need_commit_before_edits = set() for edit in edits: path = edit[0] if path in seen_paths: allowed = seen_paths[path] else: - # Use the base Coder's permission check method allowed = await self.allowed_to_edit(path) seen_paths[path] = allowed if allowed: prepared_edits.append(edit) - - # Commit any dirty files identified by allowed_to_edit await self.dirty_commit() - self.need_commit_before_edits = set() # Clear after commit - - # 3. Apply edits (logic adapted from EditBlockCoder.apply_edits) + self.need_commit_before_edits = set() failed = [] passed = [] for edit in prepared_edits: path, original, updated = edit full_path = self.abs_root_path(path) new_content = None - if Path(full_path).exists(): content = self.io.read_text(full_path) - # Use the imported do_replace function new_content = do_replace(full_path, content, original, updated, self.fence) - - # Simplified cross-file patching check from EditBlockCoder if not new_content and original.strip(): for other_full_path in self.abs_fnames: if other_full_path == full_path: @@ -1958,26 +1444,22 @@ async def _apply_edits_from_response(self): new_content = other_new_content self.io.tool_warning(f"Applied edit intended for {edit[0]} to {path}") break - if new_content: if not self.dry_run: self.io.write_text(full_path, new_content) self.io.tool_output(f"Applied edit to {path}") else: self.io.tool_output(f"Did not apply edit to {path} (--dry-run)") - passed.append((path, original, updated)) # Store path relative to root + passed.append((path, original, updated)) else: failed.append(edit) - if failed: - # Handle failed edits (adapted from EditBlockCoder) blocks = "block" if len(failed) == 1 else "blocks" error_message = f"# {len(failed)} SEARCH/REPLACE {blocks} failed to match!\n" for edit in failed: path, original, updated = edit full_path = self.abs_root_path(path) - content = self.io.read_text(full_path) # Read content again for context - + content = self.io.read_text(full_path) error_message += f""" ## SearchReplaceNoExactMatch: This SEARCH block failed to exactly match lines in {path} <<<<<<< SEARCH @@ -2001,7 +1483,7 @@ async def _apply_edits_from_response(self): """ error_message += ( "The SEARCH section must exactly match an existing block of lines including all" - " white space, comments, indentation, docstrings, etc\n" + " white space, comments, indentation, docstrings, etc" ) if passed: pblocks = "block" if len(passed) == 1 else "blocks" @@ -2011,49 +1493,36 @@ async def _apply_edits_from_response(self): Just reply with fixed versions of the {blocks} above that failed to match. """ self.io.tool_error(error_message) - # Set reflected_message to prompt LLM to fix the failed blocks self.reflected_message = error_message - - edited_files = set(edit[0] for edit in passed) # Use relative paths stored in passed - - # 4. Post-edit actions (commit, lint, test, shell commands) + edited_files = set(edit[0] for edit in passed) if edited_files: - self.aider_edited_files.update(edited_files) # Track edited files + self.coder_edited_files.update(edited_files) self.auto_commit(edited_files) - # We don't use saved_message here as we are not moving history back - if self.auto_lint: lint_errors = self.lint_edited(edited_files) self.auto_commit(edited_files, context="Ran the linter") - if lint_errors and not self.reflected_message: # Reflect only if no edit errors + if lint_errors and not self.reflected_message: ok = await self.io.confirm_ask("Attempt to fix lint errors?") if ok: self.reflected_message = lint_errors - shared_output = await self.run_shell_commands() if shared_output: - # Add shell output as a new user message? Or just display? - # Let's just display for now to avoid complex history manipulation self.io.tool_output("Shell command output:\n" + shared_output) - - if self.auto_test and not self.reflected_message: # Reflect only if no prior errors + if self.auto_test and not self.reflected_message: test_errors = await self.commands.cmd_test(self.test_cmd) if test_errors: ok = await self.io.confirm_ask("Attempt to fix test errors?") if ok: self.reflected_message = test_errors - self.show_undo_hint() - except ValueError as err: - # Handle parsing errors from find_original_update_blocks self.num_malformed_responses += 1 error_message = err.args[0] self.io.tool_error("The LLM did not conform to the edit format.") self.io.tool_output(urls.edit_errors) self.io.tool_output() self.io.tool_output(str(error_message)) - self.reflected_message = str(error_message) # Reflect parsing errors + self.reflected_message = str(error_message) except ANY_GIT_ERROR as err: self.io.tool_error(f"Git error during edit application: {str(err)}") self.reflected_message = f"Git error during edit application: {str(err)}" @@ -2062,7 +1531,6 @@ async def _apply_edits_from_response(self): self.io.tool_error(str(err), strip=False) self.io.tool_error(traceback.format_exc()) self.reflected_message = f"Exception while applying edits: {str(err)}" - return edited_files def _add_file_to_context(self, file_path, explicit=False): @@ -2073,57 +1541,39 @@ def _add_file_to_context(self, file_path, explicit=False): - file_path: Path to the file to add - explicit: Whether this was an explicit view command (vs. implicit through ViewFilesMatching) """ - # Check if file exists abs_path = self.abs_root_path(file_path) rel_path = self.get_rel_fname(abs_path) - if not os.path.isfile(abs_path): self.io.tool_output(f"⚠️ File '{file_path}' not found") return "File not found" - - # Check if the file is already in context (either editable or read-only) if abs_path in self.abs_fnames: if explicit: self.io.tool_output(f"📎 File '{file_path}' already in context as editable") return "File already in context as editable" return "File already in context as editable" - if abs_path in self.abs_read_only_fnames: if explicit: self.io.tool_output(f"📎 File '{file_path}' already in context as read-only") return "File already in context as read-only" return "File already in context as read-only" - - # Add file to context as read-only try: - # Check for large file and apply context management if enabled content = self.io.read_text(abs_path) if content is None: return f"Error reading file: {file_path}" - - # Check if file is very large and context management is enabled if self.context_management_enabled: file_tokens = self.main_model.token_count(content) if file_tokens > self.large_file_token_threshold: self.io.tool_output( - f"⚠️ '{file_path}' is very large ({file_tokens} tokens). " - "Use /context-management to toggle truncation off if needed." + f"⚠️ '{file_path}' is very large ({file_tokens} tokens). Use" + " /context-management to toggle truncation off if needed." ) - - # Add to read-only files self.abs_read_only_fnames.add(abs_path) - - # Track in exploration set self.files_added_in_exploration.add(rel_path) - - # Inform user if explicit: self.io.tool_output(f"📎 Viewed '{file_path}' (added to context as read-only)") return "Viewed file (added to context as read-only)" else: - # For implicit adds (from ViewFilesAtGlob/ViewFilesMatching), just return success return "Added file to context as read-only" - except Exception as e: self.io.tool_error(f"Error adding file '{file_path}' for viewing: {str(e)}") return f"Error adding file for viewing: {str(e)}" @@ -2134,15 +1584,9 @@ def _process_file_mentions(self, content): This handles the case where the LLM mentions file paths without using explicit tool commands. """ - # Extract file mentions using the parent class's method mentioned_files = set(self.get_file_mentions(content, ignore_current=False)) current_files = set(self.get_inchat_relative_files()) - - # Get new files to add (not already in context) mentioned_files - current_files - - # In agent mode, we *only* add files via explicit tool commands (`View`, `ViewFilesAtGlob`, etc.). - # Do nothing here for implicit mentions. pass async def check_for_file_mentions(self, content): @@ -2153,7 +1597,6 @@ async def check_for_file_mentions(self, content): Files should only be added via explicit tool commands (`View`, `ViewFilesAtGlob`, `ViewFilesMatching`, `ViewFilesWithSymbol`). """ - # Do nothing - disable implicit file adds in agent mode. pass async def preproc_user_input(self, inp): @@ -2161,13 +1604,9 @@ async def preproc_user_input(self, inp): Override parent's method to wrap user input in a context block. This clearly delineates user input from other sections in the context window. """ - # First apply the parent's preprocessing inp = await super().preproc_user_input(inp) - - # If we still have input after preprocessing, wrap it in a context block if inp and not inp.startswith(''): inp = f'\n{inp}\n' - return inp def get_directory_structure(self): @@ -2177,97 +1616,68 @@ def get_directory_structure(self): """ if not self.use_enhanced_context: return None - try: - # Start with the header result = '\n' result += "## Project File Structure\n\n" result += ( - "Below is a snapshot of this project's file structure at the current time. It skips" - " over .gitignore patterns.\n\n" + "Below is a snapshot of this project's file structure at the current time. " + "It skips over .gitignore patterns." ) - - # Get the root directory Path(self.root) - - # Get all files in the repo (both tracked and untracked) if self.repo: - # Get tracked files tracked_files = self.repo.get_tracked_files() - - # Get untracked files (files present in the working directory but not in git) untracked_files = [] try: - # Run git status to get untracked files untracked_output = self.repo.repo.git.status("--porcelain") for line in untracked_output.splitlines(): if line.startswith("??"): - # Extract the filename (remove the '?? ' prefix) untracked_file = line[3:] if not self.repo.ignored_file(untracked_file): untracked_files.append(untracked_file) except Exception as e: self.io.tool_warning(f"Error getting untracked files: {str(e)}") - - # Combine tracked and untracked files all_files = tracked_files + untracked_files else: - # If no repo, get all files relative to root all_files = [] for path in Path(self.root).rglob("*"): if path.is_file(): all_files.append(str(path.relative_to(self.root))) - - # Sort files to ensure deterministic output all_files = sorted(all_files) - - # Filter out .aider files/dirs all_files = [ - f for f in all_files if not any(part.startswith(".aider") for part in f.split("/")) + f for f in all_files if not any(part.startswith(".cecli") for part in f.split("/")) ] - - # Build tree structure tree = {} for file in all_files: parts = file.split("/") current = tree for i, part in enumerate(parts): - if i == len(parts) - 1: # Last part (file) + if i == len(parts) - 1: if "." not in current: current["."] = [] current["."].append(part) - else: # Directory + else: if part not in current: current[part] = {} current = current[part] - # Function to recursively print the tree def print_tree(node, prefix="- ", indent=" ", current_path=""): lines = [] - # First print all directories dirs = sorted([k for k in node.keys() if k != "."]) for i, dir_name in enumerate(dirs): - # Only print the current directory name, not the full path lines.append(f"{prefix}{dir_name}/") sub_lines = print_tree( node[dir_name], prefix=prefix, indent=indent, current_path=dir_name ) for sub_line in sub_lines: lines.append(f"{indent}{sub_line}") - - # Then print all files if "." in node: for file_name in sorted(node["."]): - # Only print the current file name, not the full path lines.append(f"{prefix}{file_name}") - return lines - # Generate the tree starting from root tree_lines = print_tree(tree, prefix="- ") result += "\n".join(tree_lines) result += "\n" - return result except Exception as e: self.io.tool_error(f"Error generating directory structure: {str(e)}") @@ -2275,37 +1685,25 @@ def print_tree(node, prefix="- ", indent=" ", current_path=""): def get_todo_list(self): """ - Generate a todo list context block from the .aider.todo.txt file. + Generate a todo list context block from the .cecli.todo.txt file. Returns formatted string with the current todo list or None if empty/not present. """ - try: - # Define the todo file path - todo_file_path = ".aider.todo.txt" + todo_file_path = ".cecli.todo.txt" abs_path = self.abs_root_path(todo_file_path) - - # Check if file exists import os if not os.path.isfile(abs_path): - return ( - '\n' - "Todo list does not exist. Please update it with the `UpdataTodoList` tool." - "" - ) - - # Read todo list content + return """ +Todo list does not exist. Please update it with the `UpdataTodoList` tool.""" content = self.io.read_text(abs_path) if content is None or not content.strip(): return None - - # Format the todo list context block result = '\n' result += "## Current Todo List\n\n" result += "Below is the current todo list managed via the `UpdateTodoList` tool:\n\n" result += f"```\n{content}\n```\n" result += "" - return result except Exception as e: self.io.tool_error(f"Error generating todo list context: {str(e)}") @@ -2320,7 +1718,6 @@ def get_skills_context(self): """ if not self.use_enhanced_context or not self.skills_manager: return None - try: return self.skills_manager.get_skills_context() except Exception as e: @@ -2336,7 +1733,6 @@ def get_skills_content(self): """ if not self.use_enhanced_context or not self.skills_manager: return None - try: return self.skills_manager.get_skills_content() except Exception as e: @@ -2350,20 +1746,15 @@ def get_git_status(self): """ if not self.use_enhanced_context or not self.repo: return None - try: result = '\n' result += "## Git Repository Status\n\n" result += "This is a snapshot of the git status at the current time.\n" - - # Get current branch try: current_branch = self.repo.repo.active_branch.name result += f"Current branch: {current_branch}\n\n" except Exception: result += "Current branch: (detached HEAD state)\n\n" - - # Get main/master branch main_branch = None try: for branch in self.repo.repo.branches: @@ -2374,53 +1765,36 @@ def get_git_status(self): result += f"Main branch (you will usually use this for PRs): {main_branch}\n\n" except Exception: pass - - # Git status result += "Status:\n" try: - # Get modified files status = self.repo.repo.git.status("--porcelain") - - # Process and categorize the status output if status: status_lines = status.strip().split("\n") - - # Group by status type for better organization staged_added = [] staged_modified = [] staged_deleted = [] unstaged_modified = [] unstaged_deleted = [] untracked = [] - for line in status_lines: - if len(line) < 4: # Ensure the line has enough characters + if len(line) < 4: continue - status_code = line[:2] file_path = line[3:] - - # Skip .aider files/dirs - if any(part.startswith(".aider") for part in file_path.split("/")): + if any(part.startswith(".cecli") for part in file_path.split("/")): continue - - # Staged changes if status_code[0] == "A": staged_added.append(file_path) elif status_code[0] == "M": staged_modified.append(file_path) elif status_code[0] == "D": staged_deleted.append(file_path) - # Unstaged changes if status_code[1] == "M": unstaged_modified.append(file_path) elif status_code[1] == "D": unstaged_deleted.append(file_path) - # Untracked files if status_code == "??": untracked.append(file_path) - - # Output in a nicely formatted manner if staged_added: for file in staged_added: result += f"A {file}\n" @@ -2443,18 +1817,15 @@ def get_git_status(self): result += "Working tree clean\n" except Exception as e: result += f"Unable to get modified files: {str(e)}\n" - - # Recent commits result += "\nRecent commits:\n" try: commits = list(self.repo.repo.iter_commits(max_count=5)) for commit in commits: short_hash = commit.hexsha[:8] - message = commit.message.strip().split("\n")[0] # First line only + message = commit.message.strip().split("\n")[0] result += f"{short_hash} {message}\n" except Exception: result += "Unable to get recent commits\n" - result += "" return result except Exception as e: @@ -2466,13 +1837,11 @@ def cmd_context_blocks(self, args=""): Toggle enhanced context blocks feature. """ self.use_enhanced_context = not self.use_enhanced_context - if self.use_enhanced_context: self.io.tool_output( "Enhanced context blocks are now ON - directory structure and git status will be" " included." ) - # Mark tokens as needing calculation, but don't calculate yet (lazy calculation) self.tokens_calculated = False self.context_blocks_cache = {} else: @@ -2480,11 +1849,9 @@ def cmd_context_blocks(self, args=""): "Enhanced context blocks are now OFF - directory structure and git status will not" " be included." ) - # Clear token counts and cache when disabled self.context_block_tokens = {} self.context_blocks_cache = {} self.tokens_calculated = False - return True def _log_chunks(self, chunks): @@ -2507,7 +1874,6 @@ def _log_chunks(self, chunks): "post_message": None, "reminder": None, } - changes = [] for key, value in self._message_hashes.items(): json_obj = json.dumps( @@ -2516,16 +1882,13 @@ def _log_chunks(self, chunks): new_hash = hashlib.sha256(json_obj.encode("utf-8")).hexdigest() if self._message_hashes[key] != new_hash: changes.append(key) - self._message_hashes[key] = new_hash - print("") print("MESSAGE CHUNK HASHES") print(self._message_hashes) print("") print(changes) print("") - except Exception as e: print(e) pass diff --git a/aider/coders/architect_coder.py b/cecli/coders/architect_coder.py similarity index 96% rename from aider/coders/architect_coder.py rename to cecli/coders/architect_coder.py index cf76fb6eb0a..a5382f44aa8 100644 --- a/aider/coders/architect_coder.py +++ b/cecli/coders/architect_coder.py @@ -56,7 +56,7 @@ async def reply_completed(self): await editor_coder.generate(user_message=content, preproc=False) self.move_back_cur_messages("I made those changes to the files.") self.total_cost = editor_coder.total_cost - self.aider_commit_hashes = editor_coder.aider_commit_hashes + self.coder_commit_hashes = editor_coder.coder_commit_hashes except Exception as e: self.io.tool_error(e) diff --git a/aider/coders/ask_coder.py b/cecli/coders/ask_coder.py similarity index 100% rename from aider/coders/ask_coder.py rename to cecli/coders/ask_coder.py diff --git a/aider/coders/base_coder.py b/cecli/coders/base_coder.py similarity index 98% rename from aider/coders/base_coder.py rename to cecli/coders/base_coder.py index d4ffe2fc5d1..0ed6d16593d 100755 --- a/aider/coders/base_coder.py +++ b/cecli/coders/base_coder.py @@ -34,30 +34,30 @@ from prompt_toolkit.patch_stdout import patch_stdout from rich.console import Console -import aider.prompts.utils.system as prompts -from aider import __version__, models, urls, utils -from aider.commands import Commands, SwitchCoder -from aider.exceptions import LiteLLMExceptions -from aider.helpers import coroutines -from aider.helpers.profiler import TokenProfiler -from aider.history import ChatSummary -from aider.io import ConfirmGroup, InputOutput -from aider.linter import Linter -from aider.llm import litellm -from aider.mcp.server import LocalServer -from aider.models import RETRY_TIMEOUT -from aider.reasoning_tags import ( +import cecli.prompts.utils.system as prompts +from cecli import __version__, models, urls, utils +from cecli.commands import Commands, SwitchCoder +from cecli.exceptions import LiteLLMExceptions +from cecli.helpers import coroutines +from cecli.helpers.profiler import TokenProfiler +from cecli.history import ChatSummary +from cecli.io import ConfirmGroup, InputOutput +from cecli.linter import Linter +from cecli.llm import litellm +from cecli.mcp.server import LocalServer +from cecli.models import RETRY_TIMEOUT +from cecli.reasoning_tags import ( REASONING_TAG, format_reasoning_content, remove_reasoning_content, replace_reasoning_tags, ) -from aider.repo import ANY_GIT_ERROR, GitRepo -from aider.repomap import RepoMap -from aider.run_cmd import run_cmd -from aider.sessions import SessionManager -from aider.tools.utils.output import print_tool_response -from aider.utils import format_tokens, is_image_file +from cecli.repo import ANY_GIT_ERROR, GitRepo +from cecli.repomap import RepoMap +from cecli.run_cmd import run_cmd +from cecli.sessions import SessionManager +from cecli.tools.utils.output import print_tool_response +from cecli.utils import format_tokens, is_image_file from ..dump import dump # noqa: F401 from ..prompts.utils.prompt_registry import registry @@ -101,8 +101,8 @@ class Coder: abs_read_only_fnames = None abs_read_only_stubs_fnames = None repo = None - last_aider_commit_hash = None - aider_edited_files = None + last_coder_commit_hash = None + coder_edited_files = None last_asked_for_commit_time = 0 repo_map = None functions = None @@ -171,7 +171,7 @@ async def create( args=None, **kwargs, ): - import aider.coders as coders + import cecli.coders as coders if not main_model: if from_coder: @@ -220,7 +220,7 @@ async def create( ), # Copy read-only stubs done_messages=done_messages, cur_messages=from_coder.cur_messages, - aider_commit_hashes=from_coder.aider_commit_hashes, + coder_commit_hashes=from_coder.coder_commit_hashes, commands=from_coder.commands.clone(), total_cost=from_coder.total_cost, ignore_mentions=from_coder.ignore_mentions, @@ -296,7 +296,7 @@ def __init__( auto_test=False, lint_cmds=None, test_cmd=None, - aider_commit_hashes=None, + coder_commit_hashes=None, map_mul_no_files=8, map_max_line_length=100, commands=None, @@ -329,7 +329,7 @@ def __init__( self.chat_language = chat_language self.commit_language = commit_language self.commit_before_message = [] - self.aider_commit_hashes = set() + self.coder_commit_hashes = set() self.rejected_urls = set() self.abs_root_path_cache = {} @@ -361,10 +361,10 @@ def __init__( if io is None: io = InputOutput() - if aider_commit_hashes: - self.aider_commit_hashes = aider_commit_hashes + if coder_commit_hashes: + self.coder_commit_hashes = coder_commit_hashes else: - self.aider_commit_hashes = set() + self.coder_commit_hashes = set() self.chat_completion_call_hashes = [] self.chat_completion_response_hashes = [] @@ -458,7 +458,7 @@ def __init__( continue if self.repo and self.repo.ignored_file(fname): - self.io.tool_warning(f"Skipping {fname} that matches aiderignore spec.") + self.io.tool_warning(f"Skipping {fname} that matches cecli.ignore spec.") continue if not fname.exists(): @@ -553,7 +553,7 @@ def __init__( self.test_cmd = test_cmd # Clean up todo list file on startup; sessions will restore it when needed - todo_file_path = ".aider.todo.txt" + todo_file_path = ".cecli.todo.txt" abs_path = self.abs_root_path(todo_file_path) if os.path.isfile(abs_path): try: @@ -666,7 +666,7 @@ def get_announcements(self): lines.append(f"Git repo: {rel_repo_dir} with {num_files:,} files") if num_files > 1000: lines.append( - "Warning: For large repos, consider using --subtree-only and .aiderignore" + "Warning: For large repos, consider using --subtree-only and .cecli_ignore" ) lines.append(f"See: {urls.large_repos}") else: @@ -1256,7 +1256,7 @@ async def run_stream(self, user_message): yield chunk def init_before_message(self): - self.aider_edited_files = set() + self.coder_edited_files = set() self.reflected_message = None self.num_reflections = 0 self.lint_outcome = None @@ -2131,7 +2131,7 @@ def warm_cache(self, chunks): return delay = 5 * 60 - 5 - delay = float(os.environ.get("AIDER_CACHE_KEEPALIVE_DELAY", delay)) + delay = float(os.environ.get("CECLICACHE_KEEPALIVE_DELAY", delay)) self.next_cache_warm = time.time() + delay self.warming_pings_left = self.num_cache_warming_pings self.cache_warming_chunks = chunks @@ -2354,7 +2354,7 @@ async def send_message(self, inp): edited = await self.apply_updates() if edited: - self.aider_edited_files.update(edited) + self.coder_edited_files.update(edited) saved_message = await self.auto_commit(edited) if not saved_message and hasattr(self.gpt_prompts, "files_content_gpt_edits_no_repo"): @@ -3087,7 +3087,7 @@ async def show_send_output_stream(self, completion): async for chunk in completion: if self.args.debug: - with open(".aider/logs/chunks.log", "a") as f: + with open(".cecli/logs/chunks.log", "a") as f: print(chunk, file=f) # Check if confirmation is in progress and wait if needed @@ -3809,7 +3809,7 @@ async def auto_commit(self, edited, context=None): try: res = await self.repo.commit( - fnames=edited, context=context, aider_edits=True, coder=self + fnames=edited, context=context, coder_edits=True, coder=self ) if res: self.show_auto_commit_outcome(res) @@ -3826,9 +3826,9 @@ async def auto_commit(self, edited, context=None): def show_auto_commit_outcome(self, res): commit_hash, commit_message = res - self.last_aider_commit_hash = commit_hash - self.aider_commit_hashes.add(commit_hash) - self.last_aider_commit_message = commit_message + self.last_coder_commit_hash = commit_hash + self.coder_commit_hashes.add(commit_hash) + self.last_coder_commit_message = commit_message if self.show_diffs: self.commands.cmd_diff() @@ -3836,7 +3836,7 @@ def show_undo_hint(self): if not self.commit_before_message: return if self.commit_before_message[-1] != self.repo.get_head_commit_sha(): - self.io.tool_output("You can use /undo to undo and discard each aider commit.") + self.io.tool_output("You can use /undo to undo and discard each cecli commit.") async def dirty_commit(self): if not self.need_commit_before_edits: diff --git a/aider/coders/chat_chunks.py b/cecli/coders/chat_chunks.py similarity index 100% rename from aider/coders/chat_chunks.py rename to cecli/coders/chat_chunks.py diff --git a/aider/coders/context_coder.py b/cecli/coders/context_coder.py similarity index 100% rename from aider/coders/context_coder.py rename to cecli/coders/context_coder.py diff --git a/aider/coders/copypaste_coder.py b/cecli/coders/copypaste_coder.py similarity index 97% rename from aider/coders/copypaste_coder.py rename to cecli/coders/copypaste_coder.py index 2ab077ad82a..1ca00540f31 100644 --- a/aider/coders/copypaste_coder.py +++ b/cecli/coders/copypaste_coder.py @@ -4,8 +4,8 @@ import time import uuid -from aider.exceptions import LiteLLMExceptions -from aider.llm import litellm +from cecli.exceptions import LiteLLMExceptions +from cecli.llm import litellm from .base_coder import Coder @@ -73,7 +73,7 @@ def _init_prompts_from_selected_edit_format(self): # Find the coder class that would have been selected for this edit_format. try: - import aider.coders as coders + import cecli.coders as coders except Exception: coders = None @@ -156,14 +156,14 @@ async def send(self, messages, model=None, functions=None, tools=None): def copy_paste_completion(self, messages, model): try: - from aider.helpers import copypaste + from cecli.helpers import copypaste except ImportError: # pragma: no cover - import error path self.io.tool_error("copy/paste mode requires the pyperclip package.") self.io.tool_output("Install it with: pip install pyperclip") raise def content_to_text(content): - """Extract text from the various content formats Aider/LLMs can produce.""" + """Extract text from the various content formats cecli/LLMs can produce.""" if not content: return "" if isinstance(content, str): diff --git a/aider/coders/editblock_coder.py b/cecli/coders/editblock_coder.py similarity index 99% rename from aider/coders/editblock_coder.py rename to cecli/coders/editblock_coder.py index 4f2425c0f54..cef1dcaf485 100644 --- a/aider/coders/editblock_coder.py +++ b/cecli/coders/editblock_coder.py @@ -5,7 +5,7 @@ from difflib import SequenceMatcher from pathlib import Path -from aider import utils +from cecli import utils from ..dump import dump # noqa: F401 from .base_coder import Coder diff --git a/aider/coders/editblock_fenced_coder.py b/cecli/coders/editblock_fenced_coder.py similarity index 100% rename from aider/coders/editblock_fenced_coder.py rename to cecli/coders/editblock_fenced_coder.py diff --git a/aider/coders/editblock_func_coder.py b/cecli/coders/editblock_func_coder.py similarity index 100% rename from aider/coders/editblock_func_coder.py rename to cecli/coders/editblock_func_coder.py diff --git a/aider/coders/editor_diff_fenced_coder.py b/cecli/coders/editor_diff_fenced_coder.py similarity index 100% rename from aider/coders/editor_diff_fenced_coder.py rename to cecli/coders/editor_diff_fenced_coder.py diff --git a/aider/coders/editor_editblock_coder.py b/cecli/coders/editor_editblock_coder.py similarity index 100% rename from aider/coders/editor_editblock_coder.py rename to cecli/coders/editor_editblock_coder.py diff --git a/aider/coders/editor_whole_coder.py b/cecli/coders/editor_whole_coder.py similarity index 100% rename from aider/coders/editor_whole_coder.py rename to cecli/coders/editor_whole_coder.py diff --git a/aider/coders/help_coder.py b/cecli/coders/help_coder.py similarity index 81% rename from aider/coders/help_coder.py rename to cecli/coders/help_coder.py index 613e83d9e17..728aef49b3d 100644 --- a/aider/coders/help_coder.py +++ b/cecli/coders/help_coder.py @@ -3,7 +3,7 @@ class HelpCoder(Coder): - """Interactive help and documentation about aider.""" + """Interactive help and documentation about cecli.""" edit_format = "help" prompt_format = "help" diff --git a/aider/coders/patch_coder.py b/cecli/coders/patch_coder.py similarity index 100% rename from aider/coders/patch_coder.py rename to cecli/coders/patch_coder.py diff --git a/aider/coders/search_replace.py b/cecli/coders/search_replace.py similarity index 99% rename from aider/coders/search_replace.py rename to cecli/coders/search_replace.py index 320ff3ec794..a51ef94970e 100755 --- a/aider/coders/search_replace.py +++ b/cecli/coders/search_replace.py @@ -11,8 +11,8 @@ from diff_match_patch import diff_match_patch from tqdm import tqdm -from aider.dump import dump -from aider.utils import GitTemporaryDirectory +from cecli.dump import dump +from cecli.utils import GitTemporaryDirectory class RelativeIndenter: diff --git a/aider/coders/shell.py b/cecli/coders/shell.py similarity index 100% rename from aider/coders/shell.py rename to cecli/coders/shell.py diff --git a/aider/coders/single_wholefile_func_coder.py b/cecli/coders/single_wholefile_func_coder.py similarity index 99% rename from aider/coders/single_wholefile_func_coder.py rename to cecli/coders/single_wholefile_func_coder.py index fad3a636435..eef35a7edb2 100644 --- a/aider/coders/single_wholefile_func_coder.py +++ b/cecli/coders/single_wholefile_func_coder.py @@ -1,4 +1,4 @@ -from aider import diffs +from cecli import diffs from ..dump import dump # noqa: F401 from .base_coder import Coder diff --git a/aider/coders/udiff_coder.py b/cecli/coders/udiff_coder.py similarity index 100% rename from aider/coders/udiff_coder.py rename to cecli/coders/udiff_coder.py diff --git a/aider/coders/udiff_simple.py b/cecli/coders/udiff_simple.py similarity index 100% rename from aider/coders/udiff_simple.py rename to cecli/coders/udiff_simple.py diff --git a/aider/coders/wholefile_coder.py b/cecli/coders/wholefile_coder.py similarity index 99% rename from aider/coders/wholefile_coder.py rename to cecli/coders/wholefile_coder.py index 079ac9259db..8df01bb47f5 100644 --- a/aider/coders/wholefile_coder.py +++ b/cecli/coders/wholefile_coder.py @@ -1,6 +1,6 @@ from pathlib import Path -from aider import diffs +from cecli import diffs from ..dump import dump # noqa: F401 from .base_coder import Coder diff --git a/aider/coders/wholefile_func_coder.py b/cecli/coders/wholefile_func_coder.py similarity index 99% rename from aider/coders/wholefile_func_coder.py rename to cecli/coders/wholefile_func_coder.py index d0ae3aa90ba..ed70bdd2fc2 100644 --- a/aider/coders/wholefile_func_coder.py +++ b/cecli/coders/wholefile_func_coder.py @@ -1,4 +1,4 @@ -from aider import diffs +from cecli import diffs from ..dump import dump # noqa: F401 from .base_coder import Coder diff --git a/cecli/commands.py b/cecli/commands.py new file mode 100644 index 00000000000..6e2d65a94d4 --- /dev/null +++ b/cecli/commands.py @@ -0,0 +1,207 @@ +import asyncio +import re +import sys +from pathlib import Path + +from cecli.helpers.file_searcher import handle_core_files +from cecli.repo import ANY_GIT_ERROR + +from .commands.utils.registry import CommandRegistry + + +class SwitchCoder(Exception): + def __init__(self, placeholder=None, **kwargs): + self.kwargs = kwargs + self.placeholder = placeholder + + +class Commands: + scraper = None + + def clone(self): + return Commands( + self.io, + None, + voice_language=self.voice_language, + voice_input_device=self.voice_input_device, + voice_format=self.voice_format, + verify_ssl=self.verify_ssl, + args=self.args, + parser=self.parser, + verbose=self.verbose, + editor=self.editor, + original_read_only_fnames=self.original_read_only_fnames, + ) + + def __init__( + self, + io, + coder, + voice_language=None, + voice_input_device=None, + voice_format=None, + verify_ssl=True, + args=None, + parser=None, + verbose=False, + editor=None, + original_read_only_fnames=None, + ): + self.io = io + self.coder = coder + self.parser = parser + self.args = args + self.verbose = verbose + self.verify_ssl = verify_ssl + if voice_language == "auto": + voice_language = None + self.voice_language = voice_language + self.voice_format = voice_format + self.voice_input_device = voice_input_device + self.help = None + self.editor = editor + self.original_read_only_fnames = set(original_read_only_fnames or []) + self.cmd_running_event = asyncio.Event() + self.cmd_running_event.set() + + def is_command(self, inp): + return inp[0] in "/!" + + def is_run_command(self, inp): + return inp and ( + inp[0] in "!" or inp[:5] == "/lint" or inp[:5] == "/test" or inp[:4] == "/run" + ) + + def is_test_command(self, inp): + return inp and (inp[:5] == "/lint" or inp[:5] == "/test") + + def get_raw_completions(self, cmd): + assert cmd.startswith("/") + cmd = cmd[1:] + cmd = cmd.replace("-", "_") + raw_completer = getattr(self, f"completions_raw_{cmd}", None) + return raw_completer + + def get_completions(self, cmd): + assert cmd.startswith("/") + cmd = cmd[1:] + command_class = CommandRegistry.get_command(cmd) + if command_class: + return command_class.get_completions(self.io, self.coder, "") + return [] + + def get_commands(self): + registry_commands = CommandRegistry.list_commands() + commands = [f"/{cmd}" for cmd in registry_commands] + return sorted(commands) + + async def do_run(self, cmd_name, args, **kwargs): + command_class = CommandRegistry.get_command(cmd_name) + if not command_class: + self.io.tool_output(f"Error: Command {cmd_name} not found.") + return + self.cmd_running_event.clear() + try: + kwargs.update( + { + "original_read_only_fnames": self.original_read_only_fnames, + "voice_language": self.voice_language, + "voice_format": self.voice_format, + "voice_input_device": self.voice_input_device, + "verify_ssl": self.verify_ssl, + "parser": self.parser, + "verbose": self.verbose, + "editor": self.editor, + "system_args": self.args, + } + ) + return await CommandRegistry.execute(cmd_name, self.io, self.coder, args, **kwargs) + except ANY_GIT_ERROR as err: + self.io.tool_error(f"Unable to complete {cmd_name}: {err}") + return + except SwitchCoder as e: + raise e + except Exception as e: + self.io.tool_error(f"Error executing command {cmd_name}: {str(e)}") + return + finally: + self.cmd_running_event.set() + if self.coder.tui and self.coder.tui(): + self.coder.tui().refresh() + + def matching_commands(self, inp): + words = inp.strip().split() + if not words: + return + first_word = words[0] + rest_inp = inp[len(words[0]) :].strip() + all_commands = self.get_commands() + matching_commands = [cmd for cmd in all_commands if cmd.startswith(first_word)] + return matching_commands, first_word, rest_inp + + async def run(self, inp): + if inp.startswith("!"): + return await self.do_run("run", inp[1:]) + res = self.matching_commands(inp) + if res is None: + return + matching_commands, first_word, rest_inp = res + if len(matching_commands) == 1: + command = matching_commands[0][1:] + return await self.do_run(command, rest_inp) + elif first_word in matching_commands: + command = first_word[1:] + return await self.do_run(command, rest_inp) + elif len(matching_commands) > 1: + self.io.tool_error(f"Ambiguous command: {', '.join(matching_commands)}") + else: + self.io.tool_error(f"Invalid command: {first_word}") + + def get_help_md(self): + """Show help about all commands in markdown""" + res = "\n|Command|Description|\n|:------|:----------|\n" + commands = sorted(self.get_commands()) + for cmd in commands: + cmd_name = cmd[1:] + command_class = CommandRegistry.get_command(cmd_name) + if command_class: + description = command_class.DESCRIPTION + res += f"| **{cmd}** | {description} |\n" + else: + res += f"| **{cmd}** | |\n" + res += "\n" + return res + + def _get_session_directory(self): + """Get the session storage directory, creating it if needed""" + session_dir = handle_core_files(Path(self.coder.root) / ".cecli" / "sessions") + session_dir.mkdir(parents=True, exist_ok=True) + return session_dir + + def _get_session_file_path(self, session_name): + """Get the full path for a session file""" + session_dir = self._get_session_directory() + safe_name = re.sub("[^a-zA-Z0-9_.-]", "_", session_name) + ext = "" if safe_name[-5:] == ".json" else ".json" + return session_dir / f"{safe_name}{ext}" + + +def parse_quoted_filenames(args): + filenames = re.findall('\\"(.+?)\\"|(\\S+)', args) + filenames = [name for sublist in filenames for name in sublist if name] + return filenames + + +def get_help_md(): + md = Commands(None, None).get_help_md() + return md + + +def main(): + md = get_help_md() + print(md) + + +if __name__ == "__main__": + status = main() + sys.exit(status) diff --git a/aider/commands/__init__.py b/cecli/commands/__init__.py similarity index 97% rename from aider/commands/__init__.py rename to cecli/commands/__init__.py index a6e27509556..183a13e1aec 100644 --- a/aider/commands/__init__.py +++ b/cecli/commands/__init__.py @@ -1,5 +1,5 @@ """ -Command system for Aider. +Command system for cecli. This package contains individual command implementations that follow the BaseCommand pattern for modular, testable command execution. @@ -140,10 +140,10 @@ import importlib.util spec = importlib.util.spec_from_file_location( - "aider.commands_module", Path(__file__).parent.parent / "commands.py" + "cecli.commands_module", Path(__file__).parent.parent / "commands.py" ) commands_module = importlib.util.module_from_spec(spec) - sys.modules["aider.commands_module"] = commands_module + sys.modules["cecli.commands_module"] = commands_module spec.loader.exec_module(commands_module) # Get the classes from the module diff --git a/aider/commands/add.py b/cecli/commands/add.py similarity index 95% rename from aider/commands/add.py rename to cecli/commands/add.py index 899fcedf70c..c3e6b620efd 100644 --- a/aider/commands/add.py +++ b/cecli/commands/add.py @@ -3,18 +3,18 @@ from pathlib import Path from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import ( +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import ( format_command_result, parse_quoted_filenames, quote_filename, ) -from aider.utils import is_image_file, run_fzf +from cecli.utils import is_image_file, run_fzf class AddCommand(BaseCommand): NORM_NAME = "add" - DESCRIPTION = "Add files to the chat so aider can edit them or review them in detail" + DESCRIPTION = "Add files to the chat so cecli can edit them or review them in detail" @classmethod async def execute(cls, io, coder, args, **kwargs): @@ -41,7 +41,7 @@ async def execute(cls, io, coder, args, **kwargs): fname = Path(coder.root) / word if coder.repo and coder.repo.ignored_file(fname): - io.tool_warning(f"Skipping {fname} due to aiderignore or --subtree-only.") + io.tool_warning(f"Skipping {fname} due to cecli.ignore or --subtree-only.") continue if fname.exists(): @@ -137,7 +137,7 @@ async def execute(cls, io, coder, args, **kwargs): map_tokens = 0 map_mul_no_files = 1 - from aider.commands import SwitchCoder + from cecli.commands import SwitchCoder raise SwitchCoder( edit_format=coder.edit_format, @@ -217,7 +217,7 @@ def get_help(cls) -> str: help_text += " /add main.py # Add main.py\n" help_text += ' /add "file with spaces.py" # Add file with spaces\n' help_text += ( - "\nThis command adds files to the chat so aider can edit them or review them in" + "\nThis command adds files to the chat so cecli can edit them or review them in" " detail.\n" ) help_text += "If a file doesn't exist, you'll be asked if you want to create it.\n" diff --git a/aider/commands/agent.py b/cecli/commands/agent.py similarity index 94% rename from aider/commands/agent.py rename to cecli/commands/agent.py index f74d7792132..635f0287bb1 100644 --- a/aider/commands/agent.py +++ b/cecli/commands/agent.py @@ -1,6 +1,6 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand +from cecli.commands.utils.base_command import BaseCommand class AgentCommand(BaseCommand): @@ -27,7 +27,7 @@ def get_completions(cls, io, coder, args) -> List[str]: """Get completion options for agent command.""" # The original completions_agent raises CommandCompletionException # This is handled by the completion system - from aider.io import CommandCompletionException + from cecli.io import CommandCompletionException raise CommandCompletionException() diff --git a/aider/commands/architect.py b/cecli/commands/architect.py similarity index 93% rename from aider/commands/architect.py rename to cecli/commands/architect.py index 3d0acc0cac0..6f2d33e44cd 100644 --- a/aider/commands/architect.py +++ b/cecli/commands/architect.py @@ -1,6 +1,6 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand +from cecli.commands.utils.base_command import BaseCommand class ArchitectCommand(BaseCommand): @@ -20,7 +20,7 @@ def get_completions(cls, io, coder, args) -> List[str]: """Get completion options for architect command.""" # The original completions_architect raises CommandCompletionException # This is handled by the completion system - from aider.io import CommandCompletionException + from cecli.io import CommandCompletionException raise CommandCompletionException() diff --git a/aider/commands/ask.py b/cecli/commands/ask.py similarity index 93% rename from aider/commands/ask.py rename to cecli/commands/ask.py index 56bbc0d4088..0bb6cb61850 100644 --- a/aider/commands/ask.py +++ b/cecli/commands/ask.py @@ -1,6 +1,6 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand +from cecli.commands.utils.base_command import BaseCommand class AskCommand(BaseCommand): @@ -20,7 +20,7 @@ def get_completions(cls, io, coder, args) -> List[str]: """Get completion options for ask command.""" # The original completions_ask raises CommandCompletionException # This is handled by the completion system - from aider.io import CommandCompletionException + from cecli.io import CommandCompletionException raise CommandCompletionException() diff --git a/aider/commands/chat_mode.py b/cecli/commands/chat_mode.py similarity index 100% rename from aider/commands/chat_mode.py rename to cecli/commands/chat_mode.py diff --git a/aider/commands/clear.py b/cecli/commands/clear.py similarity index 90% rename from aider/commands/clear.py rename to cecli/commands/clear.py index 7aa8e010a9e..25d921503f0 100644 --- a/aider/commands/clear.py +++ b/cecli/commands/clear.py @@ -1,7 +1,7 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class ClearCommand(BaseCommand): diff --git a/aider/commands/code.py b/cecli/commands/code.py similarity index 94% rename from aider/commands/code.py rename to cecli/commands/code.py index 312cffa6932..5730550a913 100644 --- a/aider/commands/code.py +++ b/cecli/commands/code.py @@ -1,6 +1,6 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand +from cecli.commands.utils.base_command import BaseCommand class CodeCommand(BaseCommand): @@ -23,7 +23,7 @@ def get_completions(cls, io, coder, args) -> List[str]: """Get completion options for code command.""" # The original completions_code raises CommandCompletionException # This is handled by the completion system - from aider.io import CommandCompletionException + from cecli.io import CommandCompletionException raise CommandCompletionException() diff --git a/aider/commands/command_prefix.py b/cecli/commands/command_prefix.py similarity index 93% rename from aider/commands/command_prefix.py rename to cecli/commands/command_prefix.py index 1a32f949dfc..b9db62bb058 100644 --- a/aider/commands/command_prefix.py +++ b/cecli/commands/command_prefix.py @@ -1,7 +1,7 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class CommandPrefixCommand(BaseCommand): diff --git a/aider/commands/commit.py b/cecli/commands/commit.py similarity index 90% rename from aider/commands/commit.py rename to cecli/commands/commit.py index 1668968f072..6f0d1679575 100644 --- a/aider/commands/commit.py +++ b/cecli/commands/commit.py @@ -1,8 +1,8 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result -from aider.repo import ANY_GIT_ERROR +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result +from cecli.repo import ANY_GIT_ERROR class CommitCommand(BaseCommand): @@ -48,5 +48,5 @@ def get_help(cls) -> str: help_text += "\nThis command commits all uncommitted changes in the repository.\n" help_text += "If no commit message is provided, an auto-generated message will be used.\n" help_text += "\nNote: This only commits changes made outside the chat session.\n" - help_text += "Changes made by aider during the chat are automatically committed.\n" + help_text += "Changes made by cecli during the chat are automatically committed.\n" return help_text diff --git a/aider/commands/context.py b/cecli/commands/context.py similarity index 93% rename from aider/commands/context.py rename to cecli/commands/context.py index 08b8fe78491..8ed9331120d 100644 --- a/aider/commands/context.py +++ b/cecli/commands/context.py @@ -1,6 +1,6 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand +from cecli.commands.utils.base_command import BaseCommand class ContextCommand(BaseCommand): @@ -22,7 +22,7 @@ def get_completions(cls, io, coder, args) -> List[str]: """Get completion options for context command.""" # The original completions_context raises CommandCompletionException # This is handled by the completion system - from aider.io import CommandCompletionException + from cecli.io import CommandCompletionException raise CommandCompletionException() diff --git a/aider/commands/context_blocks.py b/cecli/commands/context_blocks.py similarity index 98% rename from aider/commands/context_blocks.py rename to cecli/commands/context_blocks.py index 844726a20de..f7d4d05edd6 100644 --- a/aider/commands/context_blocks.py +++ b/cecli/commands/context_blocks.py @@ -1,7 +1,7 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class ContextBlocksCommand(BaseCommand): diff --git a/aider/commands/context_management.py b/cecli/commands/context_management.py similarity index 94% rename from aider/commands/context_management.py rename to cecli/commands/context_management.py index 6c4eddb85d9..c7efa2ab606 100644 --- a/aider/commands/context_management.py +++ b/cecli/commands/context_management.py @@ -1,7 +1,7 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class ContextManagementCommand(BaseCommand): diff --git a/aider/commands/copy.py b/cecli/commands/copy.py similarity index 95% rename from aider/commands/copy.py rename to cecli/commands/copy.py index 757555b0620..fad7965e100 100644 --- a/aider/commands/copy.py +++ b/cecli/commands/copy.py @@ -2,8 +2,8 @@ import pyperclip -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class CopyCommand(BaseCommand): diff --git a/aider/commands/copy_context.py b/cecli/commands/copy_context.py similarity index 96% rename from aider/commands/copy_context.py rename to cecli/commands/copy_context.py index 8555c0644ab..c490bb54c1d 100644 --- a/aider/commands/copy_context.py +++ b/cecli/commands/copy_context.py @@ -2,8 +2,8 @@ import pyperclip -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class CopyContextCommand(BaseCommand): diff --git a/aider/commands/diff.py b/cecli/commands/diff.py similarity index 94% rename from aider/commands/diff.py rename to cecli/commands/diff.py index bd626f581ef..c5907788b31 100644 --- a/aider/commands/diff.py +++ b/cecli/commands/diff.py @@ -1,8 +1,8 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.repo import ANY_GIT_ERROR -from aider.run_cmd import run_cmd +from cecli.commands.utils.base_command import BaseCommand +from cecli.repo import ANY_GIT_ERROR +from cecli.run_cmd import run_cmd class DiffCommand(BaseCommand): diff --git a/aider/commands/drop.py b/cecli/commands/drop.py similarity index 98% rename from aider/commands/drop.py rename to cecli/commands/drop.py index fdb1142fe67..d438d50998c 100644 --- a/aider/commands/drop.py +++ b/cecli/commands/drop.py @@ -2,8 +2,8 @@ from pathlib import Path from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import ( +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import ( expand_subdir, format_command_result, parse_quoted_filenames, diff --git a/aider/commands/editor.py b/cecli/commands/editor.py similarity index 94% rename from aider/commands/editor.py rename to cecli/commands/editor.py index ae68e0f420f..5994382bf85 100644 --- a/aider/commands/editor.py +++ b/cecli/commands/editor.py @@ -1,8 +1,8 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result -from aider.editor import pipe_editor +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result +from cecli.editor import pipe_editor class EditorCommand(BaseCommand): diff --git a/aider/commands/exit.py b/cecli/commands/exit.py similarity index 86% rename from aider/commands/exit.py rename to cecli/commands/exit.py index 547efd46a9d..73f3e5d4d61 100644 --- a/aider/commands/exit.py +++ b/cecli/commands/exit.py @@ -3,8 +3,8 @@ import sys from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class ExitCommand(BaseCommand): @@ -47,9 +47,9 @@ def get_help(cls) -> str: """Get help text for the exit command.""" help_text = super().get_help() help_text += "\nUsage:\n" - help_text += " /exit # Exit the aider application\n" + help_text += " /exit # Exit the cecli application\n" help_text += " /quit # Alias for /exit\n" - help_text += "\nThis command gracefully exits the aider application.\n" + help_text += "\nThis command gracefully exits the cecli application.\n" help_text += "If running in TUI mode, it will restore the terminal properly.\n" help_text += "Otherwise, it will exit the Python process.\n" return help_text diff --git a/aider/commands/git.py b/cecli/commands/git.py similarity index 94% rename from aider/commands/git.py rename to cecli/commands/git.py index 27605823ec4..e1d96708efd 100644 --- a/aider/commands/git.py +++ b/cecli/commands/git.py @@ -1,8 +1,8 @@ import subprocess from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class GitCommand(BaseCommand): diff --git a/aider/commands/help.py b/cecli/commands/help.py similarity index 88% rename from aider/commands/help.py rename to cecli/commands/help.py index 866e7f28e91..3a943bdd456 100644 --- a/aider/commands/help.py +++ b/cecli/commands/help.py @@ -1,13 +1,13 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result -from aider.commands.utils.registry import CommandRegistry +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result +from cecli.commands.utils.registry import CommandRegistry class HelpCommand(BaseCommand): NORM_NAME = "help" - DESCRIPTION = "Ask questions about aider" + DESCRIPTION = "Ask questions about cecli" @classmethod async def execute(cls, io, coder, args, **kwargs): @@ -16,8 +16,8 @@ async def execute(cls, io, coder, args, **kwargs): await cls._basic_help(io, coder) return format_command_result(io, "help", "Displayed basic help") - from aider.coders.base_coder import Coder - from aider.help import Help, install_help_extra + from cecli.coders.base_coder import Coder + from cecli.help import Help, install_help_extra # Get the Commands instance from kwargs if available commands_instance = kwargs.get("commands_instance") @@ -30,7 +30,7 @@ async def execute(cls, io, coder, args, **kwargs): if not commands_instance: # Create a minimal Commands instance if not provided - from aider.commands import Commands + from cecli.commands import Commands commands_instance = Commands(io, coder) commands_instance.help = Help() @@ -56,7 +56,7 @@ async def execute(cls, io, coder, args, **kwargs): help_coder = await Coder.create(**kwargs) user_msg = help_instance.ask(args) user_msg += """ -# Announcement lines from when this session of aider was launched: +# Announcement lines from when this session of cecli was launched: """ user_msg += "\n".join(coder.get_announcements()) + "\n" @@ -70,7 +70,7 @@ async def execute(cls, io, coder, args, **kwargs): map_tokens = 0 map_mul_no_files = 1 - from aider.commands import SwitchCoder + from cecli.commands import SwitchCoder raise SwitchCoder( edit_format=coder.edit_format, @@ -89,7 +89,7 @@ async def _basic_help(cls, io, coder): # We need to get commands from the Commands class too # Since we don't have a Commands instance, we'll create a minimal one - from aider.commands import Commands + from cecli.commands import Commands commands_instance = Commands(io, coder) all_commands = commands_instance.get_commands() @@ -117,7 +117,7 @@ async def _basic_help(cls, io, coder): io.tool_output(f"{cmd_display} No description available.") io.tool_output() - io.tool_output("Use `/help ` to ask questions about how to use aider.") + io.tool_output("Use `/help ` to ask questions about how to use cecli.") @classmethod def get_completions(cls, io, coder, args) -> List[str]: @@ -130,11 +130,11 @@ def get_help(cls) -> str: help_text = super().get_help() help_text += "\nUsage:\n" help_text += " /help # Show basic help with available commands\n" - help_text += " /help # Ask a question about how to use aider\n" + help_text += " /help # Ask a question about how to use cecli\n" help_text += "\nExamples:\n" help_text += " /help # List all available commands\n" help_text += " /help how to add files # Ask how to add files\n" help_text += " /help undo command # Ask about the undo command\n" - help_text += "\nNote: When asking a question, aider will switch to a special help mode\n" + help_text += "\nNote: When asking a question, cecli will switch to a special help mode\n" help_text += "to answer your question, then switch back to your original mode.\n" return help_text diff --git a/aider/commands/history_search.py b/cecli/commands/history_search.py similarity index 90% rename from aider/commands/history_search.py rename to cecli/commands/history_search.py index a7eb8bc0dbb..9a68e48f2e8 100644 --- a/aider/commands/history_search.py +++ b/cecli/commands/history_search.py @@ -1,8 +1,8 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result -from aider.utils import run_fzf +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result +from cecli.utils import run_fzf class HistorySearchCommand(BaseCommand): diff --git a/aider/commands/lint.py b/cecli/commands/lint.py similarity index 93% rename from aider/commands/lint.py rename to cecli/commands/lint.py index 939bd6b5372..de5206092b6 100644 --- a/aider/commands/lint.py +++ b/cecli/commands/lint.py @@ -1,8 +1,8 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result -from aider.utils import expand_glob_patterns +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result +from cecli.utils import expand_glob_patterns class LintCommand(BaseCommand): @@ -59,7 +59,7 @@ async def execute(cls, io, coder, args, **kwargs): # Commit everything before we start fixing lint errors if coder.repo.is_dirty() and coder.dirty_commits: # Use the commit command from registry - from aider.commands import CommandRegistry + from cecli.commands import CommandRegistry await CommandRegistry.execute("commit", io, coder, "") @@ -77,7 +77,7 @@ async def execute(cls, io, coder, args, **kwargs): if lint_coder and coder.repo.is_dirty() and coder.auto_commits: # Use the commit command from registry - from aider.commands import CommandRegistry + from cecli.commands import CommandRegistry await CommandRegistry.execute("commit", io, coder, "") diff --git a/aider/commands/list_sessions.py b/cecli/commands/list_sessions.py similarity index 87% rename from aider/commands/list_sessions.py rename to cecli/commands/list_sessions.py index 67935c03fff..b896b17e547 100644 --- a/aider/commands/list_sessions.py +++ b/cecli/commands/list_sessions.py @@ -1,17 +1,17 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class ListSessionsCommand(BaseCommand): NORM_NAME = "list-sessions" - DESCRIPTION = "List all saved sessions in .aider/sessions/" + DESCRIPTION = "List all saved sessions in .cecli/sessions/" @classmethod async def execute(cls, io, coder, args, **kwargs): """Execute the list-sessions command with given parameters.""" - from aider import sessions + from cecli import sessions session_manager = sessions.SessionManager(coder, io) sessions_list = session_manager.list_sessions() @@ -44,7 +44,7 @@ def get_help(cls) -> str: help_text += "\nUsage:\n" help_text += " /list-sessions # List all saved sessions\n" help_text += ( - "\nThis command lists all saved chat sessions in the .aider/sessions/ directory.\n" + "\nThis command lists all saved chat sessions in the .cecli/sessions/ directory.\n" ) help_text += ( "Each session shows the name, model, edit format, number of messages, and number of" diff --git a/aider/commands/load.py b/cecli/commands/load.py similarity index 93% rename from aider/commands/load.py rename to cecli/commands/load.py index 266fce9ca38..a32655dd402 100644 --- a/aider/commands/load.py +++ b/cecli/commands/load.py @@ -1,8 +1,8 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result -from aider.commands.utils.save_load_manager import SaveLoadManager +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result +from cecli.commands.utils.save_load_manager import SaveLoadManager class LoadCommand(BaseCommand): @@ -32,7 +32,7 @@ async def execute(cls, io, coder, args, **kwargs): if not commands_instance: # Create a minimal Commands instance if not provided - from aider.commands import Commands + from cecli.commands import Commands commands_instance = Commands(io, coder) diff --git a/aider/commands/load_session.py b/cecli/commands/load_session.py similarity index 87% rename from aider/commands/load_session.py rename to cecli/commands/load_session.py index 53083ef6ca6..1d5676d97e9 100644 --- a/aider/commands/load_session.py +++ b/cecli/commands/load_session.py @@ -1,7 +1,7 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class LoadSessionCommand(BaseCommand): @@ -15,7 +15,7 @@ async def execute(cls, io, coder, args, **kwargs): io.tool_output("Usage: /load-session ") return format_command_result(io, "load-session", "No session name provided") - from aider import sessions + from cecli import sessions session_manager = sessions.SessionManager(coder, io) session_manager.load_session(args.strip()) @@ -26,7 +26,7 @@ async def execute(cls, io, coder, args, **kwargs): def get_completions(cls, io, coder, args) -> List[str]: """Get completion options for load-session command.""" # Return available session names for completion - from aider import sessions + from cecli import sessions session_manager = sessions.SessionManager(coder, io) sessions_list = session_manager.list_sessions() @@ -41,7 +41,7 @@ def get_help(cls) -> str: help_text += "\nExamples:\n" help_text += " /load-session my-feature # Load session 'my-feature'\n" help_text += " /load-session bug-fix # Load session 'bug-fix'\n" - help_text += "\nSessions are loaded from the .aider/sessions/ directory.\n" + help_text += "\nSessions are loaded from the .cecli/sessions/ directory.\n" help_text += ( "Use /list-sessions to see saved sessions and /save-session to save a session.\n" ) diff --git a/aider/commands/load_skill.py b/cecli/commands/load_skill.py similarity index 95% rename from aider/commands/load_skill.py rename to cecli/commands/load_skill.py index 708f8d62d40..8fabc6833b7 100644 --- a/aider/commands/load_skill.py +++ b/cecli/commands/load_skill.py @@ -1,7 +1,7 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class LoadSkillCommand(BaseCommand): diff --git a/aider/commands/ls.py b/cecli/commands/ls.py similarity index 95% rename from aider/commands/ls.py rename to cecli/commands/ls.py index c1283aec9e3..f4a0d797851 100644 --- a/aider/commands/ls.py +++ b/cecli/commands/ls.py @@ -1,7 +1,7 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class LsCommand(BaseCommand): diff --git a/aider/commands/map.py b/cecli/commands/map.py similarity index 90% rename from aider/commands/map.py rename to cecli/commands/map.py index 935b87815f7..25b624f5d20 100644 --- a/aider/commands/map.py +++ b/cecli/commands/map.py @@ -1,7 +1,7 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class MapCommand(BaseCommand): diff --git a/aider/commands/map_refresh.py b/cecli/commands/map_refresh.py similarity index 89% rename from aider/commands/map_refresh.py rename to cecli/commands/map_refresh.py index 9b7f27bf331..53754ce9102 100644 --- a/aider/commands/map_refresh.py +++ b/cecli/commands/map_refresh.py @@ -1,7 +1,7 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class MapRefreshCommand(BaseCommand): @@ -31,5 +31,5 @@ def get_help(cls) -> str: help_text += "\nUsage:\n" help_text += " /map-refresh # Force a refresh of the repository map\n" help_text += "\nThis command forces a refresh of the repository map, which can be useful\n" - help_text += "if files have been added, removed, or modified outside of aider.\n" + help_text += "if files have been added, removed, or modified outside of cecli.\n" return help_text diff --git a/aider/commands/model.py b/cecli/commands/model.py similarity index 92% rename from aider/commands/model.py rename to cecli/commands/model.py index fd2a2d2b068..5bb6edeac34 100644 --- a/aider/commands/model.py +++ b/cecli/commands/model.py @@ -1,8 +1,8 @@ from typing import List -import aider.models as models -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +import cecli.models as models +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class ModelCommand(BaseCommand): @@ -45,7 +45,7 @@ async def execute(cls, io, coder, args, **kwargs): original_edit_format = coder.edit_format # Create a temporary coder with the new model - from aider.coders import Coder + from cecli.coders import Coder kwargs = dict() kwargs["main_model"] = model @@ -70,10 +70,10 @@ async def execute(cls, io, coder, args, **kwargs): await temp_coder.generate(user_message=message, preproc=False) coder.move_back_cur_messages(f"Model {model_name} made those changes to the files.") coder.total_cost = temp_coder.total_cost - coder.aider_commit_hashes = temp_coder.aider_commit_hashes + coder.coder_commit_hashes = temp_coder.coder_commit_hashes # Restore the original model configuration - from aider.commands import SwitchCoder + from cecli.commands import SwitchCoder raise SwitchCoder(main_model=original_main_model, edit_format=original_edit_format) except Exception as e: @@ -87,7 +87,7 @@ async def execute(cls, io, coder, args, **kwargs): # Re-raise SwitchCoder if that's what was thrown raise else: - from aider.commands import SwitchCoder + from cecli.commands import SwitchCoder raise SwitchCoder(main_model=model, edit_format=new_edit_format) diff --git a/aider/commands/models.py b/cecli/commands/models.py similarity index 90% rename from aider/commands/models.py rename to cecli/commands/models.py index 2af2a56771d..8892dbb7300 100644 --- a/aider/commands/models.py +++ b/cecli/commands/models.py @@ -1,8 +1,8 @@ from typing import List -import aider.models as models -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +import cecli.models as models +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class ModelsCommand(BaseCommand): diff --git a/aider/commands/multiline_mode.py b/cecli/commands/multiline_mode.py similarity index 92% rename from aider/commands/multiline_mode.py rename to cecli/commands/multiline_mode.py index 80c971fe5b4..46ba4986d67 100644 --- a/aider/commands/multiline_mode.py +++ b/cecli/commands/multiline_mode.py @@ -1,7 +1,7 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class MultilineModeCommand(BaseCommand): diff --git a/aider/commands/paste.py b/cecli/commands/paste.py similarity index 96% rename from aider/commands/paste.py rename to cecli/commands/paste.py index d81e85a89a3..181f292d2d3 100644 --- a/aider/commands/paste.py +++ b/cecli/commands/paste.py @@ -6,8 +6,8 @@ import pyperclip from PIL import Image, ImageGrab -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class PasteCommand(BaseCommand): diff --git a/aider/commands/quit.py b/cecli/commands/quit.py similarity index 80% rename from aider/commands/quit.py rename to cecli/commands/quit.py index e0207b38c21..28a204ed2f6 100644 --- a/aider/commands/quit.py +++ b/cecli/commands/quit.py @@ -1,7 +1,7 @@ from typing import List -from aider.commands.exit import ExitCommand -from aider.commands.utils.base_command import BaseCommand +from cecli.commands.exit import ExitCommand +from cecli.commands.utils.base_command import BaseCommand class QuitCommand(BaseCommand): @@ -24,9 +24,9 @@ def get_help(cls) -> str: """Get help text for the quit command.""" help_text = super().get_help() help_text += "\nUsage:\n" - help_text += " /quit # Exit the aider application\n" + help_text += " /quit # Exit the cecli application\n" help_text += " /exit # Alias for /quit\n" - help_text += "\nThis command gracefully exits the aider application.\n" + help_text += "\nThis command gracefully exits the cecli application.\n" help_text += "If running in TUI mode, it will restore the terminal properly.\n" help_text += "Otherwise, it will exit the Python process.\n" return help_text diff --git a/aider/commands/read_only.py b/cecli/commands/read_only.py similarity index 98% rename from aider/commands/read_only.py rename to cecli/commands/read_only.py index 848f368da4b..9ec985e725c 100644 --- a/aider/commands/read_only.py +++ b/cecli/commands/read_only.py @@ -4,13 +4,13 @@ from pathlib import Path from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import ( +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import ( format_command_result, parse_quoted_filenames, quote_filename, ) -from aider.utils import is_image_file, run_fzf +from cecli.utils import is_image_file, run_fzf class ReadOnlyCommand(BaseCommand): diff --git a/aider/commands/read_only_stub.py b/cecli/commands/read_only_stub.py similarity index 98% rename from aider/commands/read_only_stub.py rename to cecli/commands/read_only_stub.py index cb98b592123..d7ec727f096 100644 --- a/aider/commands/read_only_stub.py +++ b/cecli/commands/read_only_stub.py @@ -4,13 +4,13 @@ from pathlib import Path from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import ( +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import ( format_command_result, parse_quoted_filenames, quote_filename, ) -from aider.utils import is_image_file, run_fzf +from cecli.utils import is_image_file, run_fzf class ReadOnlyStubCommand(BaseCommand): diff --git a/aider/commands/reasoning_effort.py b/cecli/commands/reasoning_effort.py similarity index 95% rename from aider/commands/reasoning_effort.py rename to cecli/commands/reasoning_effort.py index 8696a5bb583..b04a8d59b25 100644 --- a/aider/commands/reasoning_effort.py +++ b/cecli/commands/reasoning_effort.py @@ -1,7 +1,7 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class ReasoningEffortCommand(BaseCommand): diff --git a/aider/commands/remove_skill.py b/cecli/commands/remove_skill.py similarity index 95% rename from aider/commands/remove_skill.py rename to cecli/commands/remove_skill.py index 57d394e6a01..0fe7a992317 100644 --- a/aider/commands/remove_skill.py +++ b/cecli/commands/remove_skill.py @@ -1,7 +1,7 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class RemoveSkillCommand(BaseCommand): diff --git a/aider/commands/report.py b/cecli/commands/report.py similarity index 88% rename from aider/commands/report.py rename to cecli/commands/report.py index a618111803b..21bcfb30bda 100644 --- a/aider/commands/report.py +++ b/cecli/commands/report.py @@ -1,7 +1,7 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class ReportCommand(BaseCommand): @@ -10,7 +10,7 @@ class ReportCommand(BaseCommand): @classmethod async def execute(cls, io, coder, args, **kwargs): - from aider.report import report_github_issue + from cecli.report import report_github_issue announcements = "\n".join(coder.get_announcements()) issue_text = announcements diff --git a/aider/commands/reset.py b/cecli/commands/reset.py similarity index 96% rename from aider/commands/reset.py rename to cecli/commands/reset.py index 87bf923e8fe..608123345d6 100644 --- a/aider/commands/reset.py +++ b/cecli/commands/reset.py @@ -1,7 +1,7 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class ResetCommand(BaseCommand): diff --git a/aider/commands/run.py b/cecli/commands/run.py similarity index 94% rename from aider/commands/run.py rename to cecli/commands/run.py index 4c66123894b..37b124d7fdd 100644 --- a/aider/commands/run.py +++ b/cecli/commands/run.py @@ -1,10 +1,10 @@ import asyncio from typing import List -import aider.prompts.utils.system as prompts -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result -from aider.run_cmd import run_cmd +import cecli.prompts.utils.system as prompts +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result +from cecli.run_cmd import run_cmd class RunCommand(BaseCommand): diff --git a/aider/commands/save.py b/cecli/commands/save.py similarity index 85% rename from aider/commands/save.py rename to cecli/commands/save.py index be04542d16a..b63c0ca9511 100644 --- a/aider/commands/save.py +++ b/cecli/commands/save.py @@ -1,8 +1,8 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result -from aider.commands.utils.save_load_manager import SaveLoadManager +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result +from cecli.commands.utils.save_load_manager import SaveLoadManager class SaveCommand(BaseCommand): @@ -39,8 +39,8 @@ def get_help(cls) -> str: help_text += "\nUsage:\n" help_text += " /save # Save commands to reconstruct current chat session\n" help_text += "\nExamples:\n" - help_text += " /save session # Save to .aider/saves/session.txt\n" - help_text += " /save session.txt # Save to .aider/saves/session.txt\n" + help_text += " /save session # Save to .cecli/saves/session.txt\n" + help_text += " /save session.txt # Save to .cecli/saves/session.txt\n" help_text += " /save ./session.txt # Save to ./session.txt (explicit path)\n" help_text += " /save /tmp/session.txt # Save to /tmp/session.txt (absolute path)\n" help_text += "\nThe saved file contains commands that can be used with /load to restore\n" diff --git a/aider/commands/save_session.py b/cecli/commands/save_session.py similarity index 86% rename from aider/commands/save_session.py rename to cecli/commands/save_session.py index 46fd63c6118..799ab817d12 100644 --- a/aider/commands/save_session.py +++ b/cecli/commands/save_session.py @@ -1,12 +1,12 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class SaveSessionCommand(BaseCommand): NORM_NAME = "save-session" - DESCRIPTION = "Save the current chat session to a named file in .aider/sessions/" + DESCRIPTION = "Save the current chat session to a named file in .cecli/sessions/" @classmethod async def execute(cls, io, coder, args, **kwargs): @@ -15,7 +15,7 @@ async def execute(cls, io, coder, args, **kwargs): io.tool_error("Please provide a session name to save.") return format_command_result(io, "save-session", "No session name provided") - from aider import sessions + from cecli import sessions session_manager = sessions.SessionManager(coder, io) session_manager.save_session(args.strip()) @@ -38,6 +38,6 @@ def get_help(cls) -> str: help_text += "\nExamples:\n" help_text += " /save-session my-feature # Save session as 'my-feature'\n" help_text += " /save-session bug-fix # Save session as 'bug-fix'\n" - help_text += "\nSessions are saved in the .aider/sessions/ directory as JSON files.\n" + help_text += "\nSessions are saved in the .cecli/sessions/ directory as JSON files.\n" help_text += "Use /list-sessions to see saved sessions and /load-session to load them.\n" return help_text diff --git a/aider/commands/settings.py b/cecli/commands/settings.py similarity index 93% rename from aider/commands/settings.py rename to cecli/commands/settings.py index ace5230b528..2dd7f6010ab 100644 --- a/aider/commands/settings.py +++ b/cecli/commands/settings.py @@ -1,8 +1,8 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result -from aider.format_settings import format_settings +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result +from cecli.format_settings import format_settings class SettingsCommand(BaseCommand): diff --git a/aider/commands/test.py b/cecli/commands/test.py similarity index 90% rename from aider/commands/test.py rename to cecli/commands/test.py index 74c14d03bfe..5462d4785db 100644 --- a/aider/commands/test.py +++ b/cecli/commands/test.py @@ -1,7 +1,7 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class TestCommand(BaseCommand): @@ -21,7 +21,7 @@ async def execute(cls, io, coder, args, **kwargs): if type(args) is not str: raise ValueError(repr(args)) # Use the run command with add_on_nonzero_exit=True - from aider.commands import CommandRegistry + from cecli.commands import CommandRegistry return await CommandRegistry.execute("run", io, coder, args, add_on_nonzero_exit=True) @@ -53,6 +53,6 @@ def get_help(cls) -> str: help_text += "If the test passes (exit code 0), the output is not added to the chat.\n" help_text += ( "\nYou can set a default test command using the --test-cmd option when starting" - " aider.\n" + " cecli.\n" ) return help_text diff --git a/aider/commands/think_tokens.py b/cecli/commands/think_tokens.py similarity index 96% rename from aider/commands/think_tokens.py rename to cecli/commands/think_tokens.py index 036ba43967b..71b77e31166 100644 --- a/aider/commands/think_tokens.py +++ b/cecli/commands/think_tokens.py @@ -1,7 +1,7 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class ThinkTokensCommand(BaseCommand): diff --git a/aider/commands/tokens.py b/cecli/commands/tokens.py similarity index 98% rename from aider/commands/tokens.py rename to cecli/commands/tokens.py index 1ce6e3f1bd1..a2e42a91fe6 100644 --- a/aider/commands/tokens.py +++ b/cecli/commands/tokens.py @@ -1,8 +1,8 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result -from aider.utils import is_image_file +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result +from cecli.utils import is_image_file class TokensCommand(BaseCommand): diff --git a/aider/commands/undo.py b/cecli/commands/undo.py similarity index 90% rename from aider/commands/undo.py rename to cecli/commands/undo.py index 13993b13bd8..3c6f3781edd 100644 --- a/aider/commands/undo.py +++ b/cecli/commands/undo.py @@ -1,14 +1,14 @@ from typing import List -import aider.prompts.utils.system as prompts -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result -from aider.repo import ANY_GIT_ERROR +import cecli.prompts.utils.system as prompts +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result +from cecli.repo import ANY_GIT_ERROR class UndoCommand(BaseCommand): NORM_NAME = "undo" - DESCRIPTION = "Undo the last git commit if it was done by aider" + DESCRIPTION = "Undo the last git commit if it was done by cecli" @classmethod async def execute(cls, io, coder, args, **kwargs): @@ -32,13 +32,13 @@ async def _raw_cmd_undo(cls, io, coder, args): last_commit_hash = coder.repo.get_head_commit_sha(short=True) last_commit_message = coder.repo.get_head_commit_message("(unknown)").strip() last_commit_message = (last_commit_message.splitlines() or [""])[0] - if last_commit_hash not in coder.aider_commit_hashes: - io.tool_error("The last commit was not made by aider in this chat session.") + if last_commit_hash not in coder.coder_commit_hashes: + io.tool_error("The last commit was not made by cecli in this chat session.") io.tool_output( "You could try `/git reset --hard HEAD^` but be aware that this is a destructive" " command!" ) - return format_command_result(io, "undo", "Last commit not made by aider") + return format_command_result(io, "undo", "Last commit not made by cecli") if len(last_commit.parents) > 1: io.tool_error( @@ -116,7 +116,7 @@ async def _raw_cmd_undo(cls, io, coder, args): if coder.main_model.send_undo_reply: return prompts.undo_command_reply - return format_command_result(io, "undo", "Successfully undone last aider commit") + return format_command_result(io, "undo", "Successfully undone last cecli commit") @classmethod def get_completions(cls, io, coder, args) -> List[str]: @@ -128,13 +128,13 @@ def get_help(cls) -> str: """Get help text for the undo command.""" help_text = super().get_help() help_text += "\nUsage:\n" - help_text += " /undo # Undo the last git commit if it was made by aider\n" + help_text += " /undo # Undo the last git commit if it was made by cecli\n" help_text += ( - "\nThis command undoes the last git commit if it was made by aider in the current chat" + "\nThis command undoes the last git commit if it was made by cecli in the current chat" " session.\n" ) help_text += "It checks various safety conditions before performing the undo:\n" - help_text += " - The commit must have been made by aider in this session\n" + help_text += " - The commit must have been made by cecli in this session\n" help_text += " - The commit must not have multiple parents (merge commit)\n" help_text += " - Files must not have uncommitted changes\n" help_text += " - Files must exist in the previous commit\n" diff --git a/aider/commands/utils/__init__.py b/cecli/commands/utils/__init__.py similarity index 100% rename from aider/commands/utils/__init__.py rename to cecli/commands/utils/__init__.py diff --git a/aider/commands/utils/base_command.py b/cecli/commands/utils/base_command.py similarity index 93% rename from aider/commands/utils/base_command.py rename to cecli/commands/utils/base_command.py index 6603ea11cfc..b36ea5cd59a 100644 --- a/aider/commands/utils/base_command.py +++ b/cecli/commands/utils/base_command.py @@ -96,11 +96,11 @@ async def _generic_chat_command(cls, io, coder, args, edit_format, placeholder=N """ if not args.strip(): # Switch to the corresponding chat mode - from aider.commands import SwitchCoder + from cecli.commands import SwitchCoder raise SwitchCoder(edit_format=edit_format) - from aider.coders.base_coder import Coder + from cecli.coders.base_coder import Coder user_msg = args @@ -112,16 +112,16 @@ async def _generic_chat_command(cls, io, coder, args, edit_format, placeholder=N "edit_format": edit_format, "summarize_from_coder": False, "num_cache_warming_pings": 0, - "aider_commit_hashes": coder.aider_commit_hashes, + "coder_commit_hashes": coder.coder_commit_hashes, "args": coder.args, } new_coder = await Coder.create(**kwargs) await new_coder.generate(user_message=user_msg, preproc=False) - coder.aider_commit_hashes = new_coder.aider_commit_hashes + coder.coder_commit_hashes = new_coder.coder_commit_hashes - from aider.commands import SwitchCoder + from cecli.commands import SwitchCoder raise SwitchCoder( main_model=original_main_model, diff --git a/aider/commands/utils/helpers.py b/cecli/commands/utils/helpers.py similarity index 100% rename from aider/commands/utils/helpers.py rename to cecli/commands/utils/helpers.py diff --git a/aider/commands/utils/registry.py b/cecli/commands/utils/registry.py similarity index 100% rename from aider/commands/utils/registry.py rename to cecli/commands/utils/registry.py diff --git a/aider/commands/utils/save_load_manager.py b/cecli/commands/utils/save_load_manager.py similarity index 97% rename from aider/commands/utils/save_load_manager.py rename to cecli/commands/utils/save_load_manager.py index f7ea7a4720b..82012f707a4 100644 --- a/aider/commands/utils/save_load_manager.py +++ b/cecli/commands/utils/save_load_manager.py @@ -12,7 +12,7 @@ def __init__(self, coder, io): def get_saves_directory(self) -> Path: """Get the saves directory, creating it if necessary.""" - saves_dir = Path(self.coder.abs_root_path(".aider/saves")) + saves_dir = Path(self.coder.abs_root_path(".cecli/saves")) os.makedirs(saves_dir, exist_ok=True) return saves_dir @@ -20,7 +20,7 @@ def resolve_filepath(self, filename: str) -> Path: """Resolve a filename to an absolute path, using saves directory if needed.""" filepath = Path(filename) - # If it's a simple filename (no directory separators), save to .aider/saves/ + # If it's a simple filename (no directory separators), save to .cecli/saves/ if not filepath.is_absolute() and str(filepath) == filepath.name: saves_dir = self.get_saves_directory() filepath = saves_dir / filepath diff --git a/aider/commands/voice.py b/cecli/commands/voice.py similarity index 94% rename from aider/commands/voice.py rename to cecli/commands/voice.py index 271e7f645e5..6b40fddb538 100644 --- a/aider/commands/voice.py +++ b/cecli/commands/voice.py @@ -1,10 +1,10 @@ import os from typing import List -import aider.voice as voice -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result -from aider.llm import litellm +import cecli.voice as voice +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result +from cecli.llm import litellm class VoiceCommand(BaseCommand): diff --git a/aider/commands/weak_model.py b/cecli/commands/weak_model.py similarity index 92% rename from aider/commands/weak_model.py rename to cecli/commands/weak_model.py index cb7b5d37ef5..be1a1ead4aa 100644 --- a/aider/commands/weak_model.py +++ b/cecli/commands/weak_model.py @@ -1,8 +1,8 @@ from typing import List -import aider.models as models -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result +import cecli.models as models +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result class WeakModelCommand(BaseCommand): @@ -40,7 +40,7 @@ async def execute(cls, io, coder, args, **kwargs): original_edit_format = coder.edit_format # Create a temporary coder with the new model - from aider.coders import Coder + from cecli.coders import Coder kwargs = dict() kwargs["main_model"] = model @@ -67,10 +67,10 @@ async def execute(cls, io, coder, args, **kwargs): f"Weak model {model_name} made those changes to the files." ) coder.total_cost = temp_coder.total_cost - coder.aider_commit_hashes = temp_coder.aider_commit_hashes + coder.coder_commit_hashes = temp_coder.coder_commit_hashes # Restore the original model configuration - from aider.commands import SwitchCoder + from cecli.commands import SwitchCoder raise SwitchCoder(main_model=original_main_model, edit_format=original_edit_format) except Exception as e: @@ -84,7 +84,7 @@ async def execute(cls, io, coder, args, **kwargs): # Re-raise SwitchCoder if that's what was thrown raise else: - from aider.commands import SwitchCoder + from cecli.commands import SwitchCoder raise SwitchCoder(main_model=model, edit_format=coder.edit_format) diff --git a/aider/commands/web.py b/cecli/commands/web.py similarity index 91% rename from aider/commands/web.py rename to cecli/commands/web.py index 3acf9438e65..9b498aa7b19 100644 --- a/aider/commands/web.py +++ b/cecli/commands/web.py @@ -1,8 +1,8 @@ from typing import List -from aider.commands.utils.base_command import BaseCommand -from aider.commands.utils.helpers import format_command_result -from aider.scrape import Scraper, install_playwright +from cecli.commands.utils.base_command import BaseCommand +from cecli.commands.utils.helpers import format_command_result +from cecli.scrape import Scraper, install_playwright class WebCommand(BaseCommand): @@ -78,7 +78,7 @@ def get_help(cls) -> str: help_text += " /web # Scrape a webpage and add its content to the chat\n" help_text += "\nExamples:\n" help_text += " /web https://example.com # Scrape example.com\n" - help_text += " /web https://github.com/aider-chat/aider # Scrape aider GitHub page\n" + help_text += " /web https://github.com/dwash96/aider-ce # Scrape cecli GitHub page\n" help_text += ( "\nThis command scrapes a webpage, converts it to markdown, and adds it to the chat.\n" ) diff --git a/aider/deprecated_args.py b/cecli/deprecated_args.py similarity index 98% rename from aider/deprecated_args.py rename to cecli/deprecated_args.py index d8c3656a864..e5b3986f31f 100644 --- a/aider/deprecated_args.py +++ b/cecli/deprecated_args.py @@ -164,7 +164,7 @@ def handle_deprecated_model_args(args, io): arg_name_clean = arg_name.replace("-", "_") if hasattr(args, arg_name_clean) and getattr(args, arg_name_clean): # Find preferred name to display in warning - from aider.models import MODEL_ALIASES + from cecli.models import MODEL_ALIASES display_name = model_name # Check if there's a shorter alias for this model diff --git a/aider/diffs.py b/cecli/diffs.py similarity index 100% rename from aider/diffs.py rename to cecli/diffs.py diff --git a/aider/dump.py b/cecli/dump.py similarity index 100% rename from aider/dump.py rename to cecli/dump.py diff --git a/aider/editor.py b/cecli/editor.py similarity index 99% rename from aider/editor.py rename to cecli/editor.py index a7cf741349b..711254099d4 100644 --- a/aider/editor.py +++ b/cecli/editor.py @@ -15,7 +15,7 @@ from rich.console import Console -from aider.dump import dump # noqa +from cecli.dump import dump # noqa DEFAULT_EDITOR_NIX = "vi" DEFAULT_EDITOR_OS_X = "vim" diff --git a/aider/exceptions.py b/cecli/exceptions.py similarity index 98% rename from aider/exceptions.py rename to cecli/exceptions.py index a151f504150..0bedc5327e4 100644 --- a/aider/exceptions.py +++ b/cecli/exceptions.py @@ -1,6 +1,6 @@ from dataclasses import dataclass -from aider.dump import dump # noqa: F401 +from cecli.dump import dump # noqa: F401 @dataclass @@ -66,7 +66,7 @@ def _load(self, strict=False): for var in dir(litellm): if var.endswith("Error"): if var not in self.exception_info: - raise ValueError(f"{var} is in litellm but not in aider's exceptions list") + raise ValueError(f"{var} is in litellm but not in cecli's exceptions list") for var in self.exception_info: ex = getattr(litellm, var, "default") diff --git a/aider/format_settings.py b/cecli/format_settings.py similarity index 100% rename from aider/format_settings.py rename to cecli/format_settings.py diff --git a/aider/help.py b/cecli/help.py similarity index 70% rename from aider/help.py rename to cecli/help.py index 3da5deec300..50466d07d4a 100755 --- a/aider/help.py +++ b/cecli/help.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - import json import os import shutil @@ -8,19 +6,16 @@ import importlib_resources -from aider import __version__, utils -from aider.dump import dump # noqa: F401 -from aider.help_pats import exclude_website_pats +from cecli import __version__, utils +from cecli.dump import dump # noqa +from cecli.help_pats import exclude_website_pats +from cecli.helpers.file_searcher import handle_core_files warnings.simplefilter("ignore", category=FutureWarning) async def install_help_extra(io): - pip_install_cmd = [ - "aider-ce[help]", - "--extra-index-url", - "https://download.pytorch.org/whl/cpu", - ] + pip_install_cmd = ["cecli[help]", "--extra-index-url", "https://download.pytorch.org/whl/cpu"] res = await utils.check_pip_install_extra( io, "llama_index.embeddings.huggingface", @@ -31,7 +26,7 @@ async def install_help_extra(io): def get_package_files(): - for path in importlib_resources.files("aider.website").iterdir(): + for path in importlib_resources.files("cecli.website").iterdir(): if path.is_file(): yield path elif path.is_dir(): @@ -43,42 +38,23 @@ def fname_to_url(filepath): website = "website" index = "index.md" md = ".md" - - # Convert backslashes to forward slashes for consistency filepath = filepath.replace("\\", "/") - - # Convert to Path object for easier manipulation path = Path(filepath) - - # Split the path into parts parts = path.parts - - # Find the 'website' part in the path try: website_index = [p.lower() for p in parts].index(website.lower()) except ValueError: - return "" # 'website' not found in the path - - # Extract the part of the path starting from 'website' + return "" relevant_parts = parts[website_index + 1 :] - - # Handle _includes directory if relevant_parts and relevant_parts[0].lower() == "_includes": return "" - - # Join the remaining parts url_path = "/".join(relevant_parts) - - # Handle index.md and other .md files if url_path.lower().endswith(index.lower()): url_path = url_path[: -len(index)] elif url_path.lower().endswith(md.lower()): url_path = url_path[: -len(md)] + ".html" - - # Ensure the URL starts and ends with '/' url_path = url_path.strip("/") - - return f"https://aider.chat/{url_path}" + return f"https://cecli.dev/{url_path}" def get_index(): @@ -90,43 +66,33 @@ def get_index(): ) from llama_index.core.node_parser import MarkdownNodeParser - dname = Path.home() / ".aider" / "caches" / ("help." + __version__) - + dname = handle_core_files(Path.home() / ".cecli" / "caches" / ("help." + __version__)) index = None try: if dname.exists(): - storage_context = StorageContext.from_defaults( - persist_dir=dname, - ) + storage_context = StorageContext.from_defaults(persist_dir=dname) index = load_index_from_storage(storage_context) except (OSError, json.JSONDecodeError): shutil.rmtree(dname) - if index is None: parser = MarkdownNodeParser() - nodes = [] for fname in get_package_files(): fname = Path(fname) if any(fname.match(pat) for pat in exclude_website_pats): continue - doc = Document( - text=importlib_resources.files("aider.website") + text=importlib_resources.files("cecli.website") .joinpath(fname) .read_text(encoding="utf-8"), metadata=dict( - filename=fname.name, - extension=fname.suffix, - url=fname_to_url(str(fname)), + filename=fname.name, extension=fname.suffix, url=fname_to_url(str(fname)) ), ) nodes += parser.get_nodes_from_documents([doc]) - index = VectorStoreIndex(nodes, show_progress=True) dname.parent.mkdir(parents=True, exist_ok=True) index.storage_context.persist(dname) - return index @@ -137,27 +103,17 @@ def __init__(self): os.environ["TOKENIZERS_PARALLELISM"] = "true" Settings.embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-small-en-v1.5") - index = get_index() - self.retriever = index.as_retriever(similarity_top_k=20) def ask(self, question): nodes = self.retriever.retrieve(question) - - context = f"""# Question: {question} - -# Relevant docs: - -""" # noqa: E231 - + context = f"# Question: {question}\n\n# Relevant docs:\n\n" for node in nodes: url = node.metadata.get("url", "") if url: url = f' from_url="{url}"' - context += f"\n" context += node.text context += "\n\n\n" - return context diff --git a/aider/help_pats.py b/cecli/help_pats.py similarity index 100% rename from aider/help_pats.py rename to cecli/help_pats.py diff --git a/aider/helpers/__init__.py b/cecli/helpers/__init__.py similarity index 83% rename from aider/helpers/__init__.py rename to cecli/helpers/__init__.py index abcf8f9c2c7..3c5f71af04f 100644 --- a/aider/helpers/__init__.py +++ b/cecli/helpers/__init__.py @@ -1,4 +1,4 @@ -"""Utility functions for aider.""" +"""Utility functions for cecli.""" from .similarity import cosine_similarity, create_bigram_vector, normalize_vector diff --git a/aider/helpers/copypaste.py b/cecli/helpers/copypaste.py similarity index 96% rename from aider/helpers/copypaste.py rename to cecli/helpers/copypaste.py index 6f241f313ec..9de8567ae20 100644 --- a/aider/helpers/copypaste.py +++ b/cecli/helpers/copypaste.py @@ -73,13 +73,13 @@ def watch_clipboard(): break except ClipboardError as err: if self.verbose: - from aider.dump import dump + from cecli.dump import dump dump(f"Clipboard watcher error: {err}") continue except Exception as err: # pragma: no cover - unexpected errors if self.verbose: - from aider.dump import dump + from cecli.dump import dump dump(f"Clipboard watcher unexpected error: {err}") continue @@ -105,7 +105,7 @@ def stop(self): def main(): """Example usage of the clipboard watcher.""" - from aider.io import InputOutput + from cecli.io import InputOutput io = InputOutput() watcher = ClipboardWatcher(io, verbose=True) diff --git a/aider/helpers/coroutines.py b/cecli/helpers/coroutines.py similarity index 100% rename from aider/helpers/coroutines.py rename to cecli/helpers/coroutines.py diff --git a/cecli/helpers/file_searcher.py b/cecli/helpers/file_searcher.py new file mode 100644 index 00000000000..2c2e1945f79 --- /dev/null +++ b/cecli/helpers/file_searcher.py @@ -0,0 +1,142 @@ +""" +File search utilities for aider. + +This module provides functions for searching and resolving file paths +relative to various directories (git root, home folder, .aider, .cecli, etc.). +""" + +from pathlib import Path +from typing import List, Optional + + +def generate_search_path_list( + default_file: str, git_root: Optional[str], command_line_file: Optional[str] +) -> List[str]: + """ + Generate a list of file paths to search for configuration files. + + The search order is: + 1. Home directory (~/default_file) + 2. Git root directory (git_root/default_file) if git_root is provided + 3. Current directory (default_file) + 4. Command line specified file (command_line_file) if provided + + Args: + default_file: The default filename to search for + git_root: The git root directory (optional) + command_line_file: A file specified on the command line (optional) + + Returns: + List of resolved file paths in search order (first to last) + """ + files = [] + files.append(Path.home() / default_file) # homedir + if git_root: + files.append(Path(git_root) / default_file) # git root + files.append(default_file) + if command_line_file: + files.append(command_line_file) + + resolved_files = [] + for fn in files: + try: + resolved_files.append(Path(fn).expanduser().resolve()) + except OSError: + pass + + files = resolved_files + files.reverse() + uniq = [] + for fn in files: + if fn not in uniq: + uniq.append(fn) + uniq.reverse() + files = uniq + files = list(map(str, files)) + files = list(dict.fromkeys(files)) + + return files + + +def handle_core_files( + file_path: str, + prepend_folder: Optional[str] = None, + namespace_folder: bool = False, +) -> str: + """ + Handle core configuration files, migrating from .aider to .cecli if needed. + + This function receives paths with .cecli (new naming) and: + 1. Checks if corresponding .aider versions exist + 2. If .aider exists, copies it to the .cecli version (preserving original as backup) + 3. Returns the .cecli path (whether copied or original) + + Handles both: + 1. Files that start with '.cecli' (e.g., '.cecli-config.yml') + 2. Folders named '.cecli' in the path (e.g., '.cecli/config.yml') + + Args: + file_path: The target file path with .cecli naming + prepend_folder: Optional folder to prepend to the path (e.g., 'configs/') + namespace_folder: If True, prepend '.cecli/' to the path (default: False) + + Returns: + The processed .cecli file path with any modifications applied + + Example: + >>> handle_core_files(".cecli/config.yml", "configs", True) + "configs/.cecli/config.yml" + # If .aider/config.yml exists, it will be copied to configs/.cecli/config.yml + + >>> handle_core_files(".cecli-settings.json") + ".cecli-settings.json" + # If .aider-settings.json exists, it will be copied to .cecli-settings.json + + >>> handle_core_files("project/.cecli/config.yml") + "project/.cecli/config.yml" + # If project/.aider/config.yml exists, it will be copied to project/.cecli/config.yml + """ + import shutil + from pathlib import Path, PurePath + + is_path_obj = isinstance(file_path, PurePath) + + # Convert to Path object for easier manipulation + path = Path(file_path) + + # First apply prepend_folder and namespace_folder to get the target .cecli path + if prepend_folder: + path = Path(prepend_folder) / path + + if namespace_folder: + path = Path(".cecli") / path + + # Now check if this .cecli path has a corresponding .aider version that exists + path_str = str(path) + if ".cecli" in path_str: + # Create the corresponding .aider path by replacing .cecli with .aider + # Handle both folder names (.cecli/) and file names (.cecli) + aider_path_str = path_str.replace(".cecli/", ".aider/").replace(".cecli", ".aider", 1) + aider_path = Path(aider_path_str) + + # Check if the .aider file/folder exists (and .cecli doesn't already exist) + if aider_path.exists() and not path.exists(): + # Create parent directories for the .cecli path if needed + path.parent.mkdir(parents=True, exist_ok=True) + + # Copy the file/folder from .aider to .cecli (preserve original as backup) + try: + if aider_path.is_file(): + # Copy file with metadata preservation + shutil.copy2(str(aider_path), str(path)) + elif aider_path.is_dir(): + # Copy directory tree + shutil.copytree(str(aider_path), str(path), dirs_exist_ok=True) + except (OSError, shutil.Error) as e: + # If copy fails, log but continue with .cecli path + import logging + + logging.debug(f"Failed to copy {aider_path} to {path}: {e}") + + # Return the .cecli path as Path or string + return path if is_path_obj else str(path) diff --git a/aider/helpers/model_providers.py b/cecli/helpers/model_providers.py similarity index 91% rename from aider/helpers/model_providers.py rename to cecli/helpers/model_providers.py index a78aa5d75fc..4092cac188a 100644 --- a/aider/helpers/model_providers.py +++ b/cecli/helpers/model_providers.py @@ -1,6 +1,6 @@ """Unified model provider metadata caching and lookup. -Historically aider kept separate modules per provider (OpenRouter vs OpenAI-like). +Historically cecli kept separate modules per provider (OpenRouter vs OpenAI-like). Those grew unwieldy and duplicated caching, request, and normalization logic. This helper centralizes that behavior so every OpenAI-compatible endpoint defines a small config blob and inherits the same cache + LiteLLM registration plumbing. @@ -21,16 +21,17 @@ import requests -try: # Optional imports; litellm might not be installed during docs builds +from cecli.helpers.file_searcher import handle_core_files + +try: from litellm.llms.custom_httpx.http_handler import HTTPHandler from litellm.llms.custom_llm import CustomLLM, CustomLLMError from litellm.llms.openai_like.chat.handler import OpenAILikeChatHandler -except Exception: # pragma: no cover - only during partial installs - CustomLLM = None # type: ignore - CustomLLMError = Exception # type: ignore - OpenAILikeChatHandler = None # type: ignore - HTTPHandler = None # type: ignore - +except Exception: + CustomLLM = None + CustomLLMError = Exception + OpenAILikeChatHandler = None + HTTPHandler = None RESOURCE_FILE = "providers.json" _PROVIDERS_REGISTERED = False _CUSTOM_HANDLERS: Dict[str, "_JSONOpenAIProvider"] = {} @@ -60,13 +61,13 @@ def _first_env_value(names): return None -class _JSONOpenAIProvider(OpenAILikeChatHandler): # type: ignore[misc] +class _JSONOpenAIProvider(OpenAILikeChatHandler): """CustomLLM wrapper that routes OpenAI-compatible providers through LiteLLM.""" def __init__(self, slug: str, config: Dict): - if CustomLLM is None or OpenAILikeChatHandler is None: # pragma: no cover + if CustomLLM is None or OpenAILikeChatHandler is None: raise RuntimeError("litellm custom handler support unavailable") - super().__init__() # type: ignore[misc] + super().__init__() self.slug = slug self.config = config @@ -77,7 +78,7 @@ def _resolve_api_base(self, api_base: Optional[str]) -> str: or self.config.get("api_base") ) if not base: - raise CustomLLMError(500, f"{self.slug} missing base URL") # type: ignore[misc] + raise CustomLLMError(500, f"{self.slug} missing base URL") return base.rstrip("/") def _resolve_api_key(self, api_key: Optional[str]) -> Optional[str]: @@ -133,7 +134,6 @@ def completion(self, *args, **kwargs): kwargs["messages"] = self._apply_special_handling(kwargs.get("messages", [])) kwargs["model"] = self._normalize_model_name(kwargs.get("model", None)) kwargs["custom_llm_provider"] = "openai" - return super().completion(*args, **kwargs) async def acompletion(self, *args, **kwargs): @@ -147,7 +147,6 @@ async def acompletion(self, *args, **kwargs): kwargs["model"] = self._normalize_model_name(kwargs.get("model", None)) kwargs["custom_llm_provider"] = "openai" kwargs["acompletion"] = True - return await super().completion(*args, **kwargs) def streaming(self, *args, **kwargs): @@ -160,9 +159,7 @@ def streaming(self, *args, **kwargs): kwargs["messages"] = self._apply_special_handling(kwargs.get("messages", [])) kwargs["model"] = self._normalize_model_name(kwargs.get("model", None)) kwargs["custom_llm_provider"] = "openai" - response = super().completion(*args, **kwargs) - for chunk in response: yield self.get_generic_chunk(chunk) @@ -177,39 +174,24 @@ async def astreaming(self, *args, **kwargs): kwargs["model"] = self._normalize_model_name(kwargs.get("model", None)) kwargs["custom_llm_provider"] = "openai" kwargs["acompletion"] = True - response = await super().completion(*args, **kwargs) - async for chunk in response: yield self.get_generic_chunk(chunk) def get_generic_chunk(self, chunk): - # Extract the first choice (standard for single-candidate streams) choice = chunk.choices[0] if chunk.choices else None delta = choice.delta if choice else None - - # Safe extraction of text (content can be None in tool-call chunks) text_content = delta.content if delta and delta.content else "" - - # Safe extraction of tool calls - # LiteLLM provides these as a list of objects, we pass them through - # or set to None if empty tool_calls = delta.tool_calls if delta and delta.tool_calls else None - if tool_calls and len(tool_calls): tool_calls = tool_calls[0] - - # Handle Usage (often only present in the final chunk) usage_data = getattr(chunk, "usage", None) - # If usage is a Pydantic object, dump it to dict; otherwise default to 0s if hasattr(usage_data, "model_dump"): usage_dict = usage_data.model_dump() elif isinstance(usage_data, dict): usage_dict = usage_data else: usage_dict = {"completion_tokens": 0, "prompt_tokens": 0, "total_tokens": 0} - - # 3. Construct the GenericStreamingChunk dictionary generic_chunk = { "finish_reason": choice.finish_reason if choice else None, "index": choice.index if choice else 0, @@ -218,7 +200,6 @@ def get_generic_chunk(self, chunk): "tool_use": tool_calls, "usage": usage_dict, } - return generic_chunk @@ -228,27 +209,21 @@ def _register_provider_with_litellm(slug: str, config: Dict) -> None: from litellm.llms.openai_like.json_loader import JSONProviderRegistry except Exception: return - JSONProviderRegistry.load() - base_url = config.get("api_base") api_key_env = _coerce_str(config.get("api_key_env")) if not base_url or not api_key_env: return - try: - import litellm # noqa: WPS433 + import litellm except Exception: return - handler = _CUSTOM_HANDLERS.get(slug) if handler is None: handler = _JSONOpenAIProvider(slug, config) _CUSTOM_HANDLERS[slug] = handler - if handler is None: return - already_present = any(item.get("provider") == slug for item in litellm.custom_provider_map) if not already_present: litellm.custom_provider_map.append({"provider": slug, "custom_handler": handler}) @@ -273,15 +248,13 @@ def _load_provider_configs() -> Dict[str, Dict]: """Load provider configuration overrides from the packaged JSON file.""" configs: Dict[str, Dict] = {} try: - resource = importlib_resources.files("aider.resources").joinpath(RESOURCE_FILE) + resource = importlib_resources.files("cecli.resources").joinpath(RESOURCE_FILE) data = json.loads(resource.read_text()) - except (FileNotFoundError, json.JSONDecodeError): # pragma: no cover + except (FileNotFoundError, json.JSONDecodeError): data = {} - for provider, override in data.items(): base = configs.get(provider, {}) configs[provider] = _deep_merge(base, override) - return configs @@ -289,11 +262,11 @@ def _load_provider_configs() -> Dict[str, Dict]: class ModelProviderManager: - CACHE_TTL = 60 * 60 * 24 # 24 hours + CACHE_TTL = 60 * 60 * 24 DEFAULT_TOKEN_PRICE_RATIO = 1000000 def __init__(self, provider_configs: Optional[Dict[str, Dict]] = None) -> None: - self.cache_dir = Path.home() / ".aider" / "caches" + self.cache_dir = handle_core_files(Path.home() / ".cecli" / "caches") self.verify_ssl: bool = True self.provider_configs = provider_configs or deepcopy(PROVIDER_CONFIGS) self._provider_cache: Dict[str, Dict | None] = {} @@ -339,7 +312,6 @@ def get_model_info(self, model: str) -> Dict: provider, route = self._split_model(model) if not provider or not self._ensure_provider_state(provider): return {} - content = self._ensure_content(provider) record = self._find_record(content, route) if not record and self.refresh_provider_cache(provider): @@ -413,10 +385,8 @@ def _record_to_info(self, record: Dict, provider: str) -> Dict: "top_provider_context_length", "top_provider", ) - if isinstance(context_len, dict): context_len = context_len.get("context_length") or context_len.get("max_tokens") - pricing = record.get("pricing", {}) if isinstance(record.get("pricing"), dict) else {} input_cost = _cost_per_token( _first_value(pricing, "prompt", "input", "prompt_tokens") @@ -426,7 +396,6 @@ def _record_to_info(self, record: Dict, provider: str) -> Dict: _first_value(pricing, "completion", "output", "completion_tokens") or _first_value(record, "output_cost_per_token", "completion_cost_per_token") ) - max_tokens = _first_value( record, "max_tokens", @@ -443,22 +412,16 @@ def _record_to_info(self, record: Dict, provider: str) -> Dict: "context_window", "top_provider_context_length", ) - if max_tokens is None: max_tokens = context_len if max_output_tokens is None: max_output_tokens = context_len - # Normalize pricing: detect if values are in $/M format vs $/token format - # If cost >= 0.001, it's likely in $/M format (e.g., "1.0" = $1/M tokens) - # If cost < 0.001, it's likely already in $/token format (e.g., "0.00000055") def _normalize_cost(cost: Optional[float]) -> float: if cost is None or cost == 0: return 0.0 if cost >= 0.001: - # Likely in $/M format, convert to $/token return cost / self.DEFAULT_TOKEN_PRICE_RATIO - # Already in $/token format return cost info = { @@ -496,7 +459,6 @@ def _load_cache(self, provider: str) -> None: def _update_cache(self, provider: str) -> None: payload = self._fetch_provider_models(provider) cache_file = self._get_cache_file(provider) - if payload: self._provider_cache[provider] = payload try: @@ -504,7 +466,6 @@ def _update_cache(self, provider: str) -> None: except OSError: pass return - static_models = self.provider_configs[provider].get("static_models") if static_models and not self._provider_cache.get(provider): self._provider_cache[provider] = {"data": static_models} @@ -518,19 +479,15 @@ def _fetch_provider_models(self, provider: str) -> Optional[Dict]: models_url = api_base.rstrip("/") + "/models" if not models_url: return None - headers = {} default_headers = config.get("default_headers") or {} headers.update(default_headers) - api_key = self._get_api_key(provider) requires_api_key = config.get("requires_api_key", True) - if api_key: headers["Authorization"] = f"Bearer {api_key}" elif requires_api_key: return None - try: response = requests.get( models_url, @@ -540,7 +497,7 @@ def _fetch_provider_models(self, provider: str) -> Optional[Dict]: ) response.raise_for_status() return response.json() - except Exception as ex: # noqa: BLE001 + except Exception as ex: print(f"Failed to fetch {provider} model list: {ex}") return None @@ -563,7 +520,7 @@ def ensure_litellm_providers_registered() -> None: _PROVIDERS_REGISTERED = True -_NUMBER_RE = re.compile(r"-?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?") +_NUMBER_RE = re.compile("-?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?") def _cost_per_token(val: Optional[str | float | int]) -> Optional[float]: diff --git a/aider/helpers/profiler.py b/cecli/helpers/profiler.py similarity index 100% rename from aider/helpers/profiler.py rename to cecli/helpers/profiler.py diff --git a/aider/helpers/requests.py b/cecli/helpers/requests.py similarity index 100% rename from aider/helpers/requests.py rename to cecli/helpers/requests.py diff --git a/aider/helpers/similarity.py b/cecli/helpers/similarity.py similarity index 100% rename from aider/helpers/similarity.py rename to cecli/helpers/similarity.py diff --git a/aider/helpers/skills.py b/cecli/helpers/skills.py similarity index 99% rename from aider/helpers/skills.py rename to cecli/helpers/skills.py index b57c1e0d313..1f99c79018c 100644 --- a/aider/helpers/skills.py +++ b/cecli/helpers/skills.py @@ -1,5 +1,5 @@ """ -Skills helper for aider. +Skills helper for cecli. This module provides functions for loading, parsing, and managing skills according to the Skills specification. diff --git a/aider/history.py b/cecli/history.py similarity index 98% rename from aider/history.py rename to cecli/history.py index 2780371ff35..0d0cac6b089 100644 --- a/aider/history.py +++ b/cecli/history.py @@ -1,8 +1,8 @@ import argparse -import aider.prompts.utils.system as prompts -from aider import models -from aider.dump import dump # noqa: F401 +import cecli.prompts.utils.system as prompts +from cecli import models +from cecli.dump import dump # noqa: F401 class ChatSummary: diff --git a/aider/io.py b/cecli/io.py similarity index 99% rename from aider/io.py rename to cecli/io.py index 2b50c9d88f5..d24e54aa972 100644 --- a/aider/io.py +++ b/cecli/io.py @@ -40,7 +40,7 @@ from rich.style import Style as RichStyle from rich.text import Text -from aider.helpers import coroutines +from cecli.helpers import coroutines from .dump import dump # noqa: F401 from .editor import pipe_editor @@ -48,7 +48,7 @@ from .waiting import Spinner # Constants -NOTIFICATION_MESSAGE = "Aider is waiting for your input" +NOTIFICATION_MESSAGE = "cecli is waiting for your input" def ensure_hash_prefix(color): @@ -500,7 +500,7 @@ def __init__( # Validate color settings after console is initialized self._validate_color_settings() - self.append_chat_history(f"\n# aider chat started at {current_time}\n\n") + self.append_chat_history(f"\n# cecli chat started at {current_time}\n\n") def _spinner_supports_unicode(self) -> bool: if not self.is_tty: @@ -1617,17 +1617,17 @@ def get_default_notification_command(self): if system == "Darwin": # macOS # Check for terminal-notifier first if shutil.which("terminal-notifier"): - return f"terminal-notifier -title 'Aider' -message '{NOTIFICATION_MESSAGE}'" + return f"terminal-notifier -title 'cecli' -message '{NOTIFICATION_MESSAGE}'" # Fall back to osascript return ( - f'osascript -e \'display notification "{NOTIFICATION_MESSAGE}" with title "Aider"\'' + f'osascript -e \'display notification "{NOTIFICATION_MESSAGE}" with title "cecli"\'' ) elif system == "Linux": # Check for common Linux notification tools for cmd in ["notify-send", "zenity"]: if shutil.which(cmd): if cmd == "notify-send": - return f"notify-send 'Aider' '{NOTIFICATION_MESSAGE}'" + return f"notify-send 'cecli' '{NOTIFICATION_MESSAGE}'" elif cmd == "zenity": return f"zenity --notification --text='{NOTIFICATION_MESSAGE}'" return None # No known notification tool found @@ -1637,7 +1637,7 @@ def get_default_notification_command(self): "powershell -command" " \"[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms');" f" [System.Windows.Forms.MessageBox]::Show('{NOTIFICATION_MESSAGE}'," - " 'Aider')\"" + " 'cecli')\"" ) return None # Unknown system diff --git a/aider/linter.py b/cecli/linter.py similarity index 98% rename from aider/linter.py rename to cecli/linter.py index d386696e50f..360655bf650 100644 --- a/aider/linter.py +++ b/cecli/linter.py @@ -11,8 +11,8 @@ from grep_ast import TreeContext, filename_to_lang from grep_ast.tsl import get_parser # noqa: E402 -from aider.dump import dump # noqa: F401 -from aider.run_cmd import run_cmd_subprocess # noqa: F401 +from cecli.dump import dump # noqa: F401 +from cecli.run_cmd import run_cmd_subprocess # noqa: F401 # tree_sitter is throwing a FutureWarning warnings.simplefilter("ignore", category=FutureWarning) diff --git a/aider/llm.py b/cecli/llm.py similarity index 92% rename from aider/llm.py rename to cecli/llm.py index ff320dee3d3..7ddd15fcb5e 100644 --- a/aider/llm.py +++ b/cecli/llm.py @@ -5,16 +5,16 @@ import warnings from collections.abc import Coroutine -from aider.dump import dump # noqa: F401 -from aider.helpers.model_providers import ensure_litellm_providers_registered +from cecli.dump import dump # noqa: F401 +from cecli.helpers.model_providers import ensure_litellm_providers_registered warnings.filterwarnings("ignore", category=UserWarning, module="pydantic") -AIDER_SITE_URL = "https://aider.chat" -AIDER_APP_NAME = "Aider" +SITE_URL = "https://cecli.dev" +APP_NAME = "cecli" -os.environ["OR_SITE_URL"] = AIDER_SITE_URL -os.environ["OR_APP_NAME"] = AIDER_APP_NAME +os.environ["OR_SITE_URL"] = SITE_URL +os.environ["OR_APP_NAME"] = APP_NAME os.environ["LITELLM_MODE"] = "PRODUCTION" # `import litellm` takes 1.5 seconds, defer it! diff --git a/cecli/main.py b/cecli/main.py new file mode 100644 index 00000000000..8d1f46d934d --- /dev/null +++ b/cecli/main.py @@ -0,0 +1,1278 @@ +import os + +from cecli.helpers.file_searcher import handle_core_files + +try: + if not os.getenv("CECLIDEFAULT_TLS"): + import truststore + + truststore.inject_into_ssl() +except Exception as e: + print(e) + pass +import asyncio +import json +import os +import re +import sys +import threading +import time +import traceback +import webbrowser +from dataclasses import fields +from pathlib import Path + +try: + import git +except ImportError: + git = None +import importlib_resources +import shtab +from dotenv import load_dotenv + +if sys.platform == "win32": + if hasattr(asyncio, "set_event_loop_policy"): + asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) +from prompt_toolkit.enums import EditingMode + +from cecli import __version__, models, urls, utils +from cecli.args import get_parser +from cecli.coders import Coder +from cecli.coders.base_coder import UnknownEditFormat +from cecli.commands import Commands, SwitchCoder +from cecli.deprecated_args import handle_deprecated_model_args +from cecli.format_settings import format_settings, scrub_sensitive_info +from cecli.helpers.copypaste import ClipboardWatcher +from cecli.helpers.file_searcher import generate_search_path_list +from cecli.history import ChatSummary +from cecli.io import InputOutput +from cecli.llm import litellm +from cecli.mcp import load_mcp_servers +from cecli.models import ModelSettings +from cecli.onboarding import offer_openrouter_oauth, select_default_model +from cecli.repo import ANY_GIT_ERROR, GitRepo +from cecli.report import report_uncaught_exceptions, set_args_error_data +from cecli.versioncheck import check_version, install_from_main_branch, install_upgrade +from cecli.watch import FileWatcher + +from .dump import dump # noqa + + +def convert_yaml_to_json_string(value): + """ + Convert YAML dict/list values to JSON strings for compatibility. + + configargparse.YAMLConfigFileParser converts YAML to Python objects, + but some arguments expect JSON strings. This function handles: + - Direct dict/list objects + - String representations of dicts/lists (Python literals) + - Already JSON strings (passed through unchanged) + + Args: + value: The value to convert + + Returns: + str: JSON string if value is a dict/list, otherwise the original value + """ + if value is None: + return None + if isinstance(value, (dict, list)): + return json.dumps(value) + if isinstance(value, str): + try: + import ast + + parsed = ast.literal_eval(value) + if isinstance(parsed, (dict, list)): + return json.dumps(parsed) + except (SyntaxError, ValueError): + pass + return value + + +def check_config_files_for_yes(config_files): + found = False + for config_file in config_files: + if Path(config_file).exists(): + try: + with open(config_file, "r") as f: + for line in f: + if line.strip().startswith("yes:"): + print("Configuration error detected.") + print(f"The file {config_file} contains a line starting with 'yes:'") + print("Please replace 'yes:' with 'yes-always:' in this file.") + found = True + except Exception: + pass + return found + + +def get_git_root(): + """Try and guess the git repo, since the conf.yml can be at the repo root""" + try: + repo = git.Repo(search_parent_directories=True) + return repo.working_tree_dir + except (git.InvalidGitRepositoryError, FileNotFoundError): + return None + + +def guessed_wrong_repo(io, git_root, fnames, git_dname): + """After we parse the args, we can determine the real repo. Did we guess wrong?""" + try: + check_repo = Path(GitRepo(io, fnames, git_dname).root).resolve() + except (OSError,) + ANY_GIT_ERROR: + return + if not git_root: + return str(check_repo) + git_root = Path(git_root).resolve() + if check_repo == git_root: + return + return str(check_repo) + + +def validate_tui_args(args): + """Validate that incompatible flags aren't used with --tui""" + if not args.tui: + return + incompatible = [] + if args.vim: + incompatible.append("--vim") + if not args.fancy_input: + incompatible.append("--no-fancy-input") + if incompatible: + print(f"Error: --tui is incompatible with: {', '.join(incompatible)}") + print("Remove these flags or use standard CLI mode.") + sys.exit(1) + + +async def make_new_repo(git_root, io): + try: + repo = git.Repo.init(git_root) + await check_gitignore(git_root, io, False) + except ANY_GIT_ERROR as err: + io.tool_error(f"Unable to create git repo in {git_root}") + io.tool_output(str(err)) + return + io.tool_output(f"Git repository created in {git_root}") + return repo + + +async def setup_git(git_root, io): + if git is None: + return + try: + cwd = Path.cwd() + except OSError: + cwd = None + repo = None + if git_root: + try: + repo = git.Repo(git_root) + except ANY_GIT_ERROR: + pass + elif cwd == Path.home(): + io.tool_warning( + "You should probably run cecli in your project's directory, not your home dir." + ) + return + elif cwd and await io.confirm_ask( + "No git repo found, create one to track cecli's changes (recommended)?", acknowledge=True + ): + git_root = str(cwd.resolve()) + repo = await make_new_repo(git_root, io) + if not repo: + return + try: + user_name = repo.git.config("--get", "user.name") or None + except git.exc.GitCommandError: + user_name = None + try: + user_email = repo.git.config("--get", "user.email") or None + except git.exc.GitCommandError: + user_email = None + if user_name and user_email: + return repo.working_tree_dir + with repo.config_writer() as git_config: + if not user_name: + git_config.set_value("user", "name", "Your Name") + io.tool_warning('Update git name with: git config user.name "Your Name"') + if not user_email: + git_config.set_value("user", "email", "you@example.com") + io.tool_warning('Update git email with: git config user.email "you@example.com"') + return repo.working_tree_dir + + +async def check_gitignore(git_root, io, ask=True): + if not git_root: + return + try: + repo = git.Repo(git_root) + patterns_to_add = [] + if not repo.ignored(".cecli"): + patterns_to_add.append(".cecli*") + env_path = Path(git_root) / ".env" + if env_path.exists() and not repo.ignored(".env"): + patterns_to_add.append(".env") + if not patterns_to_add: + return + gitignore_file = Path(git_root) / ".gitignore" + if gitignore_file.exists(): + try: + content = io.read_text(gitignore_file) + if content is None: + return + if not content.endswith("\n"): + content += "\n" + except OSError as e: + io.tool_error(f"Error when trying to read {gitignore_file}: {e}") + return + else: + content = "" + except ANY_GIT_ERROR: + return + if ask: + io.tool_output("You can skip this check with --no-gitignore") + if not await io.confirm_ask( + f"Add {', '.join(patterns_to_add)} to .gitignore (recommended)?", acknowledge=True + ): + return + content += "\n".join(patterns_to_add) + "\n" + try: + io.write_text(gitignore_file, content) + io.tool_output(f"Added {', '.join(patterns_to_add)} to .gitignore") + except OSError as e: + io.tool_error(f"Error when trying to write to {gitignore_file}: {e}") + io.tool_output( + "Try running with appropriate permissions or manually add these patterns to .gitignore:" + ) + for pattern in patterns_to_add: + io.tool_output(f" {pattern}") + + +def parse_lint_cmds(lint_cmds, io): + err = False + res = dict() + for lint_cmd in lint_cmds: + if re.match("^[a-z]+:.*", lint_cmd): + pieces = lint_cmd.split(":") + lang = pieces[0] + cmd = lint_cmd[len(lang) + 1 :] + lang = lang.strip() + else: + lang = None + cmd = lint_cmd + cmd = cmd.strip() + if cmd: + res[lang] = cmd + else: + io.tool_error(f'Unable to parse --lint-cmd "{lint_cmd}"') + io.tool_output('The arg should be "language: cmd --args ..."') + io.tool_output('For example: --lint-cmd "python: flake8 --select=E9"') + err = True + if err: + return + return res + + +def register_models(git_root, model_settings_fname, io, verbose=False): + model_settings_files = generate_search_path_list( + ".cecli.model.settings.yml", git_root, model_settings_fname + ) + try: + files_loaded = models.register_models(model_settings_files) + if len(files_loaded) > 0: + if verbose: + io.tool_output("Loaded model settings from:") + for file_loaded in files_loaded: + io.tool_output(f" - {file_loaded}") + elif verbose: + io.tool_output("No model settings files loaded") + if ( + model_settings_fname + and model_settings_fname not in files_loaded + and model_settings_fname != ".cecli.model.settings.yml" + ): + io.tool_warning(f"Model Settings File Not Found: {model_settings_fname}") + except Exception as e: + io.tool_error(f"Error loading cecli model settings: {e}") + return 1 + if verbose: + io.tool_output("Searched for model settings files:") + for file in model_settings_files: + io.tool_output(f" - {file}") + return None + + +def load_dotenv_files(git_root, dotenv_fname, encoding="utf-8"): + dotenv_files = generate_search_path_list(".env", git_root, dotenv_fname) + oauth_keys_file = handle_core_files(Path.home() / ".cecli" / "oauth-keys.env") + if oauth_keys_file.exists(): + dotenv_files.insert(0, str(oauth_keys_file.resolve())) + dotenv_files = list(dict.fromkeys(dotenv_files)) + loaded = [] + for fname in dotenv_files: + try: + if Path(fname).exists(): + load_dotenv(fname, override=True, encoding=encoding) + loaded.append(fname) + except OSError as e: + print(f"OSError loading {fname}: {e}") + except Exception as e: + print(f"Error loading {fname}: {e}") + return loaded + + +def register_litellm_models(git_root, model_metadata_fname, io, verbose=False): + model_metadata_files = [] + resource_metadata = importlib_resources.files("cecli.resources").joinpath("model-metadata.json") + model_metadata_files.append(str(resource_metadata)) + model_metadata_files += generate_search_path_list( + ".cecli.model.metadata.json", git_root, model_metadata_fname + ) + try: + model_metadata_files_loaded = models.register_litellm_models(model_metadata_files) + if len(model_metadata_files_loaded) > 0 and verbose: + io.tool_output("Loaded model metadata from:") + for model_metadata_file in model_metadata_files_loaded: + io.tool_output(f" - {model_metadata_file}") + if ( + model_metadata_fname + and model_metadata_fname not in model_metadata_files_loaded + and model_metadata_fname != ".cecli.model.metadata.json" + ): + io.tool_warning(f"Model Metadata File Not Found: {model_metadata_fname}") + except Exception as e: + io.tool_error(f"Error loading model metadata models: {e}") + return 1 + + +def load_model_overrides(git_root, model_overrides_fname, io, verbose=False): + """Load model tag overrides from a YAML file.""" + from pathlib import Path + + import yaml + + model_overrides_files = generate_search_path_list( + ".cecli.model.overrides.yml", git_root, model_overrides_fname + ) + overrides = {} + files_loaded = [] + for fname in model_overrides_files: + try: + if Path(fname).exists(): + with open(fname, "r") as f: + content = yaml.safe_load(f) + if content: + for model_name, tags in content.items(): + if model_name not in overrides: + overrides[model_name] = {} + overrides[model_name].update(tags) + files_loaded.append(fname) + except Exception as e: + io.tool_error(f"Error loading model overrides from {fname}: {e}") + if len(files_loaded) > 0 and verbose: + io.tool_output("Loaded model overrides from:") + for file_loaded in files_loaded: + io.tool_output(f" - {file_loaded}") + if ( + model_overrides_fname + and model_overrides_fname not in files_loaded + and model_overrides_fname != ".cecli.model.overrides.yml" + ): + io.tool_warning(f"Model Overrides File Not Found: {model_overrides_fname}") + return overrides + + +def load_model_overrides_from_string(model_overrides_str, io): + """Load model tag overrides from a JSON/YAML string.""" + import json + + import yaml + + overrides = {} + if not model_overrides_str: + return overrides + try: + try: + content = json.loads(model_overrides_str) + except json.JSONDecodeError: + content = yaml.safe_load(model_overrides_str) + if content and isinstance(content, dict): + for model_name, tags in content.items(): + if model_name not in overrides: + overrides[model_name] = {} + overrides[model_name].update(tags) + return overrides + except Exception as e: + io.tool_error(f"Error parsing model overrides string: {e}") + return {} + + +async def sanity_check_repo(repo, io): + if not repo: + return True + if not repo.repo.working_tree_dir: + io.tool_error("The git repo does not seem to have a working tree?") + return False + bad_ver = False + try: + repo.get_tracked_files() + if not repo.git_repo_error: + return True + error_msg = str(repo.git_repo_error) + except UnicodeDecodeError as exc: + error_msg = ( + "Failed to read the Git repository. " + "This issue is likely caused by a path encoded in a format different from " + f'the expected encoding "{sys.getfilesystemencoding()}"\n' + f"Internal error: {str(exc)}" + ) + except ANY_GIT_ERROR as exc: + error_msg = str(exc) + bad_ver = "version in (1, 2)" in error_msg + except AssertionError as exc: + error_msg = str(exc) + bad_ver = True + if bad_ver: + io.tool_error("cecli only works with git repos with version number 1 or 2.") + io.tool_output("You may be able to convert your repo: git update-index --index-version=2") + io.tool_output("Or run cecli --no-git to proceed without using git.") + await io.offer_url( + urls.git_index_version, "Open documentation url for more info?", acknowledge=True + ) + return False + io.tool_error("Unable to read git repository, it may be corrupt?") + io.tool_output(error_msg) + return False + + +PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) +log_file = None +file_excludelist = { + "get_bottom_toolbar": True, + "": True, + "is_active": True, + "auto_save_session": True, + "input_task": True, + "output_task": True, + "check_output_queue": True, + "_animate_spinner": True, + "handle_output_message": True, + "update_spinner": True, +} + + +def custom_tracer(frame, event, arg): + try: + import os + except Exception: + return None + global log_file + if not log_file: + os.makedirs(".cecli/logs/", exist_ok=True) + log_file = open(".cecli/logs/debug.log", "w", buffering=1) + filename = os.path.abspath(frame.f_code.co_filename) + if not filename.startswith(PROJECT_ROOT): + return None + if filename.endswith("repo.py"): + return None + if event == "call": + func_name = frame.f_code.co_name + line_no = frame.f_lineno + if func_name not in file_excludelist: + log_file.write( + f"""-> CALL: {func_name}() in {os.path.basename(filename)}:{line_no} - {time.time()} +""" + ) + if event == "return": + func_name = frame.f_code.co_name + line_no = frame.f_lineno + if func_name not in file_excludelist: + log_file.write( + f"""<- RETURN: {func_name}() in {os.path.basename(filename)}:{line_no} - {time.time()} +""" + ) + return custom_tracer + + +def main(argv=None, input=None, output=None, force_git_root=None, return_coder=False): + if sys.platform == "win32": + if sys.version_info >= (3, 12) and hasattr(asyncio, "SelectorEventLoop"): + return asyncio.run( + main_async(argv, input, output, force_git_root, return_coder), + loop_factory=asyncio.SelectorEventLoop, + ) + return asyncio.run(main_async(argv, input, output, force_git_root, return_coder)) + + +async def main_async(argv=None, input=None, output=None, force_git_root=None, return_coder=False): + report_uncaught_exceptions() + if argv is None: + argv = sys.argv[1:] + if git is None: + git_root = None + elif force_git_root: + git_root = force_git_root + else: + git_root = get_git_root() + conf_fname = handle_core_files(Path(".cecli.conf.yml")) + default_config_files = [] + try: + default_config_files += [conf_fname.resolve()] + except OSError: + pass + if git_root: + git_conf = Path(git_root) / conf_fname + if git_conf not in default_config_files: + default_config_files.append(git_conf) + default_config_files.append(Path.home() / conf_fname) + default_config_files = list(map(str, default_config_files)) + parser = get_parser(default_config_files, git_root) + try: + args, unknown = parser.parse_known_args(argv) + except AttributeError as e: + if all(word in str(e) for word in ["bool", "object", "has", "no", "attribute", "strip"]): + if check_config_files_for_yes(default_config_files): + return await graceful_exit(None, 1) + raise e + if args.verbose: + print("Config files search order, if no --config:") + for file in default_config_files: + exists = "(exists)" if Path(file).exists() else "" + print(f" - {file} {exists}") + default_config_files.reverse() + parser = get_parser(default_config_files, git_root) + args, unknown = parser.parse_known_args(argv) + loaded_dotenvs = load_dotenv_files(git_root, args.env_file, args.encoding) + args, unknown = parser.parse_known_args(argv) + set_args_error_data(args) + if len(unknown): + print("Unknown Args: ", unknown) + if hasattr(args, "agent_config") and args.agent_config is not None: + args.agent_config = convert_yaml_to_json_string(args.agent_config) + if hasattr(args, "tui_config") and args.tui_config is not None: + args.tui_config = convert_yaml_to_json_string(args.tui_config) + if hasattr(args, "mcp_servers") and args.mcp_servers is not None: + args.mcp_servers = convert_yaml_to_json_string(args.mcp_servers) + if args.debug: + global log_file + os.makedirs(".cecli/logs/", exist_ok=True) + log_file = open(".cecli/logs/debug.log", "w", buffering=1) + sys.settrace(custom_tracer) + if args.shell_completions: + parser.prog = "cecli" + print(shtab.complete(parser, shell=args.shell_completions)) + return await graceful_exit(None, 0) + if git is None: + args.git = False + if not args.verify_ssl: + import httpx + + os.environ["SSL_VERIFY"] = "" + litellm._load_litellm() + litellm._lazy_module.client_session = httpx.Client(verify=False) + litellm._lazy_module.aclient_session = httpx.AsyncClient(verify=False) + models.model_info_manager.set_verify_ssl(False) + if args.timeout: + models.request_timeout = args.timeout + if args.dark_mode: + args.user_input_color = "#32FF32" + args.tool_error_color = "#FF3333" + args.tool_warning_color = "#FFFF00" + args.assistant_output_color = "#00FFFF" + args.code_theme = "monokai" + if args.light_mode: + args.user_input_color = "green" + args.tool_error_color = "red" + args.tool_warning_color = "#FFA500" + args.assistant_output_color = "blue" + args.code_theme = "default" + if return_coder and args.yes_always is None: + args.yes_always = True + if args.yes_always_commands: + args.yes_always = True + editing_mode = EditingMode.VI if args.vim else EditingMode.EMACS + + def get_io(pretty): + return InputOutput( + pretty, + args.yes_always, + args.input_history_file, + args.chat_history_file, + input=input, + output=output, + user_input_color=args.user_input_color, + tool_output_color=args.tool_output_color, + tool_warning_color=args.tool_warning_color, + tool_error_color=args.tool_error_color, + completion_menu_color=args.completion_menu_color, + completion_menu_bg_color=args.completion_menu_bg_color, + completion_menu_current_color=args.completion_menu_current_color, + completion_menu_current_bg_color=args.completion_menu_current_bg_color, + assistant_output_color=args.assistant_output_color, + code_theme=args.code_theme, + dry_run=args.dry_run, + encoding=args.encoding, + line_endings=args.line_endings, + editingmode=editing_mode, + fancy_input=args.fancy_input, + multiline_mode=args.multiline, + notifications=args.notifications, + notifications_command=args.notifications_command, + verbose=args.verbose, + ) + + validate_tui_args(args) + output_queue = None + input_queue = None + pre_init_io = get_io(args.pretty) + if args.tui or args.tui is None and not args.linear_output: + try: + from cecli.tui import create_tui_io + + args.tui = True + args.linear_output = True + print("Starting cecli TUI...", flush=True) + io, output_queue, input_queue = create_tui_io(args, editing_mode) + except ImportError as e: + print("Error: --tui requires 'textual' package") + print("Install with: pip install cecli[tui]") + print(f"Import error: {e}") + sys.exit(1) + else: + io = pre_init_io + if not args.tui: + try: + io.rule() + except UnicodeEncodeError as err: + if not io.pretty: + raise err + io = get_io(False) + io.tool_warning("Terminal does not support pretty output (UnicodeDecodeError)") + if args.set_env: + for env_setting in args.set_env: + try: + name, value = env_setting.split("=", 1) + os.environ[name.strip()] = value.strip() + except ValueError: + io.tool_error(f"Invalid --set-env format: {env_setting}") + io.tool_output("Format should be: ENV_VAR_NAME=value") + return await graceful_exit(None, 1) + if args.api_key: + for api_setting in args.api_key: + try: + provider, key = api_setting.split("=", 1) + env_var = f"{provider.strip().upper()}_API_KEY" + os.environ[env_var] = key.strip() + except ValueError: + io.tool_error(f"Invalid --api-key format: {api_setting}") + io.tool_output("Format should be: provider=key") + return await graceful_exit(None, 1) + if args.anthropic_api_key: + os.environ["ANTHROPIC_API_KEY"] = args.anthropic_api_key + if args.openai_api_key: + os.environ["OPENAI_API_KEY"] = args.openai_api_key + handle_deprecated_model_args(args, io) + if args.openai_api_base: + os.environ["OPENAI_API_BASE"] = args.openai_api_base + if args.openai_api_version: + io.tool_warning( + "--openai-api-version is deprecated, use --set-env OPENAI_API_VERSION=" + ) + os.environ["OPENAI_API_VERSION"] = args.openai_api_version + if args.openai_api_type: + io.tool_warning("--openai-api-type is deprecated, use --set-env OPENAI_API_TYPE=") + os.environ["OPENAI_API_TYPE"] = args.openai_api_type + if args.openai_organization_id: + io.tool_warning( + "--openai-organization-id is deprecated, use --set-env OPENAI_ORGANIZATION=" + ) + os.environ["OPENAI_ORGANIZATION"] = args.openai_organization_id + if args.verbose: + for fname in loaded_dotenvs: + io.tool_output(f"Loaded {fname}") + all_files = args.files + (args.file or []) + all_files = utils.expand_glob_patterns(all_files) + fnames = [str(Path(fn).resolve()) for fn in all_files] + read_patterns = args.read or [] + read_expanded = utils.expand_glob_patterns(read_patterns) + read_only_fnames = [] + for fn in read_expanded: + path = Path(fn).expanduser().resolve() + if path.is_dir(): + read_only_fnames.extend(str(f) for f in path.rglob("*") if f.is_file()) + else: + read_only_fnames.append(str(path)) + if len(all_files) > 1: + good = True + for fname in all_files: + if Path(fname).is_dir(): + io.tool_error(f"{fname} is a directory, not provided alone.") + good = False + if not good: + io.tool_output( + "Provide either a single directory of a git repo, or a list of one or more files." + ) + return await graceful_exit(None, 1) + git_dname = None + if len(all_files) == 1: + if Path(all_files[0]).is_dir(): + if args.git: + git_dname = str(Path(all_files[0]).resolve()) + fnames = [] + else: + io.tool_error(f"{all_files[0]} is a directory, but --no-git selected.") + return await graceful_exit(None, 1) + if args.git and not force_git_root and git is not None: + right_repo_root = guessed_wrong_repo(io, git_root, fnames, git_dname) + if right_repo_root: + return await main_async(argv, input, output, right_repo_root, return_coder=return_coder) + if args.just_check_update: + update_available = await check_version(io, just_check=True, verbose=args.verbose) + return await graceful_exit(None, 0 if not update_available else 1) + if args.install_main_branch: + success = await install_from_main_branch(io) + return await graceful_exit(None, 0 if success else 1) + if args.upgrade: + success = await install_upgrade(io) + return await graceful_exit(None, 0 if success else 1) + if args.check_update: + await check_version(io, verbose=args.verbose) + if args.verbose: + show = format_settings(parser, args) + io.tool_output(show) + cmd_line = " ".join(sys.argv) + cmd_line = scrub_sensitive_info(args, cmd_line) + io.tool_output(cmd_line, log_only=True) + is_first_run = is_first_run_of_new_version(io, verbose=args.verbose) + await check_and_load_imports(io, is_first_run, verbose=args.verbose) + register_models(git_root, args.model_settings_file, io, verbose=args.verbose) + register_litellm_models(git_root, args.model_metadata_file, io, verbose=args.verbose) + if args.list_models: + models.print_matching_models(io, args.list_models) + return await graceful_exit(None) + if args.alias: + for alias_def in args.alias: + parts = alias_def.split(":", 1) + if len(parts) != 2: + io.tool_error(f"Invalid alias format: {alias_def}") + io.tool_output("Format should be: alias:model-name") + return await graceful_exit(None, 1) + alias, model = parts + models.MODEL_ALIASES[alias.strip()] = model.strip() + selected_model_name = await select_default_model(args, io) + if not selected_model_name: + return await graceful_exit(None, 1) + args.model = selected_model_name + model_overrides = {} + if args.model_overrides_file: + model_overrides = load_model_overrides( + git_root, args.model_overrides_file, io, verbose=args.verbose + ) + if args.model_overrides: + direct_overrides = load_model_overrides_from_string(args.model_overrides, io) + for model_name, tags in direct_overrides.items(): + if model_name not in model_overrides: + model_overrides[model_name] = {} + model_overrides[model_name].update(tags) + override_index = {} + for base_model, suffixes in model_overrides.items(): + if not isinstance(suffixes, dict): + continue + for suffix, cfg in suffixes.items(): + if not isinstance(cfg, dict): + continue + full_name = f"{base_model}:{suffix}" + override_index[full_name] = base_model, cfg + + def apply_model_overrides(model_name): + """Return (effective_model_name, override_kwargs) for a given model_name. + + If model_name exactly matches a configured "base:suffix" override, we + switch to the base model and apply that override dict. Otherwise we + leave the name unchanged and return empty overrides. + """ + if not model_name: + return model_name, {} + prefix = "" + if model_name.startswith(models.COPY_PASTE_PREFIX): + prefix = models.COPY_PASTE_PREFIX + model_name = model_name[len(prefix) :] + entry = override_index.get(model_name) + if not entry: + model_name = prefix + model_name + return model_name, {} + base_model, cfg = entry + model_name = prefix + base_model + return model_name, cfg.copy() + + main_model_name, main_model_overrides = apply_model_overrides(args.model) + weak_model_name, weak_model_overrides = apply_model_overrides(args.weak_model) + editor_model_name, editor_model_overrides = apply_model_overrides(args.editor_model) + weak_model_obj = None + if weak_model_name: + weak_model_obj = models.Model( + weak_model_name, + weak_model=False, + verbose=args.verbose, + io=io, + override_kwargs=weak_model_overrides, + ) + editor_model_obj = None + if editor_model_name: + editor_model_obj = models.Model( + editor_model_name, + editor_model=False, + verbose=args.verbose, + io=io, + override_kwargs=editor_model_overrides, + ) + if main_model_name.startswith("openrouter/") and not os.environ.get("OPENROUTER_API_KEY"): + io.tool_warning( + f"The specified model '{main_model_name}' requires an OpenRouter API key, which was not" + " found." + ) + if await offer_openrouter_oauth(io): + if os.environ.get("OPENROUTER_API_KEY"): + io.tool_output("OpenRouter successfully connected.") + else: + io.tool_error( + "OpenRouter authentication seemed successful, but the key is still missing." + ) + return await graceful_exit(None, 1) + else: + io.tool_error( + f"Unable to proceed without an OpenRouter API key for model '{main_model_name}'." + ) + await io.offer_url( + urls.models_and_keys, "Open documentation URL for more info?", acknowledge=True + ) + return await graceful_exit(None, 1) + main_model = models.Model( + main_model_name, + weak_model=weak_model_obj, + editor_model=editor_model_obj, + editor_edit_format=args.editor_edit_format, + verbose=args.verbose, + io=io, + override_kwargs=main_model_overrides, + ) + if args.copy_paste and main_model.copy_paste_transport == "api": + main_model.enable_copy_paste_mode() + if main_model.remove_reasoning is not None: + io.tool_warning( + "Model setting 'remove_reasoning' is deprecated, please use 'reasoning_tag' instead." + ) + if args.reasoning_effort is not None: + if ( + not args.check_model_accepts_settings + or main_model.accepts_settings + and "reasoning_effort" in main_model.accepts_settings + ): + main_model.set_reasoning_effort(args.reasoning_effort) + if args.thinking_tokens is not None: + if ( + not args.check_model_accepts_settings + or main_model.accepts_settings + and "thinking_tokens" in main_model.accepts_settings + ): + main_model.set_thinking_tokens(args.thinking_tokens) + if args.check_model_accepts_settings: + settings_to_check = [ + {"arg": args.reasoning_effort, "name": "reasoning_effort"}, + {"arg": args.thinking_tokens, "name": "thinking_tokens"}, + ] + for setting in settings_to_check: + if setting["arg"] is not None and ( + not main_model.accepts_settings + or setting["name"] not in main_model.accepts_settings + ): + io.tool_warning( + f"Warning: {main_model.name} does not support '{setting['name']}', ignoring." + ) + io.tool_output( + f"Use --no-check-model-accepts-settings to force the '{setting['name']}'" + " setting." + ) + if args.copy_paste and args.edit_format is None: + if main_model.edit_format in ("diff", "whole", "diff-fenced"): + main_model.edit_format = "editor-" + main_model.edit_format + if args.verbose: + io.tool_output("Model metadata:") + io.tool_output(json.dumps(main_model.info, indent=4)) + io.tool_output("Model settings:") + for attr in sorted(fields(ModelSettings), key=lambda x: x.name): + val = getattr(main_model, attr.name) + val = json.dumps(val, indent=4) + io.tool_output(f"{attr.name}: {val}") + lint_cmds = parse_lint_cmds(args.lint_cmd, io) + if lint_cmds is None: + return await graceful_exit(None, 1) + repo = None + if args.git: + try: + repo = GitRepo( + io, + fnames, + git_dname, + args.cecli_ignore, + models=main_model.commit_message_models(), + attribute_author=args.attribute_author, + attribute_committer=args.attribute_committer, + attribute_commit_message_author=args.attribute_commit_message_author, + attribute_commit_message_committer=args.attribute_commit_message_committer, + commit_prompt=args.commit_prompt, + subtree_only=args.subtree_only, + git_commit_verify=args.git_commit_verify, + attribute_co_authored_by=args.attribute_co_authored_by, + ) + except FileNotFoundError: + pass + if not args.skip_sanity_check_repo: + if not await sanity_check_repo(repo, io): + return await graceful_exit(None, 1) + commands = Commands( + io, + None, + voice_language=args.voice_language, + voice_input_device=args.voice_input_device, + voice_format=args.voice_format, + verify_ssl=args.verify_ssl, + args=args, + parser=parser, + verbose=args.verbose, + editor=args.editor, + original_read_only_fnames=read_only_fnames, + ) + summarizer = ChatSummary( + [main_model.weak_model, main_model], + args.max_chat_history_tokens or main_model.max_chat_history_tokens, + ) + if args.cache_prompts and args.map_refresh == "auto": + args.map_refresh = "files" + if not main_model.streaming: + if args.stream: + io.tool_warning( + f"Warning: Streaming is not supported by {main_model.name}. Disabling streaming." + " Set stream: false in config file or use --no-stream to skip this warning." + ) + args.stream = False + if args.map_tokens is None: + map_tokens = main_model.get_repo_map_tokens() + else: + map_tokens = args.map_tokens + if args.enable_context_compaction and ( + args.context_compaction_max_tokens is None or args.context_compaction_max_tokens < 1 + ): + max_input_tokens = main_model.info.get("max_input_tokens") + ratio = 0.8 + if args.context_compaction_max_tokens: + ratio = args.context_compaction_max_tokens + if max_input_tokens: + args.context_compaction_max_tokens = int(max_input_tokens * ratio) + try: + mcp_servers = load_mcp_servers( + args.mcp_servers, args.mcp_servers_file, io, args.verbose, args.mcp_transport + ) + if not mcp_servers: + mcp_servers = [] + coder = await Coder.create( + main_model=main_model, + edit_format=args.edit_format, + io=io, + args=args, + repo=repo, + fnames=fnames, + read_only_fnames=read_only_fnames, + read_only_stubs_fnames=[], + show_diffs=args.show_diffs, + auto_commits=args.auto_commits, + dirty_commits=args.dirty_commits, + dry_run=args.dry_run, + map_tokens=map_tokens, + verbose=args.verbose, + stream=args.stream, + use_git=args.git, + restore_chat_history=args.restore_chat_history, + auto_lint=args.auto_lint, + auto_test=args.auto_test, + lint_cmds=lint_cmds, + test_cmd=args.test_cmd, + commands=commands, + summarizer=summarizer, + map_refresh=args.map_refresh, + cache_prompts=args.cache_prompts, + map_mul_no_files=args.map_multiplier_no_files, + map_max_line_length=args.map_max_line_length, + num_cache_warming_pings=args.cache_keepalive_pings, + suggest_shell_commands=args.suggest_shell_commands, + chat_language=args.chat_language, + commit_language=args.commit_language, + detect_urls=args.detect_urls, + auto_copy_context=args.copy_paste, + auto_accept_architect=args.auto_accept_architect, + mcp_servers=mcp_servers, + add_gitignore_files=args.add_gitignore_files, + enable_context_compaction=args.enable_context_compaction, + context_compaction_max_tokens=args.context_compaction_max_tokens, + context_compaction_summary_tokens=args.context_compaction_summary_tokens, + map_cache_dir=args.map_cache_dir, + repomap_in_memory=args.map_memory_cache, + linear_output=args.linear_output, + ) + if args.show_model_warnings: + problem = await models.sanity_check_models(pre_init_io, main_model) + if problem: + pre_init_io.tool_output("You can skip this check with --no-show-model-warnings") + try: + await pre_init_io.offer_url( + urls.model_warnings, + "Open documentation url for more info?", + acknowledge=True, + ) + pre_init_io.tool_output() + except KeyboardInterrupt: + return await graceful_exit(coder, 1) + if args.git: + git_root = await setup_git(git_root, pre_init_io) + if args.gitignore: + await check_gitignore(git_root, pre_init_io) + except UnknownEditFormat as err: + pre_init_io.tool_error(str(err)) + await pre_init_io.offer_url( + urls.edit_formats, "Open documentation about edit formats?", acknowledge=True + ) + return await graceful_exit(None, 1) + except ValueError as err: + pre_init_io.tool_error(str(err)) + return await graceful_exit(None, 1) + if return_coder: + return coder + ignores = [] + if git_root: + ignores.append(str(Path(git_root) / ".gitignore")) + if args.cecli_ignore: + ignores.append(args.cecli_ignore) + if args.watch_files: + file_watcher = FileWatcher( + coder, + gitignores=ignores, + verbose=args.verbose, + root=str(Path.cwd()) if args.subtree_only else None, + ) + coder.file_watcher = file_watcher + if args.copy_paste: + ClipboardWatcher(coder.io, verbose=args.verbose) + if args.show_prompts: + coder.cur_messages += [dict(role="user", content="Hello!")] + messages = coder.format_messages().all_messages() + utils.show_messages(messages) + return await graceful_exit(coder) + if args.lint: + await coder.commands.do_run("lint", "") + if args.test: + if not args.test_cmd: + io.tool_error("No --test-cmd provided.") + return await graceful_exit(coder, 1) + await coder.commands.cmd_test(args.test_cmd) + if io.placeholder: + await coder.run(io.placeholder) + if args.commit: + if args.dry_run: + io.tool_output("Dry run enabled, skipping commit.") + else: + await coder.commands.cmd_commit() + if args.lint or args.test or args.commit: + return await graceful_exit(coder) + if args.show_repo_map: + repo_map = coder.get_repo_map() + if repo_map: + io.tool_output(repo_map) + return await graceful_exit(coder) + if args.apply: + content = io.read_text(args.apply) + if content is None: + return await graceful_exit(coder) + coder.partial_response_content = content + await coder.apply_updates() + return await graceful_exit(coder) + if args.apply_clipboard_edits: + args.edit_format = main_model.editor_edit_format + args.message = "/paste" + if args.show_release_notes is True: + io.tool_output(f"Opening release notes: {urls.release_notes}") + io.tool_output() + webbrowser.open(urls.release_notes) + elif args.show_release_notes is None and is_first_run: + io.tool_output() + await io.offer_url( + urls.release_notes, + "Would you like to see what's new in this version?", + allow_never=False, + acknowledge=True, + ) + if git_root and Path.cwd().resolve() != Path(git_root).resolve(): + io.tool_warning( + "Note: in-chat filenames are always relative to the git working dir, not the current" + " working dir." + ) + io.tool_output(f"Cur working dir: {Path.cwd()}") + io.tool_output(f"Git working dir: {git_root}") + if args.stream and args.cache_prompts: + io.tool_warning("Cost estimates may be inaccurate when using streaming and caching.") + if args.load: + await commands.cmd_load(args.load) + if args.message: + io.add_to_input_history(args.message) + io.tool_output() + try: + await coder.run(with_message=args.message) + except (SwitchCoder, KeyboardInterrupt, SystemExit): + pass + return await graceful_exit(coder) + if args.message_file: + try: + message_from_file = io.read_text(args.message_file) + io.tool_output() + await coder.run(with_message=message_from_file) + except (SwitchCoder, KeyboardInterrupt, SystemExit): + pass + except FileNotFoundError: + io.tool_error(f"Message file not found: {args.message_file}") + return await graceful_exit(coder, 1) + except IOError as e: + io.tool_error(f"Error reading message file: {e}") + return await graceful_exit(coder, 1) + return await graceful_exit(coder) + if args.exit: + return await graceful_exit(coder) + if args.auto_load: + try: + from cecli.sessions import SessionManager + + session_manager = SessionManager(coder, io) + session_manager.load_session( + args.auto_save_session_name if args.auto_save_session_name else "auto-save" + ) + except Exception: + pass + if args.tui: + from cecli.tui import launch_tui + + del pre_init_io + return_code = await launch_tui(coder, output_queue, input_queue, args) + return await graceful_exit(coder, return_code) + while True: + try: + coder.ok_to_warm_cache = bool(args.cache_keepalive_pings) + await coder.run() + return await graceful_exit(coder) + except SwitchCoder as switch: + coder.ok_to_warm_cache = False + if hasattr(switch, "placeholder") and switch.placeholder is not None: + io.placeholder = switch.placeholder + kwargs = dict(io=io, from_coder=coder) + kwargs.update(switch.kwargs) + if "show_announcements" in kwargs: + del kwargs["show_announcements"] + kwargs["num_cache_warming_pings"] = 0 + kwargs["args"] = coder.args + coder = await Coder.create(**kwargs) + if switch.kwargs.get("show_announcements") is False: + coder.suppress_announcements_for_next_prompt = True + except SystemExit: + sys.settrace(None) + return await graceful_exit(coder) + + +def is_first_run_of_new_version(io, verbose=False): + """Check if this is the first run of a new version/executable combination""" + installs_file = handle_core_files(Path.home() / ".cecli" / "installs.json") + key = __version__, sys.executable + if ".dev" in __version__: + return False + if verbose: + io.tool_output( + f"Checking imports for version {__version__} and executable {sys.executable}" + ) + io.tool_output(f"Installs file: {installs_file}") + try: + if installs_file.exists(): + with open(installs_file, "r") as f: + installs = json.load(f) + if verbose: + io.tool_output("Installs file exists and loaded") + else: + installs = {} + if verbose: + io.tool_output("Installs file does not exist, creating new dictionary") + is_first_run = str(key) not in installs + if is_first_run: + installs[str(key)] = True + installs_file.parent.mkdir(parents=True, exist_ok=True) + with open(installs_file, "w") as f: + json.dump(installs, f, indent=4) + return is_first_run + except Exception as e: + io.tool_warning(f"Error checking version: {e}") + if verbose: + io.tool_output(f"Full exception details: {traceback.format_exc()}") + return True + + +async def check_and_load_imports(io, is_first_run, verbose=False): + try: + if is_first_run: + if verbose: + io.tool_output( + "First run for this version and executable, loading imports synchronously" + ) + try: + load_slow_imports(swallow=False) + except Exception as err: + io.tool_error(str(err)) + io.tool_output("Error loading required imports. Did you install cecli properly?") + await io.offer_url( + urls.install_properly, "Open documentation url for more info?", acknowledge=True + ) + sys.exit(1) + if verbose: + io.tool_output("Imports loaded and installs file updated") + else: + if verbose: + io.tool_output("Not first run, loading imports in background thread") + thread = threading.Thread(target=load_slow_imports) + thread.daemon = True + thread.start() + except Exception as e: + io.tool_warning(f"Error in loading imports: {e}") + if verbose: + io.tool_output(f"Full exception details: {traceback.format_exc()}") + + +def load_slow_imports(swallow=True): + try: + import httpx # noqa + import litellm # noqa + import numpy # noqa + except Exception as e: + if not swallow: + raise e + + +async def graceful_exit(coder=None, exit_code=0): + sys.settrace(None) + if coder: + if hasattr(coder, "_autosave_future"): + await coder._autosave_future + for server in coder.mcp_servers: + try: + await server.exit_stack.aclose() + except Exception: + pass + return exit_code + + +if __name__ == "__main__": + status = main() + sys.exit(status) diff --git a/aider/mcp/__init__.py b/cecli/mcp/__init__.py similarity index 91% rename from aider/mcp/__init__.py rename to cecli/mcp/__init__.py index eea87122003..f40c27af978 100644 --- a/aider/mcp/__init__.py +++ b/cecli/mcp/__init__.py @@ -1,7 +1,7 @@ import json from pathlib import Path -from aider.mcp.server import HttpStreamingServer, McpServer, SseServer +from cecli.mcp.server import HttpStreamingServer, McpServer, SseServer def _parse_mcp_servers_from_json_string(json_string, io, verbose=False, mcp_transport="stdio"): @@ -42,7 +42,7 @@ def _parse_mcp_servers_from_json_string(json_string, io, verbose=False, mcp_tran def _resolve_mcp_config_path(file_path, io, verbose=False): - """Resolve MCP config file path relative to closest aider.conf.yml, git directory, or CWD.""" + """Resolve MCP config file path relative to closest cecli.conf.yml, git directory, or CWD.""" if not file_path: return None @@ -51,22 +51,22 @@ def _resolve_mcp_config_path(file_path, io, verbose=False): if path.is_absolute() or path.exists(): return str(path.resolve()) - # Search for the closest aider.conf.yml in parent directories + # Search for the closest cecli.conf.yml in parent directories current_dir = Path.cwd() - aider_conf_path = None + conf_path = None for parent in [current_dir] + list(current_dir.parents): - conf_file = parent / ".aider.conf.yml" + conf_file = parent / ".cecli.conf.yml" if conf_file.exists(): - aider_conf_path = parent + conf_path = parent break - # If aider.conf.yml found, try relative to that directory - if aider_conf_path: - resolved_path = aider_conf_path / file_path + # If cecli.conf.yml found, try relative to that directory + if conf_path: + resolved_path = conf_path / file_path if resolved_path.exists(): if verbose: - io.tool_output(f"Resolved MCP config relative to aider.conf.yml: {resolved_path}") + io.tool_output(f"Resolved MCP config relative to cecli.conf.yml: {resolved_path}") return str(resolved_path.resolve()) # Try to find git root directory @@ -102,7 +102,7 @@ def _parse_mcp_servers_from_file(file_path, io, verbose=False, mcp_transport="st """Parse MCP servers from a JSON file.""" servers = [] - # Resolve the file path relative to closest aider.conf.yml, git directory, or CWD + # Resolve the file path relative to closest cecli.conf.yml, git directory, or CWD resolved_file_path = _resolve_mcp_config_path(file_path, io, verbose) try: @@ -169,6 +169,6 @@ def load_mcp_servers(mcp_servers, mcp_servers_file, io, verbose=False, mcp_trans # and maybe it is actually prompt_toolkit's fault # but this hack works swimmingly because ??? # so sure! why not - servers = [McpServer(json.loads('{"aider_default": {}}'))] + servers = [McpServer(json.loads('{"cecli_default": {}}'))] return servers diff --git a/aider/mcp/server.py b/cecli/mcp/server.py similarity index 96% rename from aider/mcp/server.py rename to cecli/mcp/server.py index c2b3e52a483..9d4162fb666 100644 --- a/aider/mcp/server.py +++ b/cecli/mcp/server.py @@ -11,7 +11,7 @@ class McpServer: """ - A client for MCP servers that provides tools to Aider coders. An McpServer class + A client for MCP servers that provides tools to cecli coders. An McpServer class is initialized per configured MCP Server Uses the mcp library to create and initialize ClientSession objects. @@ -51,8 +51,8 @@ async def connect(self): ) try: - os.makedirs(".aider/logs/", exist_ok=True) - with open(".aider/logs/mcp-errors.log", "w") as err_file: + os.makedirs(".cecli/logs/", exist_ok=True) + with open(".cecli/logs/mcp-errors.log", "w") as err_file: stdio_transport = await self.exit_stack.enter_async_context( stdio_client(server_params, errlog=err_file) ) diff --git a/aider/mdstream.py b/cecli/mdstream.py similarity index 98% rename from aider/mdstream.py rename to cecli/mdstream.py index 774b247c2be..679ab2c2254 100755 --- a/aider/mdstream.py +++ b/cecli/mdstream.py @@ -11,7 +11,7 @@ from rich.syntax import Syntax from rich.text import Text -from aider.dump import dump # noqa: F401 +from cecli.dump import dump # noqa: F401 _text_prefix = """ # Header @@ -229,7 +229,7 @@ def find_minimal_suffix(self, text, match_lines=50): if __name__ == "__main__": - with open("aider/io.py", "r") as f: + with open("cecli/io.py", "r") as f: code = f.read() _text = _text_prefix + code + _text_suffix _text = _text * 10 diff --git a/aider/models.py b/cecli/models.py similarity index 82% rename from aider/models.py rename to cecli/models.py index 537226e2a27..04c035ab0c8 100644 --- a/aider/models.py +++ b/cecli/models.py @@ -15,22 +15,20 @@ import yaml from PIL import Image -from aider import __version__ -from aider.dump import dump # noqa: F401 -from aider.helpers.model_providers import ModelProviderManager -from aider.helpers.requests import model_request_parser -from aider.llm import litellm -from aider.sendchat import sanity_check_messages -from aider.utils import check_pip_install_extra +from cecli import __version__ +from cecli.dump import dump +from cecli.helpers.file_searcher import handle_core_files +from cecli.helpers.model_providers import ModelProviderManager +from cecli.helpers.requests import model_request_parser +from cecli.llm import litellm +from cecli.sendchat import sanity_check_messages +from cecli.utils import check_pip_install_extra RETRY_TIMEOUT = 60 COPY_PASTE_PREFIX = "cp:" - request_timeout = 600 - DEFAULT_MODEL_NAME = "gpt-4o" ANTHROPIC_BETA_HEADER = "prompt-caching-2024-07-31,pdfs-2024-09-25" - OPENAI_MODELS = """ o1 o1-preview @@ -61,9 +59,7 @@ gpt-3.5-turbo-16k gpt-3.5-turbo-16k-0613 """ - OPENAI_MODELS = [ln.strip() for ln in OPENAI_MODELS.splitlines() if ln.strip()] - ANTHROPIC_MODELS = """ claude-2 claude-2.1 @@ -76,23 +72,17 @@ claude-sonnet-4-20250514 claude-opus-4-20250514 """ - ANTHROPIC_MODELS = [ln.strip() for ln in ANTHROPIC_MODELS.splitlines() if ln.strip()] - -# Mapping of model aliases to their canonical names MODEL_ALIASES = { - # Claude models "sonnet": "anthropic/claude-sonnet-4-20250514", "haiku": "claude-3-5-haiku-20241022", "opus": "claude-opus-4-20250514", - # GPT models "4": "gpt-4-0613", "4o": "gpt-4o", "4-turbo": "gpt-4-1106-preview", "35turbo": "gpt-3.5-turbo", "35-turbo": "gpt-3.5-turbo", "3": "gpt-3.5-turbo", - # Other models "deepseek": "deepseek/deepseek-chat", "flash": "gemini/gemini-2.5-flash", "flash-lite": "gemini/gemini-2.5-flash-lite", @@ -105,12 +95,10 @@ "grok3": "xai/grok-3-beta", "optimus": "openrouter/openrouter/optimus-alpha", } -# Model metadata loaded from resources and user's files. @dataclass class ModelSettings: - # Model class needs to have each of these as well name: str edit_format: str = "diff" weak_model_name: Optional[str] = None @@ -129,37 +117,31 @@ class ModelSettings: editor_model_name: Optional[str] = None editor_edit_format: Optional[str] = None reasoning_tag: Optional[str] = None - remove_reasoning: Optional[str] = None # Deprecated alias for reasoning_tag + remove_reasoning: Optional[str] = None system_prompt_prefix: Optional[str] = None accepts_settings: Optional[list] = None -# Load model settings from package resource MODEL_SETTINGS = [] -with importlib.resources.open_text("aider.resources", "model-settings.yml") as f: +with importlib.resources.open_text("cecli.resources", "model-settings.yml") as f: model_settings_list = yaml.safe_load(f) for model_settings_dict in model_settings_list: MODEL_SETTINGS.append(ModelSettings(**model_settings_dict)) class ModelInfoManager: - MODEL_INFO_URL = ( - "https://raw.githubusercontent.com/BerriAI/litellm/main/" - "model_prices_and_context_window.json" - ) - CACHE_TTL = 60 * 60 * 24 # 24 hours + MODEL_INFO_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json" + CACHE_TTL = 60 * 60 * 24 def __init__(self): - self.cache_dir = Path.home() / ".aider" / "caches" + self.cache_dir = handle_core_files(Path.home() / ".cecli" / "caches") self.cache_file = self.cache_dir / "model_prices_and_context_window.json" self.content = None self.local_model_metadata = {} self.verify_ssl = True self._cache_loaded = False - - # Manager for provider-specific cached model databases self.provider_manager = ModelProviderManager() - self.openai_provider_manager = self.provider_manager # Backwards compatibility alias + self.openai_provider_manager = self.provider_manager def set_verify_ssl(self, verify_ssl): self.verify_ssl = verify_ssl @@ -168,7 +150,6 @@ def set_verify_ssl(self, verify_ssl): def _load_cache(self): if self._cache_loaded: return - try: self.cache_dir.mkdir(parents=True, exist_ok=True) if self.cache_file.exists(): @@ -177,18 +158,15 @@ def _load_cache(self): try: self.content = json.loads(self.cache_file.read_text()) except json.JSONDecodeError: - # If the cache file is corrupted, treat it as missing self.content = None except OSError: pass - self._cache_loaded = True def _update_cache(self): try: import requests - # Respect the --no-verify-ssl switch response = requests.get(self.MODEL_INFO_URL, timeout=5, verify=self.verify_ssl) if response.status_code == 200: self.content = response.json() @@ -199,7 +177,6 @@ def _update_cache(self): except Exception as ex: print(str(ex)) try: - # Save empty dict to cache file on failure self.cache_file.write_text("{}") except OSError: pass @@ -208,31 +185,23 @@ def get_model_from_cached_json_db(self, model): data = self.local_model_metadata.get(model) if data: return data - - # Ensure cache is loaded before checking content self._load_cache() - if not self.content: self._update_cache() - if not self.content: return dict() - info = self.content.get(model, dict()) if info: return info - pieces = model.split("/") if len(pieces) == 2: info = self.content.get(pieces[1]) if info and info.get("litellm_provider") == pieces[0]: return info - return dict() def get_model_info(self, model): cached_info = self.get_model_from_cached_json_db(model) - litellm_info = None if litellm._lazy_module or not cached_info: try: @@ -240,36 +209,29 @@ def get_model_info(self, model): except Exception as ex: if "model_prices_and_context_window.json" not in str(ex): print(str(ex)) - provider_info = self._resolve_via_provider(model, cached_info) if provider_info: return provider_info - if litellm_info: return litellm_info - return cached_info def _resolve_via_provider(self, model, cached_info): if cached_info: return None - provider = model.split("/", 1)[0] if "/" in model else None if not self.provider_manager.supports_provider(provider): return None - provider_info = self.provider_manager.get_model_info(model) if provider_info: self._record_dynamic_model(model, provider_info) return provider_info - if provider == "openrouter": openrouter_info = self.fetch_openrouter_model_info(model) if openrouter_info: openrouter_info.setdefault("litellm_provider", "openrouter") self._record_dynamic_model(model, openrouter_info) return openrouter_info - return None def _record_dynamic_model(self, model, info): @@ -301,19 +263,21 @@ def fetch_openrouter_model_info(self, model): import re if re.search( - rf"The model\s*.*{re.escape(url_part)}.* is not available", html, re.IGNORECASE + f"The model\\s*.*{re.escape(url_part)}.* is not available", html, re.IGNORECASE ): - print(f"\033[91mError: Model '{url_part}' is not available\033[0m") + print(f"\x1b[91mError: Model '{url_part}' is not available\x1b[0m") return {} - text = re.sub(r"<[^>]+>", " ", html) - context_match = re.search(r"([\d,]+)\s*context", text) + text = re.sub("<[^>]+>", " ", html) + context_match = re.search("([\\d,]+)\\s*context", text) if context_match: context_str = context_match.group(1).replace(",", "") context_size = int(context_str) else: context_size = None - input_cost_match = re.search(r"\$\s*([\d.]+)\s*/M input tokens", text, re.IGNORECASE) - output_cost_match = re.search(r"\$\s*([\d.]+)\s*/M output tokens", text, re.IGNORECASE) + input_cost_match = re.search("\\$\\s*([\\d.]+)\\s*/M input tokens", text, re.IGNORECASE) + output_cost_match = re.search( + "\\$\\s*([\\d.]+)\\s*/M output tokens", text, re.IGNORECASE + ) input_cost = float(input_cost_match.group(1)) / 1000000 if input_cost_match else None output_cost = float(output_cost_match.group(1)) / 1000000 if output_cost_match else None if context_size is None or input_cost is None or output_cost is None: @@ -346,61 +310,43 @@ def __init__( io=None, override_kwargs=None, ): - # Determine copy/paste mode and map model aliases provided_model = model or "" if isinstance(provided_model, Model): provided_model = provided_model.name elif not isinstance(provided_model, str): provided_model = str(provided_model) - self.io = io self.verbose = verbose self.override_kwargs = override_kwargs or {} - self.copy_paste_mode = False self.copy_paste_transport = "api" - if provided_model.startswith(COPY_PASTE_PREFIX): model = provided_model.removeprefix(COPY_PASTE_PREFIX) self.enable_copy_paste_mode(transport="clipboard") else: model = provided_model - model = MODEL_ALIASES.get(model, model) - self.name = model - self.max_chat_history_tokens = 1024 self.weak_model = None self.editor_model = None - - # Find the extra settings self.extra_model_settings = next( - (ms for ms in MODEL_SETTINGS if ms.name == "aider/extra_params"), None + (ms for ms in MODEL_SETTINGS if ms.name == "cecli/extra_params"), None ) - self.info = self.get_model_info(model) self.litellm_provider = (self.info.get("litellm_provider") or "").lower() - - # Are all needed keys/params available? res = self.validate_environment() self.missing_keys = res.get("missing_keys") self.keys_in_environment = res.get("keys_in_environment") - max_input_tokens = self.info.get("max_input_tokens") or 0 - # Calculate max_chat_history_tokens as 1/16th of max_input_tokens, - # with minimum 1k and maximum 8k self.max_chat_history_tokens = min(max(max_input_tokens / 16, 1024), 8192) - self.configure_model_settings(model) self._apply_provider_defaults() self.get_weak_model(weak_model) - if editor_model is False: self.editor_model_name = None else: self.get_editor_model(editor_model, editor_edit_format) - if self.copy_paste_transport == "clipboard": self.streaming = False @@ -412,52 +358,33 @@ def _copy_fields(self, source): for field in fields(ModelSettings): val = getattr(source, field.name) setattr(self, field.name, val) - - # Handle backward compatibility: if remove_reasoning is set but reasoning_tag isn't, - # use remove_reasoning's value for reasoning_tag if self.reasoning_tag is None and self.remove_reasoning is not None: self.reasoning_tag = self.remove_reasoning def configure_model_settings(self, model): - # Look for exact model match exact_match = False for ms in MODEL_SETTINGS: - # direct match, or match "provider/" if model == ms.name: self._copy_fields(ms) exact_match = True - break # Continue to apply overrides - - # Initialize accepts_settings if it's None + break if self.accepts_settings is None: self.accepts_settings = [] - model = model.lower() - - # If no exact match, try generic settings if not exact_match: self.apply_generic_model_settings(model) - - # Apply override settings last if they exist if ( self.extra_model_settings and self.extra_model_settings.extra_params - and self.extra_model_settings.name == "aider/extra_params" + and self.extra_model_settings.name == "cecli/extra_params" ): - # Initialize extra_params if it doesn't exist if not self.extra_params: self.extra_params = {} - - # Deep merge the extra_params dicts for key, value in self.extra_model_settings.extra_params.items(): if isinstance(value, dict) and isinstance(self.extra_params.get(key), dict): - # For nested dicts, merge recursively self.extra_params[key] = {**self.extra_params[key], **value} else: - # For non-dict values, simply update self.extra_params[key] = value - - # Ensure OpenRouter models accept thinking_tokens and reasoning_effort if self.name.startswith("openrouter/"): if self.accepts_settings is None: self.accepts_settings = [] @@ -465,20 +392,13 @@ def configure_model_settings(self, model): self.accepts_settings.append("thinking_tokens") if "reasoning_effort" not in self.accepts_settings: self.accepts_settings.append("reasoning_effort") - - # Apply override kwargs from model-overrides configuration if self.override_kwargs: - # Initialize extra_params if it doesn't exist if not self.extra_params: self.extra_params = {} - - # Deep merge the override kwargs for key, value in self.override_kwargs.items(): if isinstance(value, dict) and isinstance(self.extra_params.get(key), dict): - # For nested dicts, merge recursively self.extra_params[key] = {**self.extra_params[key], **value} else: - # For non-dict values, simply update self.extra_params[key] = value def apply_generic_model_settings(self, model): @@ -490,43 +410,37 @@ def apply_generic_model_settings(self, model): self.system_prompt_prefix = "Formatting re-enabled. " if "reasoning_effort" not in self.accepts_settings: self.accepts_settings.append("reasoning_effort") - return # <-- - + return if "gpt-4.1-mini" in model: self.edit_format = "diff" self.use_repo_map = True self.reminder = "sys" self.examples_as_sys_msg = False - return # <-- - + return if "gpt-4.1" in model: self.edit_format = "diff" self.use_repo_map = True self.reminder = "sys" self.examples_as_sys_msg = False - return # <-- - + return last_segment = model.split("/")[-1] if last_segment in ("gpt-5", "gpt-5-2025-08-07") or "gpt-5.1" in model: self.use_temperature = False self.edit_format = "diff" if "reasoning_effort" not in self.accepts_settings: self.accepts_settings.append("reasoning_effort") - return # <-- - + return if "/o1-mini" in model: self.use_repo_map = True self.use_temperature = False self.use_system_prompt = False - return # <-- - + return if "/o1-preview" in model: self.edit_format = "diff" self.use_repo_map = True self.use_temperature = False self.use_system_prompt = False - return # <-- - + return if "/o1" in model: self.edit_format = "diff" self.use_repo_map = True @@ -535,46 +449,39 @@ def apply_generic_model_settings(self, model): self.system_prompt_prefix = "Formatting re-enabled. " if "reasoning_effort" not in self.accepts_settings: self.accepts_settings.append("reasoning_effort") - return # <-- - + return if "deepseek" in model and "v3" in model: self.edit_format = "diff" self.use_repo_map = True self.reminder = "sys" self.examples_as_sys_msg = True - return # <-- - + return if "deepseek" in model and ("r1" in model or "reasoning" in model): self.edit_format = "diff" self.use_repo_map = True self.examples_as_sys_msg = True self.use_temperature = False self.reasoning_tag = "think" - return # <-- - + return if ("llama3" in model or "llama-3" in model) and "70b" in model: self.edit_format = "diff" self.use_repo_map = True self.send_undo_reply = True self.examples_as_sys_msg = True - return # <-- - - if "gpt-4-turbo" in model or ("gpt-4-" in model and "-preview" in model): + return + if "gpt-4-turbo" in model or "gpt-4-" in model and "-preview" in model: self.edit_format = "udiff" self.use_repo_map = True self.send_undo_reply = True - return # <-- - + return if "gpt-4" in model or "claude-3-opus" in model: self.edit_format = "diff" self.use_repo_map = True self.send_undo_reply = True - return # <-- - + return if "gpt-3.5" in model or "gpt-4" in model: self.reminder = "sys" - return # <-- - + return if "3-7-sonnet" in model: self.edit_format = "diff" self.use_repo_map = True @@ -582,20 +489,17 @@ def apply_generic_model_settings(self, model): self.reminder = "user" if "thinking_tokens" not in self.accepts_settings: self.accepts_settings.append("thinking_tokens") - return # <-- - + return if "3.5-sonnet" in model or "3-5-sonnet" in model: self.edit_format = "diff" self.use_repo_map = True self.examples_as_sys_msg = True self.reminder = "user" - return # <-- - + return if model.startswith("o1-") or "/o1-" in model: self.use_system_prompt = False self.use_temperature = False - return # <-- - + return if ( "qwen" in model and "coder" in model @@ -605,8 +509,7 @@ def apply_generic_model_settings(self, model): self.edit_format = "diff" self.editor_edit_format = "editor-diff" self.use_repo_map = True - return # <-- - + return if "qwq" in model and "32b" in model and "preview" not in model: self.edit_format = "diff" self.editor_edit_format = "editor-diff" @@ -615,8 +518,7 @@ def apply_generic_model_settings(self, model): self.examples_as_sys_msg = True self.use_temperature = 0.6 self.extra_params = dict(top_p=0.95) - return # <-- - + return if "qwen3" in model: self.edit_format = "diff" self.use_repo_map = True @@ -629,12 +531,10 @@ def apply_generic_model_settings(self, model): self.use_temperature = 0.6 self.reasoning_tag = "think" self.extra_params = {"top_p": 0.95, "top_k": 20, "min_p": 0.0} - return # <-- - - # use the defaults + return if self.edit_format == "diff": self.use_repo_map = True - return # <-- + return def __str__(self): return self.name @@ -644,40 +544,27 @@ def enable_copy_paste_mode(self, *, transport="api"): self.copy_paste_transport = transport def get_weak_model(self, provided_weak_model): - # If provided_weak_model is False, set weak_model to self if provided_weak_model is False: self.weak_model = self self.weak_model_name = None return - if self.copy_paste_transport == "clipboard": self.weak_model = self self.weak_model_name = None return - - # If provided_weak_model is already a Model object, use it directly if isinstance(provided_weak_model, Model): self.weak_model = provided_weak_model self.weak_model_name = provided_weak_model.name return - - # If weak_model_name is provided as a string, override the model settings if provided_weak_model: self.weak_model_name = provided_weak_model - if not self.weak_model_name: self.weak_model = self return - if self.weak_model_name == self.name: self.weak_model = self return - - self.weak_model = Model( - self.weak_model_name, - weak_model=False, - io=self.io, - ) + self.weak_model = Model(self.weak_model_name, weak_model=False, io=self.io) return self.weak_model def commit_message_models(self): @@ -688,33 +575,21 @@ def get_editor_model(self, provided_editor_model, editor_edit_format): provided_editor_model = False self.editor_model_name = self.name self.editor_model = self - - # If provided_editor_model is already a Model object, use it directly if isinstance(provided_editor_model, Model): self.editor_model = provided_editor_model self.editor_model_name = provided_editor_model.name - else: - # If editor_model_name is provided as a string, override the model settings - if provided_editor_model: - self.editor_model_name = provided_editor_model - + elif provided_editor_model: + self.editor_model_name = provided_editor_model if editor_edit_format: self.editor_edit_format = editor_edit_format - if not self.editor_model_name or self.editor_model_name == self.name: self.editor_model = self else: - self.editor_model = Model( - self.editor_model_name, - editor_model=False, - io=self.io, - ) - + self.editor_model = Model(self.editor_model_name, editor_model=False, io=self.io) if not self.editor_edit_format: self.editor_edit_format = self.editor_model.edit_format if self.editor_edit_format in ("diff", "whole", "diff-fenced"): self.editor_edit_format = "editor-" + self.editor_edit_format - return self.editor_model def _ensure_extra_params_dict(self): @@ -726,35 +601,23 @@ def _ensure_extra_params_dict(self): def _apply_provider_defaults(self): provider = (self.info.get("litellm_provider") or "").lower() self.litellm_provider = provider or None - if not provider: return - provider_config = model_info_manager.provider_manager.get_provider_config(provider) if not provider_config: return - self._ensure_extra_params_dict() self.extra_params.setdefault("custom_llm_provider", provider) - if provider_config.get("supports_stream") is False: - # Some OpenAI-compatible providers (e.g., Synthetic) only expose the - # non-streaming /chat/completions endpoint, so forcing streaming would - # loop through LiteLLM's fallback and explode mid-response. Disable the - # streaming flag up front so the caller transparently falls back to - # standard completions for those providers. self.streaming = False - base_url = model_info_manager.provider_manager.get_provider_base_url(provider) if base_url: self.extra_params.setdefault("base_url", base_url) - default_headers = provider_config.get("default_headers") or {} if default_headers: headers = self.extra_params.setdefault("extra_headers", {}) for key, value in default_headers.items(): headers.setdefault(key, value) - provider_extra = provider_config.get("extra_params") or {} for key, value in provider_extra.items(): if key not in self.extra_params: @@ -766,21 +629,17 @@ def tokenizer(self, text): def token_count(self, messages): if isinstance(messages, dict): messages = [messages] - if isinstance(messages, list): try: return litellm.token_counter(model=self.name, messages=messages) except Exception: - pass # fall back to raw tokenizer - + pass if not self.tokenizer: return 0 - if isinstance(messages, str): msgs = messages else: msgs = json.dumps(messages) - try: return len(self.tokenizer(msgs)) except Exception as err: @@ -795,26 +654,18 @@ def token_count_for_image(self, fname): :return: The token cost for the image. """ width, height = self.get_image_size(fname) - - # If the image is larger than 2048 in any dimension, scale it down to fit within 2048x2048 max_dimension = max(width, height) if max_dimension > 2048: scale_factor = 2048 / max_dimension width = int(width * scale_factor) height = int(height * scale_factor) - - # Scale the image such that the shortest side is 768 pixels long min_dimension = min(width, height) scale_factor = 768 / min_dimension width = int(width * scale_factor) height = int(height * scale_factor) - - # Calculate the number of 512x512 tiles needed to cover the image tiles_width = math.ceil(width / 512) tiles_height = math.ceil(height / 512) num_tiles = tiles_width * tiles_height - - # Each tile costs 170 tokens, and there's an additional fixed cost of 85 tokens token_cost = num_tiles * 170 + 85 return token_cost @@ -829,15 +680,12 @@ def get_image_size(self, fname): def fast_validate_environment(self): """Fast path for common models. Avoids forcing litellm import.""" - model = self.name - pieces = model.split("/") if len(pieces) > 1: provider = pieces[0] else: provider = None - keymap = dict( openrouter="OPENROUTER_API_KEY", openai="OPENAI_API_KEY", @@ -854,10 +702,8 @@ def fast_validate_environment(self): var = "ANTHROPIC_API_KEY" else: var = keymap.get(provider) - if var and os.environ.get(var): return dict(keys_in_environment=[var], missing_keys=[]) - if not var and provider and model_info_manager.provider_manager.supports_provider(provider): provider_keys = model_info_manager.provider_manager.get_required_api_keys(provider) for env_var in provider_keys: @@ -868,13 +714,8 @@ def validate_environment(self): res = self.fast_validate_environment() if res: return res - - # https://github.com/BerriAI/litellm/issues/3190 - model = self.name res = litellm.validate_environment(model) - - # If missing AWS credential keys but AWS_PROFILE is set, consider AWS credentials valid if res["missing_keys"] and any( key in ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"] for key in res["missing_keys"] ): @@ -887,12 +728,10 @@ def validate_environment(self): ] if not res["missing_keys"]: res["keys_in_environment"] = True - if res["keys_in_environment"]: return res if res["missing_keys"]: return res - provider = self.info.get("litellm_provider", "").lower() provider_config = model_info_manager.provider_manager.get_provider_config(provider) if provider_config: @@ -908,7 +747,6 @@ def validate_environment(self): return validate_variables(["GEMINI_API_KEY"]) if provider == "groq": return validate_variables(["GROQ_API_KEY"]) - return res def get_repo_map_tokens(self): @@ -949,12 +787,9 @@ def parse_token_value(self, value): """ if isinstance(value, int): return value - if not isinstance(value, str): - return int(value) # Try to convert to int - + return int(value) value = value.strip().upper() - if value.endswith("K"): multiplier = 1024 value = value[:-1] @@ -963,8 +798,6 @@ def parse_token_value(self, value): value = value[:-1] else: multiplier = 1 - - # Convert to float first to handle decimal values like "10.5k" return int(float(value) * multiplier) def set_thinking_tokens(self, value): @@ -978,29 +811,22 @@ def set_thinking_tokens(self, value): self.use_temperature = False if not self.extra_params: self.extra_params = {} - - # OpenRouter models use 'reasoning' instead of 'thinking' if self.name.startswith("openrouter/"): if "extra_body" not in self.extra_params: self.extra_params["extra_body"] = {} if num_tokens > 0: self.extra_params["extra_body"]["reasoning"] = {"max_tokens": num_tokens} - else: - if "reasoning" in self.extra_params["extra_body"]: - del self.extra_params["extra_body"]["reasoning"] - else: - if num_tokens > 0: - self.extra_params["thinking"] = {"type": "enabled", "budget_tokens": num_tokens} - else: - if "thinking" in self.extra_params: - del self.extra_params["thinking"] + elif "reasoning" in self.extra_params["extra_body"]: + del self.extra_params["extra_body"]["reasoning"] + elif num_tokens > 0: + self.extra_params["thinking"] = {"type": "enabled", "budget_tokens": num_tokens} + elif "thinking" in self.extra_params: + del self.extra_params["thinking"] def get_raw_thinking_tokens(self): """Get formatted thinking token budget if available""" budget = None - if self.extra_params: - # Check for OpenRouter reasoning format if self.name.startswith("openrouter/"): if ( "extra_body" in self.extra_params @@ -1008,19 +834,15 @@ def get_raw_thinking_tokens(self): and "max_tokens" in self.extra_params["extra_body"]["reasoning"] ): budget = self.extra_params["extra_body"]["reasoning"]["max_tokens"] - # Check for standard thinking format elif ( "thinking" in self.extra_params and "budget_tokens" in self.extra_params["thinking"] ): budget = self.extra_params["thinking"]["budget_tokens"] - return budget def get_thinking_tokens(self): budget = self.get_raw_thinking_tokens() - if budget is not None: - # Format as xx.yK for thousands, xx.yM for millions if budget >= 1024 * 1024: value = budget / (1024 * 1024) if value == int(value): @@ -1038,7 +860,6 @@ def get_thinking_tokens(self): def get_reasoning_effort(self): """Get reasoning effort value if available""" if self.extra_params: - # Check for OpenRouter reasoning format if self.name.startswith("openrouter/"): if ( "extra_body" in self.extra_params @@ -1046,7 +867,6 @@ def get_reasoning_effort(self): and "effort" in self.extra_params["extra_body"]["reasoning"] ): return self.extra_params["extra_body"]["reasoning"]["effort"] - # Check for standard reasoning_effort format (e.g. in extra_body) elif ( "extra_body" in self.extra_params and "reasoning_effort" in self.extra_params["extra_body"] @@ -1072,142 +892,93 @@ def is_ollama(self): async def send_completion( self, messages, functions, stream, temperature=None, tools=None, max_tokens=None ): - if os.environ.get("AIDER_SANITY_CHECK_TURNS"): + if os.environ.get("CECLISANITY_CHECK_TURNS"): sanity_check_messages(messages) - messages = model_request_parser(self, messages) - if self.verbose: for message in messages: msg_role = message.get("role") msg_content = message.get("content") if message.get("content") else "" msg_trunc = "" - if message.get("content"): msg_trunc = message.get("content")[:30] - print(f"{msg_role} ({len(msg_content)}): {msg_trunc}") - kwargs = dict(model=self.name, stream=stream) - if self.use_temperature is not False: if temperature is None: if isinstance(self.use_temperature, bool): temperature = 0 else: temperature = float(self.use_temperature) - kwargs["temperature"] = temperature - - # `tools` is for modern tool usage. `functions` is for legacy/forced calls. - # This handles `base_coder` sending both with same content for `agent_coder`. effective_tools = tools - if effective_tools is None and functions: - # Convert legacy `functions` to `tools` format if `tools` isn't provided. effective_tools = [dict(type="function", function=f) for f in functions] - if effective_tools: kwargs["tools"] = effective_tools - - # Forcing a function call is for legacy style `functions` with a single function. - # This is used by ArchitectCoder and not intended for AgentCoder's tools. if functions and len(functions) == 1: function = functions[0] - if "name" in function: tool_name = function.get("name") if tool_name: kwargs["tool_choice"] = {"type": "function", "function": {"name": tool_name}} - if self.extra_params: kwargs.update(self.extra_params) - if max_tokens: kwargs["max_tokens"] = max_tokens - if "max_tokens" in kwargs and kwargs["max_tokens"]: kwargs["max_completion_tokens"] = kwargs.pop("max_tokens") if self.is_ollama() and "num_ctx" not in kwargs: num_ctx = int(self.token_count(messages) * 1.25) + 8192 kwargs["num_ctx"] = num_ctx - key = json.dumps(kwargs, sort_keys=True).encode() - # dump(kwargs) - hash_object = hashlib.sha1(key) if "timeout" not in kwargs: kwargs["timeout"] = request_timeout if self.verbose: dump(kwargs) kwargs["messages"] = messages - if not self.is_anthropic(): - # Per this: https://github.com/BerriAI/litellm/issues/10226 - # The first and second to last messages are cache optimal - # Since caches are also written to incrementally and you need - # the past and current states to properly append and gain - # efficiencies/savings in cache writing kwargs["cache_control_injection_points"] = [ - { - "location": "message", - "role": "system", - }, - { - "location": "message", - "index": -1, - }, - { - "location": "message", - "index": -2, - }, + {"location": "message", "role": "system"}, + {"location": "message", "index": -1}, + {"location": "message", "index": -2}, ] - - # Are we using github copilot? if "GITHUB_COPILOT_TOKEN" in os.environ or self.name.startswith("github_copilot/"): if "extra_headers" not in kwargs: kwargs["extra_headers"] = { - "Editor-Version": f"aider/{__version__}", + "Editor-Version": f"cecli/{__version__}", "Copilot-Integration-Id": "vscode-chat", } - try: res = await litellm.acompletion(**kwargs) except Exception as err: print(f"LiteLLM API Error: {str(err)}") res = self.model_error_response() - if self.verbose: print(f"LiteLLM API Error: {str(err)}") raise - return hash_object, res async def simple_send_with_retries(self, messages, max_tokens=None): - from aider.exceptions import LiteLLMExceptions + from cecli.exceptions import LiteLLMExceptions litellm_ex = LiteLLMExceptions() messages = model_request_parser(self, messages) retry_delay = 0.125 - if self.verbose: dump(messages) - while True: try: _hash, response = await self.send_completion( - messages=messages, - functions=None, - stream=False, - max_tokens=max_tokens, + messages=messages, functions=None, stream=False, max_tokens=max_tokens ) if not response or not hasattr(response, "choices") or not response.choices: return None res = response.choices[0].message.content - from aider.reasoning_tags import remove_reasoning_content + from cecli.reasoning_tags import remove_reasoning_content return remove_reasoning_content(res, self.reasoning_tag) - except litellm_ex.exceptions_tuple() as err: ex_info = litellm_ex.get_ex_info(err) print(str(err)) @@ -1236,7 +1007,7 @@ async def model_error_response(self): index=0, message=litellm.Message( content="Model API Response Error. Please retry the previous request" - ), # Provide an empty message object + ), ) ], model=self.name, @@ -1248,25 +1019,18 @@ def register_models(model_settings_fnames): for model_settings_fname in model_settings_fnames: if not os.path.exists(model_settings_fname): continue - if not Path(model_settings_fname).read_text().strip(): continue - try: with open(model_settings_fname, "r") as model_settings_file: model_settings_list = yaml.safe_load(model_settings_file) - for model_settings_dict in model_settings_list: model_settings = ModelSettings(**model_settings_dict) - - # Remove all existing settings for this model name MODEL_SETTINGS[:] = [ms for ms in MODEL_SETTINGS if ms.name != model_settings.name] - # Add the new settings MODEL_SETTINGS.append(model_settings) except Exception as e: raise Exception(f"Error loading model settings from {model_settings_fname}: {e}") files_loaded.append(model_settings_fname) - return files_loaded @@ -1275,7 +1039,6 @@ def register_litellm_models(model_fnames): for model_fname in model_fnames: if not os.path.exists(model_fname): continue - try: data = Path(model_fname).read_text() if not data.strip(): @@ -1283,14 +1046,10 @@ def register_litellm_models(model_fnames): model_def = json.loads(data) if not model_def: continue - - # Defer registration with litellm to faster path. model_info_manager.local_model_metadata.update(model_def) except Exception as e: raise Exception(f"Error loading model definition from {model_fname}: {e}") - files_loaded.append(model_fname) - return files_loaded @@ -1306,11 +1065,9 @@ def validate_variables(vars): async def sanity_check_models(io, main_model): problem_main = await sanity_check_model(io, main_model) - problem_weak = None if main_model.weak_model and main_model.weak_model is not main_model: problem_weak = await sanity_check_model(io, main_model.weak_model) - problem_editor = None if ( main_model.editor_model @@ -1318,16 +1075,13 @@ async def sanity_check_models(io, main_model): and main_model.editor_model is not main_model.weak_model ): problem_editor = await sanity_check_model(io, main_model.editor_model) - return problem_main or problem_weak or problem_editor async def sanity_check_model(io, model): if getattr(model, "copy_paste_transport", "api") == "clipboard": return False - show = False - if model.missing_keys: show = True io.tool_warning(f"Warning: {model} expects these environment variables") @@ -1335,32 +1089,25 @@ async def sanity_check_model(io, model): value = os.environ.get(key, "") status = "Set" if value else "Not set" io.tool_output(f"- {key}: {status}") - if platform.system() == "Windows": io.tool_output( "Note: You may need to restart your terminal or command prompt for `setx` to take" " effect." ) - elif not model.keys_in_environment: show = True io.tool_warning(f"Warning for {model}: Unknown which environment variables are required.") - - # Check for model-specific dependencies await check_for_dependencies(io, model.name) - if not model.info: show = True io.tool_warning( f"Warning for {model}: Unknown context window size and costs, using sane defaults." ) - possible_matches = fuzzy_match_models(model.name) if possible_matches: io.tool_output("Did you mean one of these?") for match in possible_matches: io.tool_output(f"- {match}") - return show @@ -1372,13 +1119,10 @@ async def check_for_dependencies(io, model_name): io: The IO object for user interaction model_name: The name of the model to check dependencies for """ - # Check if this is a Bedrock model and ensure boto3 is installed if model_name.startswith("bedrock/"): await check_pip_install_extra( io, "boto3", "AWS Bedrock models require the boto3 package.", ["boto3"] ) - - # Check if this is a Vertex AI model and ensure google-cloud-aiplatform is installed elif model_name.startswith("vertex_ai/"): await check_pip_install_extra( io, @@ -1392,10 +1136,8 @@ def get_chat_model_names(): chat_models = set() model_metadata = list(litellm.model_cost.items()) model_metadata += list(model_info_manager.local_model_metadata.items()) - openai_provider_models = model_info_manager.provider_manager.get_models_for_listing() model_metadata += list(openai_provider_models.items()) - for orig_model, attrs in model_metadata: if attrs.get("mode") != "chat": continue @@ -1407,33 +1149,18 @@ def get_chat_model_names(): else: fq_model = f"{provider}/{orig_model}" chat_models.add(fq_model) - chat_models.add(orig_model) - return sorted(chat_models) def fuzzy_match_models(name): name = name.lower() - chat_models = get_chat_model_names() - # exactly matching model - # matching_models = [ - # (fq,m) for fq,m in chat_models - # if name == fq or name == m - # ] - # if matching_models: - # return matching_models - - # Check for model names containing the name matching_models = [m for m in chat_models if name in m.lower()] if matching_models: return sorted(set(matching_models)) - - # Check for slight misspellings models = set(chat_models) matching_models = difflib.get_close_matches(name, models, n=3, cutoff=0.8) - return sorted(set(matching_models)) @@ -1453,32 +1180,24 @@ def get_model_settings_as_yaml(): import yaml model_settings_list = [] - # Add default settings first with all field values defaults = {} for field in fields(ModelSettings): defaults[field.name] = field.default defaults["name"] = "(default values)" model_settings_list.append(defaults) - - # Sort model settings by name for ms in sorted(MODEL_SETTINGS, key=lambda x: x.name): - # Create dict with explicit field order model_settings_dict = {} for field in fields(ModelSettings): value = getattr(ms, field.name) if value != field.default: model_settings_dict[field.name] = value model_settings_list.append(model_settings_dict) - # Add blank line between entries model_settings_list.append(None) - - # Filter out None values before dumping yaml_str = yaml.dump( [ms for ms in model_settings_list if ms is not None], default_flow_style=False, - sort_keys=False, # Preserve field order from dataclass + sort_keys=False, ) - # Add actual blank lines between entries return yaml_str.replace("\n- ", "\n\n- ") @@ -1486,14 +1205,12 @@ def main(): if len(sys.argv) < 2: print("Usage: python models.py or python models.py --yaml") sys.exit(1) - if sys.argv[1] == "--yaml": yaml_string = get_model_settings_as_yaml() print(yaml_string) else: model_name = sys.argv[1] matching_models = fuzzy_match_models(model_name) - if matching_models: print(f"Matching models for '{model_name}':") for model in matching_models: diff --git a/aider/onboarding.py b/cecli/onboarding.py similarity index 69% rename from aider/onboarding.py rename to cecli/onboarding.py index e3660a9a909..72d49ff4c51 100644 --- a/aider/onboarding.py +++ b/cecli/onboarding.py @@ -11,8 +11,8 @@ import requests -from aider import urls -from aider.io import InputOutput +from cecli import urls +from cecli.io import InputOutput def check_openrouter_tier(api_key): @@ -30,14 +30,12 @@ def check_openrouter_tier(api_key): response = requests.get( "https://openrouter.ai/api/v1/auth/key", headers={"Authorization": f"Bearer {api_key}"}, - timeout=5, # Add a reasonable timeout + timeout=5, ) response.raise_for_status() data = response.json() - # According to the documentation, 'is_free_tier' will be true if the user has never paid - return data.get("data", {}).get("is_free_tier", True) # Default to True if not found + return data.get("data", {}).get("is_free_tier", True) except Exception: - # If there's any error, we'll default to assuming free tier return True @@ -49,17 +47,13 @@ def try_to_select_default_model(): Returns: The name of the selected model, or None if no suitable default is found. """ - # Special handling for OpenRouter openrouter_key = os.environ.get("OPENROUTER_API_KEY") if openrouter_key: - # Check if the user is on a free tier is_free_tier = check_openrouter_tier(openrouter_key) if is_free_tier: return "openrouter/deepseek/deepseek-r1:free" else: return "openrouter/anthropic/claude-sonnet-4" - - # Select model based on other available API keys model_key_pairs = [ ("ANTHROPIC_API_KEY", "sonnet"), ("DEEPSEEK_API_KEY", "deepseek"), @@ -67,12 +61,10 @@ def try_to_select_default_model(): ("GEMINI_API_KEY", "gemini/gemini-2.5-pro-exp-03-25"), ("VERTEXAI_PROJECT", "vertex_ai/gemini-2.5-pro-exp-03-25"), ] - for env_key, model_name in model_key_pairs: api_key_value = os.environ.get(env_key) if api_key_value: return model_name - return None @@ -86,26 +78,15 @@ async def offer_openrouter_oauth(io): Returns: True if authentication was successful, False otherwise. """ - # No API keys found - Offer OpenRouter OAuth io.tool_output("OpenRouter provides free and paid access to many LLMs.") - # Use confirm_ask which handles non-interactive cases if await io.confirm_ask( - "Login to OpenRouter or create a free account?", - default="y", - acknowledge=True, + "Login to OpenRouter or create a free account?", default="y", acknowledge=True ): openrouter_key = start_openrouter_oauth_flow(io) if openrouter_key: - # Successfully got key via OAuth, use the default OpenRouter model - # Ensure OPENROUTER_API_KEY is now set in the environment for later use os.environ["OPENROUTER_API_KEY"] = openrouter_key return True - - # OAuth failed or was cancelled by user implicitly (e.g., closing browser) - # Error messages are handled within start_openrouter_oauth_flow io.tool_error("OpenRouter authentication did not complete successfully.") - # Fall through to the final error message - return False @@ -122,41 +103,30 @@ async def select_default_model(args, io): The name of the selected model, or None if no suitable default is found. """ if args.model: - return args.model # Model already specified - + return args.model model = try_to_select_default_model() if model: io.tool_warning(f"Using {model} model with API key from environment.") return model - no_model_msg = "No LLM model was specified and no API keys were provided." io.tool_warning(no_model_msg) - - # Try OAuth if no model was detected await offer_openrouter_oauth(io) - - # Check again after potential OAuth success model = try_to_select_default_model() if model: return model - await io.offer_url(urls.models_and_keys, "Open documentation URL for more info?") -# Helper function to find an available port def find_available_port(start_port=8484, end_port=8584): for port in range(start_port, end_port + 1): try: - # Check if the port is available by trying to bind to it with socketserver.TCPServer(("localhost", port), None): return port except OSError: - # Port is likely already in use continue return None -# PKCE code generation def generate_pkce_codes(): code_verifier = secrets.token_urlsafe(64) hasher = hashlib.sha256() @@ -165,20 +135,15 @@ def generate_pkce_codes(): return code_verifier, code_challenge -# Function to exchange the authorization code for an API key def exchange_code_for_key(code, code_verifier, io): try: response = requests.post( "https://openrouter.ai/api/v1/auth/keys", headers={"Content-Type": "application/json"}, - json={ - "code": code, - "code_verifier": code_verifier, - "code_challenge_method": "S256", - }, - timeout=30, # Add a timeout + json={"code": code, "code_verifier": code_verifier, "code_challenge_method": "S256"}, + timeout=30, ) - response.raise_for_status() # Raise exception for bad status codes (4xx or 5xx) + response.raise_for_status() data = response.json() api_key = data.get("key") if not api_key: @@ -204,17 +169,14 @@ def exchange_code_for_key(code, code_verifier, io): return None -# Function to start the OAuth flow def start_openrouter_oauth_flow(io): """Initiates the OpenRouter OAuth PKCE flow using a local server.""" - port = find_available_port() if not port: io.tool_error("Could not find an available port between 8484 and 8584.") io.tool_error("Please ensure a port in this range is free, or configure manually.") return None - - callback_url = f"http://localhost:{port}/callback/aider" + callback_url = f"http://localhost:{port}/callback/cecli" auth_code = None server_error = None server_started = threading.Event() @@ -224,7 +186,7 @@ class OAuthCallbackHandler(http.server.SimpleHTTPRequestHandler): def do_GET(self): nonlocal auth_code, server_error parsed_path = urlparse(self.path) - if parsed_path.path == "/callback/aider": + if parsed_path.path == "/callback/cecli": query_params = parse_qs(parsed_path.query) if "code" in query_params: auth_code = query_params["code"][0] @@ -232,29 +194,21 @@ def do_GET(self): self.send_header("Content-type", "text/html") self.end_headers() self.wfile.write( - b"

Success!

" - b"

Aider has received the authentication code. " - b"You can close this browser tab.

" + b"

Success!

cecli has received the authentication" + b" code. You can close this browser tab.

" ) - # Signal the main thread to shut down the server - # Signal the main thread to shut down the server shutdown_server.set() else: - # Redirect to aider website if 'code' is missing (e.g., user visited manually) - self.send_response(302) # Found (temporary redirect) + self.send_response(302) self.send_header("Location", urls.website) self.end_headers() - # No need to set server_error, just redirect. - # Do NOT shut down the server here; wait for timeout or success. else: - # Redirect anything else (e.g., favicon.ico) to the main website as well self.send_response(302) self.send_header("Location", urls.website) self.end_headers() self.wfile.write(b"Not Found") def log_message(self, format, *args): - # Suppress server logging to keep terminal clean pass def run_server(): @@ -262,37 +216,28 @@ def run_server(): try: with socketserver.TCPServer(("localhost", port), OAuthCallbackHandler) as httpd: io.tool_output(f"Temporary server listening on {callback_url}", log_only=True) - server_started.set() # Signal that the server is ready - # Wait until shutdown is requested or timeout occurs (handled by main thread) + server_started.set() while not shutdown_server.is_set(): - httpd.handle_request() # Handle one request at a time - # Add a small sleep to prevent busy-waiting if needed, - # though handle_request should block appropriately. + httpd.handle_request() time.sleep(0.1) io.tool_output("Shutting down temporary server.", log_only=True) except Exception as e: server_error = f"Failed to start or run temporary server: {e}" - server_started.set() # Signal even if failed, error will be checked - shutdown_server.set() # Ensure shutdown logic proceeds + server_started.set() + shutdown_server.set() server_thread = threading.Thread(target=run_server, daemon=True) server_thread.start() - - # Wait briefly for the server to start, or for an error if not server_started.wait(timeout=5): io.tool_error("Temporary authentication server failed to start in time.") - shutdown_server.set() # Ensure thread exits if it eventually starts + shutdown_server.set() server_thread.join(timeout=1) return None - - # Check if server failed during startup if server_error: io.tool_error(server_error) - shutdown_server.set() # Ensure thread exits + shutdown_server.set() server_thread.join(timeout=1) return None - - # Generate codes and URL code_verifier, code_challenge = generate_pkce_codes() auth_url_base = "https://openrouter.ai/auth" auth_params = { @@ -301,69 +246,48 @@ def run_server(): "code_challenge_method": "S256", } auth_url = f"{auth_url_base}?{'&'.join(f'{k}={v}' for k, v in auth_params.items())}" - - io.tool_output("\nPlease open this URL in your browser to connect Aider with OpenRouter:") + io.tool_output("\nPlease open this URL in your browser to connect cecli with OpenRouter:") io.tool_output() print(auth_url) - MINUTES = 5 io.tool_output(f"\nWaiting up to {MINUTES} minutes for you to finish in the browser...") io.tool_output("Use Control-C to interrupt.") - try: webbrowser.open(auth_url) except Exception: pass - - # Wait for the callback to set the auth_code or for timeout/error interrupted = False try: - shutdown_server.wait(timeout=MINUTES * 60) # Convert minutes to seconds + shutdown_server.wait(timeout=MINUTES * 60) except KeyboardInterrupt: io.tool_warning("\nOAuth flow interrupted.") interrupted = True - # Ensure the server thread is signaled to shut down shutdown_server.set() - - # Join the server thread to ensure it's cleaned up server_thread.join(timeout=1) - if interrupted: - return None # Return None if interrupted by user - + return None if server_error: io.tool_error(f"Authentication failed: {server_error}") return None - if not auth_code: io.tool_error("Authentication with OpenRouter failed.") return None - io.tool_output("Completing authentication...") - - # Exchange code for key api_key = exchange_code_for_key(auth_code, code_verifier, io) - if api_key: - # Set env var for the current session immediately os.environ["OPENROUTER_API_KEY"] = api_key - - # Save the key to the oauth-keys.env file try: - config_dir = os.path.expanduser("~/.aider") + config_dir = os.path.expanduser("~/.cecli") os.makedirs(config_dir, exist_ok=True) key_file = os.path.join(config_dir, "oauth-keys.env") with open(key_file, "a", encoding="utf-8") as f: f.write(f'OPENROUTER_API_KEY="{api_key}"\n') - - io.tool_warning("Aider will load the OpenRouter key automatically in future sessions.") + io.tool_warning("cecli will load the OpenRouter key automatically in future sessions.") io.tool_output() - return api_key except Exception as e: io.tool_error(f"Successfully obtained key, but failed to save it to file: {e}") io.tool_warning("Set OPENROUTER_API_KEY environment variable for this session only.") - # Still return the key for the current session even if saving failed return api_key else: io.tool_error("Authentication with OpenRouter failed.") @@ -373,8 +297,6 @@ def run_server(): def main(): """Main function to test the OpenRouter OAuth flow.""" print("Starting OpenRouter OAuth flow test...") - - # Use a real IO object for interaction io = InputOutput( pretty=True, yes=False, @@ -383,22 +305,14 @@ def main(): tool_output_color="BLUE", tool_error_color="RED", ) - - # Ensure OPENROUTER_API_KEY is not set, to trigger the flow naturally - # (though start_openrouter_oauth_flow doesn't check this itself) if "OPENROUTER_API_KEY" in os.environ: print("Warning: OPENROUTER_API_KEY is already set in environment.") - # del os.environ["OPENROUTER_API_KEY"] # Optionally unset it for testing - api_key = start_openrouter_oauth_flow(io) - if api_key: print("\nOAuth flow completed successfully!") print(f"Obtained API Key (first 5 chars): {api_key[:5]}...") - # Be careful printing the key, even partially else: print("\nOAuth flow failed or was cancelled.") - print("\nOpenRouter OAuth flow test finished.") diff --git a/aider/prompts/__init__.py b/cecli/prompts/__init__.py similarity index 100% rename from aider/prompts/__init__.py rename to cecli/prompts/__init__.py diff --git a/aider/prompts/agent.yml b/cecli/prompts/agent.yml similarity index 100% rename from aider/prompts/agent.yml rename to cecli/prompts/agent.yml diff --git a/aider/prompts/architect.yml b/cecli/prompts/architect.yml similarity index 100% rename from aider/prompts/architect.yml rename to cecli/prompts/architect.yml diff --git a/aider/prompts/ask.yml b/cecli/prompts/ask.yml similarity index 100% rename from aider/prompts/ask.yml rename to cecli/prompts/ask.yml diff --git a/aider/prompts/base.yml b/cecli/prompts/base.yml similarity index 100% rename from aider/prompts/base.yml rename to cecli/prompts/base.yml diff --git a/aider/prompts/context.yml b/cecli/prompts/context.yml similarity index 100% rename from aider/prompts/context.yml rename to cecli/prompts/context.yml diff --git a/aider/prompts/copypaste.yml b/cecli/prompts/copypaste.yml similarity index 100% rename from aider/prompts/copypaste.yml rename to cecli/prompts/copypaste.yml diff --git a/aider/prompts/editblock.yml b/cecli/prompts/editblock.yml similarity index 100% rename from aider/prompts/editblock.yml rename to cecli/prompts/editblock.yml diff --git a/aider/prompts/editblock_fenced.yml b/cecli/prompts/editblock_fenced.yml similarity index 100% rename from aider/prompts/editblock_fenced.yml rename to cecli/prompts/editblock_fenced.yml diff --git a/aider/prompts/editblock_func.yml b/cecli/prompts/editblock_func.yml similarity index 100% rename from aider/prompts/editblock_func.yml rename to cecli/prompts/editblock_func.yml diff --git a/aider/prompts/editor_diff_fenced.yml b/cecli/prompts/editor_diff_fenced.yml similarity index 100% rename from aider/prompts/editor_diff_fenced.yml rename to cecli/prompts/editor_diff_fenced.yml diff --git a/aider/prompts/editor_editblock.yml b/cecli/prompts/editor_editblock.yml similarity index 100% rename from aider/prompts/editor_editblock.yml rename to cecli/prompts/editor_editblock.yml diff --git a/aider/prompts/editor_whole.yml b/cecli/prompts/editor_whole.yml similarity index 100% rename from aider/prompts/editor_whole.yml rename to cecli/prompts/editor_whole.yml diff --git a/aider/prompts/help.yml b/cecli/prompts/help.yml similarity index 66% rename from aider/prompts/help.yml rename to cecli/prompts/help.yml index 0597bf76aa1..c8a682f6826 100644 --- a/aider/prompts/help.yml +++ b/cecli/prompts/help.yml @@ -13,20 +13,20 @@ files_no_full_files_with_repo_map: '' files_no_full_files_with_repo_map_reply: '' main_system: | - You are an expert on the AI coding tool called Aider. - Answer the user's questions about how to use aider. - The user is currently chatting with you using aider, to write and edit code. - Use the provided aider documentation *if it is relevant to the user's question*. - Include a bulleted list of urls to the aider docs that might be relevant for the user to read. + You are an expert on the AI coding tool called cecli. + Answer the user's questions about how to use cecli. + The user is currently chatting with you using cecli, to write and edit code. + Use the provided cecli documentation *if it is relevant to the user's question*. + Include a bulleted list of urls to the cecli docs that might be relevant for the user to read. Include *bare* urls. *Do not* make [markdown links](http://...). For example: - - https://aider.chat/docs/usage.html - - https://aider.chat/docs/faq.html - If you don't know the answer, say so and suggest some relevant aider doc urls. - If asks for something that isn't possible with aider, be clear about that. + - https://cecli.dev/docs/usage.html + - https://cecli.dev/docs/faq.html + If you don't know the answer, say so and suggest some relevant cecli doc urls. + If asks for something that isn't possible with cecli, be clear about that. Don't suggest a solution that isn't supported. Be helpful but concise. - Unless the question indicates otherwise, assume the user wants to use aider as a CLI tool. + Unless the question indicates otherwise, assume the user wants to use cecli as a CLI tool. Keep this info about the user's system in mind: {platform} diff --git a/aider/prompts/patch.yml b/cecli/prompts/patch.yml similarity index 100% rename from aider/prompts/patch.yml rename to cecli/prompts/patch.yml diff --git a/aider/prompts/single_wholefile_func.yml b/cecli/prompts/single_wholefile_func.yml similarity index 100% rename from aider/prompts/single_wholefile_func.yml rename to cecli/prompts/single_wholefile_func.yml diff --git a/aider/prompts/udiff.yml b/cecli/prompts/udiff.yml similarity index 100% rename from aider/prompts/udiff.yml rename to cecli/prompts/udiff.yml diff --git a/aider/prompts/udiff_simple.yml b/cecli/prompts/udiff_simple.yml similarity index 100% rename from aider/prompts/udiff_simple.yml rename to cecli/prompts/udiff_simple.yml diff --git a/aider/prompts/utils/__init__.py b/cecli/prompts/utils/__init__.py similarity index 100% rename from aider/prompts/utils/__init__.py rename to cecli/prompts/utils/__init__.py diff --git a/aider/prompts/utils/prompt_registry.py b/cecli/prompts/utils/prompt_registry.py similarity index 100% rename from aider/prompts/utils/prompt_registry.py rename to cecli/prompts/utils/prompt_registry.py diff --git a/aider/prompts/utils/system.py b/cecli/prompts/utils/system.py similarity index 100% rename from aider/prompts/utils/system.py rename to cecli/prompts/utils/system.py diff --git a/aider/prompts/wholefile.yml b/cecli/prompts/wholefile.yml similarity index 100% rename from aider/prompts/wholefile.yml rename to cecli/prompts/wholefile.yml diff --git a/aider/prompts/wholefile_func.yml b/cecli/prompts/wholefile_func.yml similarity index 100% rename from aider/prompts/wholefile_func.yml rename to cecli/prompts/wholefile_func.yml diff --git a/aider/queries/tree-sitter-language-pack/README.md b/cecli/queries/tree-sitter-language-pack/README.md similarity index 100% rename from aider/queries/tree-sitter-language-pack/README.md rename to cecli/queries/tree-sitter-language-pack/README.md diff --git a/aider/queries/tree-sitter-language-pack/arduino-tags.scm b/cecli/queries/tree-sitter-language-pack/arduino-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/arduino-tags.scm rename to cecli/queries/tree-sitter-language-pack/arduino-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/c-tags.scm b/cecli/queries/tree-sitter-language-pack/c-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/c-tags.scm rename to cecli/queries/tree-sitter-language-pack/c-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/chatito-tags.scm b/cecli/queries/tree-sitter-language-pack/chatito-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/chatito-tags.scm rename to cecli/queries/tree-sitter-language-pack/chatito-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/clojure-tags.scm b/cecli/queries/tree-sitter-language-pack/clojure-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/clojure-tags.scm rename to cecli/queries/tree-sitter-language-pack/clojure-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/commonlisp-tags.scm b/cecli/queries/tree-sitter-language-pack/commonlisp-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/commonlisp-tags.scm rename to cecli/queries/tree-sitter-language-pack/commonlisp-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/cpp-tags.scm b/cecli/queries/tree-sitter-language-pack/cpp-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/cpp-tags.scm rename to cecli/queries/tree-sitter-language-pack/cpp-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/csharp-tags.scm b/cecli/queries/tree-sitter-language-pack/csharp-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/csharp-tags.scm rename to cecli/queries/tree-sitter-language-pack/csharp-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/d-tags.scm b/cecli/queries/tree-sitter-language-pack/d-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/d-tags.scm rename to cecli/queries/tree-sitter-language-pack/d-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/dart-tags.scm b/cecli/queries/tree-sitter-language-pack/dart-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/dart-tags.scm rename to cecli/queries/tree-sitter-language-pack/dart-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/elisp-tags.scm b/cecli/queries/tree-sitter-language-pack/elisp-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/elisp-tags.scm rename to cecli/queries/tree-sitter-language-pack/elisp-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/elixir-tags.scm b/cecli/queries/tree-sitter-language-pack/elixir-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/elixir-tags.scm rename to cecli/queries/tree-sitter-language-pack/elixir-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/elm-tags.scm b/cecli/queries/tree-sitter-language-pack/elm-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/elm-tags.scm rename to cecli/queries/tree-sitter-language-pack/elm-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/gleam-tags.scm b/cecli/queries/tree-sitter-language-pack/gleam-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/gleam-tags.scm rename to cecli/queries/tree-sitter-language-pack/gleam-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/go-tags.scm b/cecli/queries/tree-sitter-language-pack/go-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/go-tags.scm rename to cecli/queries/tree-sitter-language-pack/go-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/java-tags.scm b/cecli/queries/tree-sitter-language-pack/java-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/java-tags.scm rename to cecli/queries/tree-sitter-language-pack/java-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/javascript-tags.scm b/cecli/queries/tree-sitter-language-pack/javascript-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/javascript-tags.scm rename to cecli/queries/tree-sitter-language-pack/javascript-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/lua-tags.scm b/cecli/queries/tree-sitter-language-pack/lua-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/lua-tags.scm rename to cecli/queries/tree-sitter-language-pack/lua-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/matlab-tags.scm b/cecli/queries/tree-sitter-language-pack/matlab-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/matlab-tags.scm rename to cecli/queries/tree-sitter-language-pack/matlab-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/ocaml-tags.scm b/cecli/queries/tree-sitter-language-pack/ocaml-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/ocaml-tags.scm rename to cecli/queries/tree-sitter-language-pack/ocaml-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/ocaml_interface-tags.scm b/cecli/queries/tree-sitter-language-pack/ocaml_interface-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/ocaml_interface-tags.scm rename to cecli/queries/tree-sitter-language-pack/ocaml_interface-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/pony-tags.scm b/cecli/queries/tree-sitter-language-pack/pony-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/pony-tags.scm rename to cecli/queries/tree-sitter-language-pack/pony-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/properties-tags.scm b/cecli/queries/tree-sitter-language-pack/properties-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/properties-tags.scm rename to cecli/queries/tree-sitter-language-pack/properties-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/python-tags.scm b/cecli/queries/tree-sitter-language-pack/python-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/python-tags.scm rename to cecli/queries/tree-sitter-language-pack/python-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/r-tags.scm b/cecli/queries/tree-sitter-language-pack/r-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/r-tags.scm rename to cecli/queries/tree-sitter-language-pack/r-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/racket-tags.scm b/cecli/queries/tree-sitter-language-pack/racket-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/racket-tags.scm rename to cecli/queries/tree-sitter-language-pack/racket-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/ruby-tags.scm b/cecli/queries/tree-sitter-language-pack/ruby-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/ruby-tags.scm rename to cecli/queries/tree-sitter-language-pack/ruby-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/rust-tags.scm b/cecli/queries/tree-sitter-language-pack/rust-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/rust-tags.scm rename to cecli/queries/tree-sitter-language-pack/rust-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/solidity-tags.scm b/cecli/queries/tree-sitter-language-pack/solidity-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/solidity-tags.scm rename to cecli/queries/tree-sitter-language-pack/solidity-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/swift-tags.scm b/cecli/queries/tree-sitter-language-pack/swift-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/swift-tags.scm rename to cecli/queries/tree-sitter-language-pack/swift-tags.scm diff --git a/aider/queries/tree-sitter-language-pack/udev-tags.scm b/cecli/queries/tree-sitter-language-pack/udev-tags.scm similarity index 100% rename from aider/queries/tree-sitter-language-pack/udev-tags.scm rename to cecli/queries/tree-sitter-language-pack/udev-tags.scm diff --git a/aider/queries/tree-sitter-languages/README.md b/cecli/queries/tree-sitter-languages/README.md similarity index 100% rename from aider/queries/tree-sitter-languages/README.md rename to cecli/queries/tree-sitter-languages/README.md diff --git a/aider/queries/tree-sitter-languages/c-tags.scm b/cecli/queries/tree-sitter-languages/c-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/c-tags.scm rename to cecli/queries/tree-sitter-languages/c-tags.scm diff --git a/aider/queries/tree-sitter-languages/c_sharp-tags.scm b/cecli/queries/tree-sitter-languages/c_sharp-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/c_sharp-tags.scm rename to cecli/queries/tree-sitter-languages/c_sharp-tags.scm diff --git a/aider/queries/tree-sitter-languages/cpp-tags.scm b/cecli/queries/tree-sitter-languages/cpp-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/cpp-tags.scm rename to cecli/queries/tree-sitter-languages/cpp-tags.scm diff --git a/aider/queries/tree-sitter-languages/dart-tags.scm b/cecli/queries/tree-sitter-languages/dart-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/dart-tags.scm rename to cecli/queries/tree-sitter-languages/dart-tags.scm diff --git a/aider/queries/tree-sitter-languages/elisp-tags.scm b/cecli/queries/tree-sitter-languages/elisp-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/elisp-tags.scm rename to cecli/queries/tree-sitter-languages/elisp-tags.scm diff --git a/aider/queries/tree-sitter-languages/elixir-tags.scm b/cecli/queries/tree-sitter-languages/elixir-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/elixir-tags.scm rename to cecli/queries/tree-sitter-languages/elixir-tags.scm diff --git a/aider/queries/tree-sitter-languages/elm-tags.scm b/cecli/queries/tree-sitter-languages/elm-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/elm-tags.scm rename to cecli/queries/tree-sitter-languages/elm-tags.scm diff --git a/aider/queries/tree-sitter-languages/fortran-tags.scm b/cecli/queries/tree-sitter-languages/fortran-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/fortran-tags.scm rename to cecli/queries/tree-sitter-languages/fortran-tags.scm diff --git a/aider/queries/tree-sitter-languages/go-tags.scm b/cecli/queries/tree-sitter-languages/go-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/go-tags.scm rename to cecli/queries/tree-sitter-languages/go-tags.scm diff --git a/aider/queries/tree-sitter-languages/haskell-tags.scm b/cecli/queries/tree-sitter-languages/haskell-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/haskell-tags.scm rename to cecli/queries/tree-sitter-languages/haskell-tags.scm diff --git a/aider/queries/tree-sitter-languages/hcl-tags.scm b/cecli/queries/tree-sitter-languages/hcl-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/hcl-tags.scm rename to cecli/queries/tree-sitter-languages/hcl-tags.scm diff --git a/aider/queries/tree-sitter-languages/java-tags.scm b/cecli/queries/tree-sitter-languages/java-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/java-tags.scm rename to cecli/queries/tree-sitter-languages/java-tags.scm diff --git a/aider/queries/tree-sitter-languages/javascript-tags.scm b/cecli/queries/tree-sitter-languages/javascript-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/javascript-tags.scm rename to cecli/queries/tree-sitter-languages/javascript-tags.scm diff --git a/aider/queries/tree-sitter-languages/julia-tags.scm b/cecli/queries/tree-sitter-languages/julia-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/julia-tags.scm rename to cecli/queries/tree-sitter-languages/julia-tags.scm diff --git a/aider/queries/tree-sitter-languages/kotlin-tags.scm b/cecli/queries/tree-sitter-languages/kotlin-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/kotlin-tags.scm rename to cecli/queries/tree-sitter-languages/kotlin-tags.scm diff --git a/aider/queries/tree-sitter-languages/matlab-tags.scm b/cecli/queries/tree-sitter-languages/matlab-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/matlab-tags.scm rename to cecli/queries/tree-sitter-languages/matlab-tags.scm diff --git a/aider/queries/tree-sitter-languages/ocaml-tags.scm b/cecli/queries/tree-sitter-languages/ocaml-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/ocaml-tags.scm rename to cecli/queries/tree-sitter-languages/ocaml-tags.scm diff --git a/aider/queries/tree-sitter-languages/ocaml_interface-tags.scm b/cecli/queries/tree-sitter-languages/ocaml_interface-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/ocaml_interface-tags.scm rename to cecli/queries/tree-sitter-languages/ocaml_interface-tags.scm diff --git a/aider/queries/tree-sitter-languages/php-tags.scm b/cecli/queries/tree-sitter-languages/php-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/php-tags.scm rename to cecli/queries/tree-sitter-languages/php-tags.scm diff --git a/aider/queries/tree-sitter-languages/python-tags.scm b/cecli/queries/tree-sitter-languages/python-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/python-tags.scm rename to cecli/queries/tree-sitter-languages/python-tags.scm diff --git a/aider/queries/tree-sitter-languages/ql-tags.scm b/cecli/queries/tree-sitter-languages/ql-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/ql-tags.scm rename to cecli/queries/tree-sitter-languages/ql-tags.scm diff --git a/aider/queries/tree-sitter-languages/ruby-tags.scm b/cecli/queries/tree-sitter-languages/ruby-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/ruby-tags.scm rename to cecli/queries/tree-sitter-languages/ruby-tags.scm diff --git a/aider/queries/tree-sitter-languages/rust-tags.scm b/cecli/queries/tree-sitter-languages/rust-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/rust-tags.scm rename to cecli/queries/tree-sitter-languages/rust-tags.scm diff --git a/aider/queries/tree-sitter-languages/scala-tags.scm b/cecli/queries/tree-sitter-languages/scala-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/scala-tags.scm rename to cecli/queries/tree-sitter-languages/scala-tags.scm diff --git a/aider/queries/tree-sitter-languages/typescript-tags.scm b/cecli/queries/tree-sitter-languages/typescript-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/typescript-tags.scm rename to cecli/queries/tree-sitter-languages/typescript-tags.scm diff --git a/aider/queries/tree-sitter-languages/zig-tags.scm b/cecli/queries/tree-sitter-languages/zig-tags.scm similarity index 100% rename from aider/queries/tree-sitter-languages/zig-tags.scm rename to cecli/queries/tree-sitter-languages/zig-tags.scm diff --git a/aider/reasoning_tags.py b/cecli/reasoning_tags.py similarity index 98% rename from aider/reasoning_tags.py rename to cecli/reasoning_tags.py index e87922383eb..b7e7153009a 100644 --- a/aider/reasoning_tags.py +++ b/cecli/reasoning_tags.py @@ -2,7 +2,7 @@ import re -from aider.dump import dump # noqa +from cecli.dump import dump # noqa # Standard tag identifier REASONING_TAG = "thinking-content-" + "7bbeb8e1441453ad999a0bbba8a46d4b" diff --git a/aider/repo.py b/cecli/repo.py similarity index 89% rename from aider/repo.py rename to cecli/repo.py index e319787562a..44c90ec9c28 100644 --- a/aider/repo.py +++ b/cecli/repo.py @@ -18,8 +18,8 @@ import pathspec -import aider.prompts.utils.system as prompts -from aider import utils +import cecli.prompts.utils.system as prompts +from cecli import utils from .dump import dump # noqa: F401 @@ -51,10 +51,10 @@ def set_git_env(var_name, value, original_value): class GitRepo: repo = None - aider_ignore_file = None - aider_ignore_spec = None - aider_ignore_ts = 0 - aider_ignore_last_check = 0 + cecli_ignore_file = None + cecli_ignore_spec = None + cecli_ignore_ts = 0 + cecli_ignore_last_check = 0 subtree_only = False ignore_file_cache = {} git_repo_error = None @@ -64,7 +64,7 @@ def __init__( io, fnames, git_dname, - aider_ignore_file=None, + cecli_ignore_file=None, models=None, attribute_author=True, attribute_committer=True, @@ -125,10 +125,10 @@ def __init__( self.repo = git.Repo(repo_paths.pop(), odbt=git.GitDB) self.root = utils.safe_abs_path(self.repo.working_tree_dir) - if aider_ignore_file: - self.aider_ignore_file = Path(aider_ignore_file) + if cecli_ignore_file: + self.cecli_ignore_file = Path(cecli_ignore_file) - async def commit(self, fnames=None, context=None, message=None, aider_edits=False, coder=None): + async def commit(self, fnames=None, context=None, message=None, coder_edits=False, coder=None): """ Commit the specified files or all dirty files if none are specified. @@ -137,7 +137,7 @@ async def commit(self, fnames=None, context=None, message=None, aider_edits=Fals dirty files). context (str, optional): Context for generating commit message. Defaults to None. message (str, optional): Explicit commit message. Defaults to None (generate message). - aider_edits (bool, optional): Whether the changes were made by Aider. Defaults to False. + coder_edits (bool, optional): Whether the changes were made by cecli. Defaults to False. This affects attribution logic. coder (Coder, optional): The Coder instance, used for config and model info. Defaults to None. @@ -149,27 +149,27 @@ async def commit(self, fnames=None, context=None, message=None, aider_edits=Fals Attribution Logic: ------------------ This method handles Git commit attribution based on configuration flags and whether - Aider generated the changes (`aider_edits`). + cecli generated the changes (`coder_edits`). Key Concepts: - Author: The person who originally wrote the code changes. - Committer: The person who last applied the commit to the repository. - - aider_edits=True: Changes were generated by Aider-CE (LLM). - - aider_edits=False: Commit is user-driven (e.g., /commit manually staged changes). + - coder_edits=True: Changes were generated by cecli (LLM). + - coder_edits=False: Commit is user-driven (e.g., /commit manually staged changes). - Explicit Setting: A flag (--attribute-...) is set to True or False via command line or config file. - Implicit Default: A flag is not explicitly set, defaulting to None in args, which is interpreted as True unless overridden by other logic. Flags: - - --attribute-author: Modify Author name to "User Name (aider-ce)". - - --attribute-committer: Modify Committer name to "User Name (aider-ce)". + - --attribute-author: Modify Author name to "User Name (cecli)". + - --attribute-committer: Modify Committer name to "User Name (cecli)". - --attribute-co-authored-by: Add - "Co-authored-by: aider-ce ()" trailer to commit message. + "Co-authored-by: cecli ()" trailer to commit message. Behavior Summary: - 1. When aider_edits = True (AI Changes): + 1. When coder_edits = True (AI Changes): - If --attribute-co-authored-by=True: - Co-authored-by trailer IS ADDED. - Author/Committer names are NOT modified by default (co-authored-by takes precedence). @@ -181,21 +181,21 @@ async def commit(self, fnames=None, context=None, message=None, aider_edits=Fals - EXCEPTION: If --attribute-author/--attribute-committer is EXPLICITLY False, the respective name is NOT modified. - 2. When aider_edits = False (User Changes): + 2. When coder_edits = False (User Changes): - --attribute-co-authored-by is IGNORED (trailer never added). - Author name is NEVER modified (--attribute-author ignored). - - Committer name IS modified by default (implicit True, as Aider-CE runs `git commit`). + - Committer name IS modified by default (implicit True, as cecli runs `git commit`). - EXCEPTION: If --attribute-committer is EXPLICITLY False, the name is NOT modified. Resulting Scenarios: - - Standard AI edit (defaults): Co-authored-by=False -> Author=You(aider-ce), - Committer=You(aider-ce) + - Standard AI edit (defaults): Co-authored-by=False -> Author=You(cecli), + Committer=You(cecli) - AI edit with Co-authored-by (default): Co-authored-by=True -> Author=You, Committer=You, Trailer added - AI edit with Co-authored-by + Explicit Author: Co-authored-by=True, - --attribute-author -> Author=You(aider-ce), Committer=You, Trailer added - - User commit (defaults): aider_edits=False -> Author=You, Committer=You(aider-ce) - - User commit with explicit no-committer: aider_edits=False, + --attribute-author -> Author=You(cecli), Committer=You, Trailer added + - User commit (defaults): coder_edits=False -> Author=You, Committer=You(cecli) + - User commit with explicit no-committer: coder_edits=False, --no-attribute-committer -> Author=You, Committer=You """ if not fnames and not self.repo.is_dirty(): @@ -239,38 +239,38 @@ async def commit(self, fnames=None, context=None, message=None, aider_edits=Fals effective_committer = True if attribute_committer is None else attribute_committer # Determine commit message prefixing - prefix_commit_message = aider_edits and ( + prefix_commit_message = coder_edits and ( attribute_commit_message_author or attribute_commit_message_committer ) # Determine Co-authored-by trailer commit_message_trailer = "" - if aider_edits and attribute_co_authored_by: + if coder_edits and attribute_co_authored_by: model_name = "unknown-model" if coder and hasattr(coder, "main_model") and coder.main_model.name: model_name = coder.main_model.name - commit_message_trailer = f"\n\nCo-authored-by: aider-ce ({model_name})" + commit_message_trailer = f"\n\nCo-authored-by: cecli ({model_name})" # Determine if author/committer names should be modified - # Author modification applies only to aider-ce edits. + # Author modification applies only to cecli edits. # It's used if effective_author is True AND # (co-authored-by is False OR author was explicitly set). use_attribute_author = ( - aider_edits and effective_author and (not attribute_co_authored_by or author_explicit) + coder_edits and effective_author and (not attribute_co_authored_by or author_explicit) ) - # Committer modification applies regardless of aider_edits (based on tests). + # Committer modification applies regardless of coder_edits (based on tests). # It's used if effective_committer is True AND - # (it's not an aider-ce edit with co-authored-by OR committer was explicitly set). + # (it's not an cecli edit with co-authored-by OR committer was explicitly set). use_attribute_committer = effective_committer and ( - not (aider_edits and attribute_co_authored_by) or committer_explicit + not (coder_edits and attribute_co_authored_by) or committer_explicit ) if not commit_message: commit_message = "(no commit message provided)" if prefix_commit_message: - commit_message = "aider-ce: " + commit_message + commit_message = "cecli: " + commit_message full_commit_message = commit_message + commit_message_trailer @@ -291,7 +291,7 @@ async def commit(self, fnames=None, context=None, message=None, aider_edits=Fals original_user_name = self.repo.git.config("--get", "user.name") original_committer_name_env = os.environ.get("GIT_COMMITTER_NAME") original_author_name_env = os.environ.get("GIT_AUTHOR_NAME") - committer_name = f"{original_user_name} (aider-ce)" + committer_name = f"{original_user_name} (cecli)" try: # Use context managers to handle environment variables @@ -502,25 +502,25 @@ def normalize_path(self, path): self.normalized_path[orig_path] = path return path - def refresh_aider_ignore(self): - if not self.aider_ignore_file: + def refresh_cecli_ignore(self): + if not self.cecli_ignore_file: return current_time = time.time() - if current_time - self.aider_ignore_last_check < 1: + if current_time - self.cecli_ignore_last_check < 1: return - self.aider_ignore_last_check = current_time + self.cecli_ignore_last_check = current_time - if not self.aider_ignore_file.is_file(): + if not self.cecli_ignore_file.is_file(): return - mtime = self.aider_ignore_file.stat().st_mtime - if mtime != self.aider_ignore_ts: - self.aider_ignore_ts = mtime + mtime = self.cecli_ignore_file.stat().st_mtime + if mtime != self.cecli_ignore_ts: + self.cecli_ignore_ts = mtime self.ignore_file_cache = {} - lines = self.aider_ignore_file.read_text().splitlines() - self.aider_ignore_spec = pathspec.PathSpec.from_lines( + lines = self.cecli_ignore_file.read_text().splitlines() + self.cecli_ignore_spec = pathspec.PathSpec.from_lines( pathspec.patterns.GitWildMatchPattern, lines, ) @@ -535,7 +535,7 @@ def git_ignored_file(self, path): return False def ignored_file(self, fname): - self.refresh_aider_ignore() + self.refresh_cecli_ignore() if fname in self.ignore_file_cache: return self.ignore_file_cache[fname] @@ -559,7 +559,7 @@ def ignored_file_raw(self, fname): if cwd_path not in fname_path.parents and fname_path != cwd_path: return True - if not self.aider_ignore_file or not self.aider_ignore_file.is_file(): + if not self.cecli_ignore_file or not self.cecli_ignore_file.is_file(): return False try: @@ -567,7 +567,7 @@ def ignored_file_raw(self, fname): except ValueError: return True - return self.aider_ignore_spec.match_file(fname) + return self.cecli_ignore_spec.match_file(fname) def path_in_repo(self, path): if not self.repo: diff --git a/aider/repomap.py b/cecli/repomap.py similarity index 99% rename from aider/repomap.py rename to cecli/repomap.py index 7885979ea1b..981d4b2e304 100644 --- a/aider/repomap.py +++ b/cecli/repomap.py @@ -16,14 +16,14 @@ from pygments.lexers import guess_lexer_for_filename from pygments.token import Token -from aider.dump import dump -from aider.helpers.similarity import ( +from cecli.dump import dump +from cecli.helpers.similarity import ( cosine_similarity, create_bigram_vector, normalize_vector, ) -from aider.special import filter_important_files -from aider.tools.utils.helpers import ToolError +from cecli.special import filter_important_files +from cecli.tools.utils.helpers import ToolError # tree_sitter is throwing a FutureWarning warnings.simplefilter("ignore", category=FutureWarning) @@ -83,7 +83,7 @@ def __new__( class RepoMap: - TAGS_CACHE_DIR = f".aider.tags.cache.v{CACHE_VERSION}" + TAGS_CACHE_DIR = f".cecli/tags.cache.v{CACHE_VERSION}" warned_files = set() diff --git a/aider/report.py b/cecli/report.py similarity index 97% rename from aider/report.py rename to cecli/report.py index 0ca8a7fdb9c..e4ae57ad30b 100644 --- a/aider/report.py +++ b/cecli/report.py @@ -6,9 +6,9 @@ import urllib.parse import webbrowser -from aider import __version__ -from aider.urls import github_issues -from aider.versioncheck import VERSION_CHECK_FNAME +from cecli import __version__ +from cecli.urls import github_issues +from cecli.versioncheck import VERSION_CHECK_FNAME # Global variable to store resolved args data for error reporting resolved_args_data = None @@ -95,7 +95,7 @@ def report_github_issue(issue_text, title=None, confirm=True): :param confirm: Whether to ask for confirmation before opening the browser (default: True) :return: None """ - version_info = f"Aider-CE version: {__version__}\n" + version_info = f"cecli version: {__version__}\n" python_version = f"Python version: {sys.version.split()[0]}\n" platform_info = f"Platform: {platform.platform()}\n" python_info = get_python_info() + "\n" @@ -126,7 +126,7 @@ def report_github_issue(issue_text, title=None, confirm=True): print(f"\n# {title}\n") print(issue_text.strip()) print() - print("Please consider reporting this bug to help improve aider!") + print("Please consider reporting this bug to help improve cecli!") prompt = "Open a GitHub Issue pre-filled with the above error in your browser? (Y/n) " confirmation = input(prompt).strip().lower() diff --git a/aider/resources/__init__.py b/cecli/resources/__init__.py similarity index 54% rename from aider/resources/__init__.py rename to cecli/resources/__init__.py index f7ca4efbe55..7a56987d612 100644 --- a/aider/resources/__init__.py +++ b/cecli/resources/__init__.py @@ -1,3 +1,3 @@ -# This ensures that importlib_resources.files("aider.resources") +# This ensures that importlib_resources.files("cecli.resources") # doesn't raise ImportError, even if there are no other files in this # dir. diff --git a/aider/resources/model-metadata.json b/cecli/resources/model-metadata.json similarity index 100% rename from aider/resources/model-metadata.json rename to cecli/resources/model-metadata.json diff --git a/aider/resources/model-settings.yml b/cecli/resources/model-settings.yml similarity index 100% rename from aider/resources/model-settings.yml rename to cecli/resources/model-settings.yml diff --git a/aider/resources/providers.json b/cecli/resources/providers.json similarity index 100% rename from aider/resources/providers.json rename to cecli/resources/providers.json diff --git a/aider/run_cmd.py b/cecli/run_cmd.py similarity index 100% rename from aider/run_cmd.py rename to cecli/run_cmd.py diff --git a/aider/scrape.py b/cecli/scrape.py similarity index 96% rename from aider/scrape.py rename to cecli/scrape.py index f5492bc86dd..2e16c90707f 100755 --- a/aider/scrape.py +++ b/cecli/scrape.py @@ -5,10 +5,10 @@ import pypandoc -from aider import __version__, urls, utils -from aider.dump import dump # noqa: F401 +from cecli import __version__, urls, utils +from cecli.dump import dump # noqa: F401 -aider_user_agent = f"Aider/{__version__} +{urls.website}" +coder_user_agent = f"cecli/{__version__} +{urls.website}" # Playwright is nice because it has a simple way to install dependencies on most # platforms. @@ -51,7 +51,7 @@ async def install_playwright(io): if has_playwright and has_chromium: return True - pip_cmd = utils.get_pip_install(["aider-ce[playwright]"]) + pip_cmd = utils.get_pip_install(["cecli[playwright]"]) chromium_cmd = "-m playwright install --with-deps chromium" chromium_cmd = [sys.executable] + chromium_cmd.split() @@ -172,7 +172,7 @@ async def scrape_with_playwright(self, url): user_agent = await page.evaluate("navigator.userAgent") user_agent = user_agent.replace("Headless", "") user_agent = user_agent.replace("headless", "") - user_agent += " " + aider_user_agent + user_agent += " " + coder_user_agent await page.set_extra_http_headers({"User-Agent": user_agent}) @@ -205,7 +205,7 @@ async def scrape_with_playwright(self, url): def scrape_with_httpx(self, url): import httpx - headers = {"User-Agent": f"Mozilla./5.0 ({aider_user_agent})"} + headers = {"User-Agent": f"Mozilla./5.0 ({coder_user_agent})"} try: with httpx.Client( headers=headers, verify=self.verify_ssl, follow_redirects=True diff --git a/aider/sendchat.py b/cecli/sendchat.py similarity index 99% rename from aider/sendchat.py rename to cecli/sendchat.py index dcb1f2d8661..e7ab59caf99 100644 --- a/aider/sendchat.py +++ b/cecli/sendchat.py @@ -1,5 +1,5 @@ -from aider.dump import dump # noqa: F401 -from aider.utils import format_messages +from cecli.dump import dump # noqa: F401 +from cecli.utils import format_messages def sanity_check_messages(messages): diff --git a/aider/sessions.py b/cecli/sessions.py similarity index 97% rename from aider/sessions.py rename to cecli/sessions.py index bbc0b2c807e..4b9deeccab6 100644 --- a/aider/sessions.py +++ b/cecli/sessions.py @@ -1,11 +1,11 @@ -"""Session management utilities for Aider.""" +"""Session management utilities for cecli.""" import json import os from pathlib import Path from typing import Dict, List, Optional -from aider import models +from cecli import models class SessionManager: @@ -17,7 +17,7 @@ def __init__(self, coder, io): def _get_session_directory(self) -> Path: """Get the session directory, creating it if necessary.""" - session_dir = Path(self.coder.abs_root_path(".aider/sessions")) + session_dir = Path(self.coder.abs_root_path(".cecli/sessions")) os.makedirs(session_dir, exist_ok=True) return session_dir @@ -129,7 +129,7 @@ def _build_session_data(self, session_name) -> Dict: # Capture todo list content so it can be restored with the session todo_content = None try: - todo_path = self.coder.abs_root_path(".aider.todo.txt") + todo_path = self.coder.abs_root_path(".cecli.todo.txt") if os.path.isfile(todo_path): todo_content = self.io.read_text(todo_path) if todo_content is None: @@ -246,7 +246,7 @@ def _apply_session_data(self, session_data: Dict, session_file: Path) -> bool: # Restore todo list content if present in the session if "todo_list" in session_data: - todo_path = self.coder.abs_root_path(".aider.todo.txt") + todo_path = self.coder.abs_root_path(".cecli.todo.txt") todo_content = session_data.get("todo_list") try: if todo_content is None: diff --git a/aider/special.py b/cecli/special.py similarity index 100% rename from aider/special.py rename to cecli/special.py diff --git a/aider/tools/__init__.py b/cecli/tools/__init__.py similarity index 95% rename from aider/tools/__init__.py rename to cecli/tools/__init__.py index 1ca38ddbf2d..61941c82dca 100644 --- a/aider/tools/__init__.py +++ b/cecli/tools/__init__.py @@ -1,5 +1,5 @@ # flake8: noqa: F401 -# Import tool modules into the aider.tools namespace +# Import tool modules into the cecli.tools namespace # Import all tool modules from . import ( diff --git a/aider/tools/command.py b/cecli/tools/command.py similarity index 97% rename from aider/tools/command.py rename to cecli/tools/command.py index bd86d7c2aae..1bfa1e0a32b 100644 --- a/aider/tools/command.py +++ b/cecli/tools/command.py @@ -1,6 +1,6 @@ # Import necessary functions -from aider.run_cmd import run_cmd_subprocess -from aider.tools.utils.base_tool import BaseTool +from cecli.run_cmd import run_cmd_subprocess +from cecli.tools.utils.base_tool import BaseTool class Tool(BaseTool): diff --git a/aider/tools/command_interactive.py b/cecli/tools/command_interactive.py similarity index 98% rename from aider/tools/command_interactive.py rename to cecli/tools/command_interactive.py index 31b3ccd006f..373c6d70236 100644 --- a/aider/tools/command_interactive.py +++ b/cecli/tools/command_interactive.py @@ -1,8 +1,8 @@ # Import necessary functions import asyncio -from aider.run_cmd import run_cmd -from aider.tools.utils.base_tool import BaseTool +from cecli.run_cmd import run_cmd +from cecli.tools.utils.base_tool import BaseTool class Tool(BaseTool): diff --git a/aider/tools/context_manager.py b/cecli/tools/context_manager.py similarity index 95% rename from aider/tools/context_manager.py rename to cecli/tools/context_manager.py index 740b91ed785..216dca67750 100644 --- a/aider/tools/context_manager.py +++ b/cecli/tools/context_manager.py @@ -1,8 +1,8 @@ import os import time -from aider.tools.utils.base_tool import BaseTool -from aider.tools.utils.helpers import ToolError +from cecli.tools.utils.base_tool import BaseTool +from cecli.tools.utils.helpers import ToolError, parse_arg_as_list class Tool(BaseTool): @@ -67,10 +67,10 @@ def execute(cls, coder, remove=None, editable=None, view=None, create=None): create: list[str] | None Files to create and make editable. """ - remove_files = remove or [] - editable_files = editable or [] - view_files = view or [] - create_files = create or [] + remove_files = parse_arg_as_list(remove) + editable_files = parse_arg_as_list(editable) + view_files = parse_arg_as_list(view) + create_files = parse_arg_as_list(create) if not remove_files and not editable_files and not view_files and not create_files: raise ToolError("You must specify at least one of: remove, editable, view, or create") diff --git a/aider/tools/delete_block.py b/cecli/tools/delete_block.py similarity index 98% rename from aider/tools/delete_block.py rename to cecli/tools/delete_block.py index 3f231ba04ae..2e1f8a35065 100644 --- a/aider/tools/delete_block.py +++ b/cecli/tools/delete_block.py @@ -1,5 +1,5 @@ -from aider.tools.utils.base_tool import BaseTool -from aider.tools.utils.helpers import ( +from cecli.tools.utils.base_tool import BaseTool +from cecli.tools.utils.helpers import ( ToolError, apply_change, determine_line_range, diff --git a/aider/tools/delete_line.py b/cecli/tools/delete_line.py similarity index 97% rename from aider/tools/delete_line.py rename to cecli/tools/delete_line.py index 64470860c77..2196227b123 100644 --- a/aider/tools/delete_line.py +++ b/cecli/tools/delete_line.py @@ -1,5 +1,5 @@ -from aider.tools.utils.base_tool import BaseTool -from aider.tools.utils.helpers import ( +from cecli.tools.utils.base_tool import BaseTool +from cecli.tools.utils.helpers import ( ToolError, apply_change, format_tool_result, diff --git a/aider/tools/delete_lines.py b/cecli/tools/delete_lines.py similarity index 98% rename from aider/tools/delete_lines.py rename to cecli/tools/delete_lines.py index 93dbc76fc1b..0831075f4ad 100644 --- a/aider/tools/delete_lines.py +++ b/cecli/tools/delete_lines.py @@ -1,5 +1,5 @@ -from aider.tools.utils.base_tool import BaseTool -from aider.tools.utils.helpers import ( +from cecli.tools.utils.base_tool import BaseTool +from cecli.tools.utils.helpers import ( ToolError, apply_change, format_tool_result, diff --git a/aider/tools/extract_lines.py b/cecli/tools/extract_lines.py similarity index 99% rename from aider/tools/extract_lines.py rename to cecli/tools/extract_lines.py index 2d2ec7fa71a..93e917f0eae 100644 --- a/aider/tools/extract_lines.py +++ b/cecli/tools/extract_lines.py @@ -1,7 +1,7 @@ import os -from aider.tools.utils.base_tool import BaseTool -from aider.tools.utils.helpers import ( +from cecli.tools.utils.base_tool import BaseTool +from cecli.tools.utils.helpers import ( ToolError, apply_change, generate_unified_diff_snippet, diff --git a/aider/tools/finished.py b/cecli/tools/finished.py similarity index 95% rename from aider/tools/finished.py rename to cecli/tools/finished.py index 1a4a8ab458b..e05b6c90006 100644 --- a/aider/tools/finished.py +++ b/cecli/tools/finished.py @@ -1,4 +1,4 @@ -from aider.tools.utils.base_tool import BaseTool +from cecli.tools.utils.base_tool import BaseTool class Tool(BaseTool): diff --git a/aider/tools/git_branch.py b/cecli/tools/git_branch.py similarity index 98% rename from aider/tools/git_branch.py rename to cecli/tools/git_branch.py index a04c2e715d4..8cd27bea2cc 100644 --- a/aider/tools/git_branch.py +++ b/cecli/tools/git_branch.py @@ -1,5 +1,5 @@ -from aider.repo import ANY_GIT_ERROR -from aider.tools.utils.base_tool import BaseTool +from cecli.repo import ANY_GIT_ERROR +from cecli.tools.utils.base_tool import BaseTool class Tool(BaseTool): diff --git a/aider/tools/git_diff.py b/cecli/tools/git_diff.py similarity index 94% rename from aider/tools/git_diff.py rename to cecli/tools/git_diff.py index 27e4e73ff46..b6a919fe09a 100644 --- a/aider/tools/git_diff.py +++ b/cecli/tools/git_diff.py @@ -1,5 +1,5 @@ -from aider.repo import ANY_GIT_ERROR -from aider.tools.utils.base_tool import BaseTool +from cecli.repo import ANY_GIT_ERROR +from cecli.tools.utils.base_tool import BaseTool class Tool(BaseTool): diff --git a/aider/tools/git_log.py b/cecli/tools/git_log.py similarity index 93% rename from aider/tools/git_log.py rename to cecli/tools/git_log.py index 93d6fcf748f..173753dc39a 100644 --- a/aider/tools/git_log.py +++ b/cecli/tools/git_log.py @@ -1,5 +1,5 @@ -from aider.repo import ANY_GIT_ERROR -from aider.tools.utils.base_tool import BaseTool +from cecli.repo import ANY_GIT_ERROR +from cecli.tools.utils.base_tool import BaseTool class Tool(BaseTool): diff --git a/aider/tools/git_remote.py b/cecli/tools/git_remote.py similarity index 92% rename from aider/tools/git_remote.py rename to cecli/tools/git_remote.py index edcf2fac464..77fca36ba3b 100644 --- a/aider/tools/git_remote.py +++ b/cecli/tools/git_remote.py @@ -1,5 +1,5 @@ -from aider.repo import ANY_GIT_ERROR -from aider.tools.utils.base_tool import BaseTool +from cecli.repo import ANY_GIT_ERROR +from cecli.tools.utils.base_tool import BaseTool class Tool(BaseTool): diff --git a/aider/tools/git_show.py b/cecli/tools/git_show.py similarity index 92% rename from aider/tools/git_show.py rename to cecli/tools/git_show.py index 5a66e612e42..4f31e5f39df 100644 --- a/aider/tools/git_show.py +++ b/cecli/tools/git_show.py @@ -1,5 +1,5 @@ -from aider.repo import ANY_GIT_ERROR -from aider.tools.utils.base_tool import BaseTool +from cecli.repo import ANY_GIT_ERROR +from cecli.tools.utils.base_tool import BaseTool class Tool(BaseTool): diff --git a/aider/tools/git_status.py b/cecli/tools/git_status.py similarity index 89% rename from aider/tools/git_status.py rename to cecli/tools/git_status.py index a3b054f418e..b16ec707f2e 100644 --- a/aider/tools/git_status.py +++ b/cecli/tools/git_status.py @@ -1,5 +1,5 @@ -from aider.repo import ANY_GIT_ERROR -from aider.tools.utils.base_tool import BaseTool +from cecli.repo import ANY_GIT_ERROR +from cecli.tools.utils.base_tool import BaseTool class Tool(BaseTool): diff --git a/aider/tools/grep.py b/cecli/tools/grep.py similarity index 99% rename from aider/tools/grep.py rename to cecli/tools/grep.py index 93102947faa..6fe14b1bcfd 100644 --- a/aider/tools/grep.py +++ b/cecli/tools/grep.py @@ -3,8 +3,8 @@ import oslex -from aider.run_cmd import run_cmd_subprocess -from aider.tools.utils.base_tool import BaseTool +from cecli.run_cmd import run_cmd_subprocess +from cecli.tools.utils.base_tool import BaseTool class Tool(BaseTool): diff --git a/aider/tools/indent_lines.py b/cecli/tools/indent_lines.py similarity index 98% rename from aider/tools/indent_lines.py rename to cecli/tools/indent_lines.py index f232746825c..b310b400a38 100644 --- a/aider/tools/indent_lines.py +++ b/cecli/tools/indent_lines.py @@ -1,5 +1,5 @@ -from aider.tools.utils.base_tool import BaseTool -from aider.tools.utils.helpers import ( +from cecli.tools.utils.base_tool import BaseTool +from cecli.tools.utils.helpers import ( ToolError, apply_change, determine_line_range, diff --git a/aider/tools/insert_block.py b/cecli/tools/insert_block.py similarity index 98% rename from aider/tools/insert_block.py rename to cecli/tools/insert_block.py index 57211742735..95ae1b1f2c1 100644 --- a/aider/tools/insert_block.py +++ b/cecli/tools/insert_block.py @@ -1,8 +1,8 @@ import re import traceback -from aider.tools.utils.base_tool import BaseTool -from aider.tools.utils.helpers import ( +from cecli.tools.utils.base_tool import BaseTool +from cecli.tools.utils.helpers import ( ToolError, apply_change, find_pattern_indices, @@ -13,7 +13,7 @@ select_occurrence_index, validate_file_for_edit, ) -from aider.tools.utils.output import tool_body_unwrapped, tool_footer, tool_header +from cecli.tools.utils.output import tool_body_unwrapped, tool_footer, tool_header class Tool(BaseTool): diff --git a/aider/tools/list_changes.py b/cecli/tools/list_changes.py similarity index 97% rename from aider/tools/list_changes.py rename to cecli/tools/list_changes.py index 234d6bac543..c2f049302af 100644 --- a/aider/tools/list_changes.py +++ b/cecli/tools/list_changes.py @@ -1,7 +1,7 @@ import traceback from datetime import datetime -from aider.tools.utils.base_tool import BaseTool +from cecli.tools.utils.base_tool import BaseTool class Tool(BaseTool): diff --git a/aider/tools/load_skill.py b/cecli/tools/load_skill.py similarity index 97% rename from aider/tools/load_skill.py rename to cecli/tools/load_skill.py index 1b7a62f0de9..85c050035e9 100644 --- a/aider/tools/load_skill.py +++ b/cecli/tools/load_skill.py @@ -1,4 +1,4 @@ -from aider.tools.utils.base_tool import BaseTool +from cecli.tools.utils.base_tool import BaseTool class Tool(BaseTool): diff --git a/aider/tools/ls.py b/cecli/tools/ls.py similarity index 98% rename from aider/tools/ls.py rename to cecli/tools/ls.py index 96d489e5323..43a649592a3 100644 --- a/aider/tools/ls.py +++ b/cecli/tools/ls.py @@ -1,6 +1,6 @@ import os -from aider.tools.utils.base_tool import BaseTool +from cecli.tools.utils.base_tool import BaseTool class Tool(BaseTool): diff --git a/aider/tools/remove_skill.py b/cecli/tools/remove_skill.py similarity index 97% rename from aider/tools/remove_skill.py rename to cecli/tools/remove_skill.py index b31f009a1ca..eb2d036a6d1 100644 --- a/aider/tools/remove_skill.py +++ b/cecli/tools/remove_skill.py @@ -1,4 +1,4 @@ -from aider.tools.utils.base_tool import BaseTool +from cecli.tools.utils.base_tool import BaseTool class Tool(BaseTool): diff --git a/aider/tools/replace_all.py b/cecli/tools/replace_all.py similarity index 96% rename from aider/tools/replace_all.py rename to cecli/tools/replace_all.py index 99ed0837f63..e855a48a42c 100644 --- a/aider/tools/replace_all.py +++ b/cecli/tools/replace_all.py @@ -1,5 +1,5 @@ -from aider.tools.utils.base_tool import BaseTool -from aider.tools.utils.helpers import ( +from cecli.tools.utils.base_tool import BaseTool +from cecli.tools.utils.helpers import ( ToolError, apply_change, format_tool_result, @@ -7,7 +7,7 @@ handle_tool_error, validate_file_for_edit, ) -from aider.tools.utils.output import tool_body_unwrapped, tool_footer, tool_header +from cecli.tools.utils.output import tool_body_unwrapped, tool_footer, tool_header class Tool(BaseTool): diff --git a/aider/tools/replace_line.py b/cecli/tools/replace_line.py similarity index 96% rename from aider/tools/replace_line.py rename to cecli/tools/replace_line.py index 81cba75c0d2..629a83887fa 100644 --- a/aider/tools/replace_line.py +++ b/cecli/tools/replace_line.py @@ -1,8 +1,8 @@ import traceback -from aider.tools.utils.base_tool import BaseTool -from aider.tools.utils.helpers import ToolError, validate_file_for_edit -from aider.tools.utils.output import tool_body_unwrapped, tool_footer, tool_header +from cecli.tools.utils.base_tool import BaseTool +from cecli.tools.utils.helpers import ToolError, validate_file_for_edit +from cecli.tools.utils.output import tool_body_unwrapped, tool_footer, tool_header class Tool(BaseTool): diff --git a/aider/tools/replace_lines.py b/cecli/tools/replace_lines.py similarity index 97% rename from aider/tools/replace_lines.py rename to cecli/tools/replace_lines.py index 3321c75cd0b..92395226d32 100644 --- a/aider/tools/replace_lines.py +++ b/cecli/tools/replace_lines.py @@ -1,5 +1,5 @@ -from aider.tools.utils.base_tool import BaseTool -from aider.tools.utils.helpers import ( +from cecli.tools.utils.base_tool import BaseTool +from cecli.tools.utils.helpers import ( ToolError, apply_change, format_tool_result, @@ -7,7 +7,7 @@ handle_tool_error, validate_file_for_edit, ) -from aider.tools.utils.output import tool_body_unwrapped, tool_footer, tool_header +from cecli.tools.utils.output import tool_body_unwrapped, tool_footer, tool_header class Tool(BaseTool): diff --git a/aider/tools/replace_text.py b/cecli/tools/replace_text.py similarity index 97% rename from aider/tools/replace_text.py rename to cecli/tools/replace_text.py index 199459f9a61..00853ba7b12 100644 --- a/aider/tools/replace_text.py +++ b/cecli/tools/replace_text.py @@ -1,8 +1,8 @@ import difflib import json -from aider.tools.utils.base_tool import BaseTool -from aider.tools.utils.helpers import ( +from cecli.tools.utils.base_tool import BaseTool +from cecli.tools.utils.helpers import ( ToolError, apply_change, format_tool_result, @@ -10,7 +10,7 @@ handle_tool_error, validate_file_for_edit, ) -from aider.tools.utils.output import color_markers, tool_footer, tool_header +from cecli.tools.utils.output import color_markers, tool_footer, tool_header class Tool(BaseTool): diff --git a/aider/tools/show_numbered_context.py b/cecli/tools/show_numbered_context.py similarity index 98% rename from aider/tools/show_numbered_context.py rename to cecli/tools/show_numbered_context.py index 21285062f09..376cf564840 100644 --- a/aider/tools/show_numbered_context.py +++ b/cecli/tools/show_numbered_context.py @@ -1,7 +1,7 @@ import os -from aider.tools.utils.base_tool import BaseTool -from aider.tools.utils.helpers import ( +from cecli.tools.utils.base_tool import BaseTool +from cecli.tools.utils.helpers import ( ToolError, handle_tool_error, is_provided, diff --git a/aider/tools/thinking.py b/cecli/tools/thinking.py similarity index 93% rename from aider/tools/thinking.py rename to cecli/tools/thinking.py index 8bb39581b22..023bba54608 100644 --- a/aider/tools/thinking.py +++ b/cecli/tools/thinking.py @@ -1,7 +1,7 @@ import json -from aider.tools.utils.base_tool import BaseTool -from aider.tools.utils.output import color_markers, tool_footer, tool_header +from cecli.tools.utils.base_tool import BaseTool +from cecli.tools.utils.output import color_markers, tool_footer, tool_header class Tool(BaseTool): diff --git a/aider/tools/undo_change.py b/cecli/tools/undo_change.py similarity index 97% rename from aider/tools/undo_change.py rename to cecli/tools/undo_change.py index 66f7528ef47..badc2623881 100644 --- a/aider/tools/undo_change.py +++ b/cecli/tools/undo_change.py @@ -1,6 +1,6 @@ import traceback -from aider.tools.utils.base_tool import BaseTool +from cecli.tools.utils.base_tool import BaseTool class Tool(BaseTool): @@ -62,7 +62,7 @@ def execute(cls, coder, change_id=None, file_path=None): abs_path = coder.abs_root_path(file_path) # Write the original content back to the file coder.io.write_text(abs_path, change_info["original"]) - coder.aider_edited_files.add( + coder.coder_edited_files.add( file_path ) # Track that the file was modified by the undo diff --git a/aider/tools/update_todo_list.py b/cecli/tools/update_todo_list.py similarity index 94% rename from aider/tools/update_todo_list.py rename to cecli/tools/update_todo_list.py index a1586bf502c..5a58a201861 100644 --- a/aider/tools/update_todo_list.py +++ b/cecli/tools/update_todo_list.py @@ -1,11 +1,11 @@ -from aider.tools.utils.base_tool import BaseTool -from aider.tools.utils.helpers import ( +from cecli.tools.utils.base_tool import BaseTool +from cecli.tools.utils.helpers import ( ToolError, format_tool_result, generate_unified_diff_snippet, handle_tool_error, ) -from aider.tools.utils.output import tool_body_unwrapped, tool_footer, tool_header +from cecli.tools.utils.output import tool_body_unwrapped, tool_footer, tool_header class Tool(BaseTool): @@ -49,13 +49,13 @@ class Tool(BaseTool): @classmethod def execute(cls, coder, content, append=False, change_id=None, dry_run=False): """ - Update the todo list file (.aider.todo.txt) with new content. + Update the todo list file (.cecli.todo.txt) with new content. Can either replace the entire content or append to it. """ tool_name = "UpdateTodoList" try: # Define the todo file path - todo_file_path = ".aider.todo.txt" + todo_file_path = ".cecli.todo.txt" abs_path = coder.abs_root_path(todo_file_path) # Get existing content if appending @@ -123,7 +123,7 @@ def execute(cls, coder, content, append=False, change_id=None, dry_run=False): change_id=change_id, ) - coder.aider_edited_files.add(todo_file_path) + coder.coder_edited_files.add(todo_file_path) # Format and return result action = "appended to" if append else "updated" diff --git a/aider/tools/utils/base_tool.py b/cecli/tools/utils/base_tool.py similarity index 94% rename from aider/tools/utils/base_tool.py rename to cecli/tools/utils/base_tool.py index be82950fa32..676fd337094 100644 --- a/aider/tools/utils/base_tool.py +++ b/cecli/tools/utils/base_tool.py @@ -1,7 +1,7 @@ from abc import ABC, abstractmethod -from aider.tools.utils.helpers import handle_tool_error -from aider.tools.utils.output import print_tool_response +from cecli.tools.utils.helpers import handle_tool_error +from cecli.tools.utils.output import print_tool_response class BaseTool(ABC): diff --git a/aider/tools/utils/helpers.py b/cecli/tools/utils/helpers.py similarity index 93% rename from aider/tools/utils/helpers.py rename to cecli/tools/utils/helpers.py index aa187a28acc..187a7e5e728 100644 --- a/aider/tools/utils/helpers.py +++ b/cecli/tools/utils/helpers.py @@ -245,6 +245,32 @@ def generate_unified_diff_snippet(original_content, new_content, file_path, cont return diff_snippet +def parse_arg_as_list(arg): + if arg is None: + return [] + if isinstance(arg, list): + return arg + if isinstance(arg, str): + # Handle empty or whitespace-only string as empty list + if not arg or arg.isspace(): + return [] + # Try to parse as JSON array + import json + + try: + parsed = json.loads(arg) + if isinstance(parsed, list): + return parsed + else: + # If it's not a list, wrap it in a list + return [parsed] + except json.JSONDecodeError: + # If not valid JSON, treat as a single file path + return [arg] + # For any other type, wrap in list + return [arg] + + def apply_change( coder, abs_path, rel_path, original_content, new_content, change_type, metadata, change_id=None ): @@ -267,7 +293,7 @@ def apply_change( coder.io.tool_error(f"Error tracking change for {change_type}: {track_e}") raise ToolError(f"Failed to track change: {track_e}") - coder.aider_edited_files.add(rel_path) + coder.coder_edited_files.add(rel_path) return final_change_id diff --git a/aider/tools/utils/output.py b/cecli/tools/utils/output.py similarity index 100% rename from aider/tools/utils/output.py rename to cecli/tools/utils/output.py diff --git a/aider/tools/view_files_matching.py b/cecli/tools/view_files_matching.py similarity index 99% rename from aider/tools/view_files_matching.py rename to cecli/tools/view_files_matching.py index c9ffd3c7b1b..f8246fec3bc 100644 --- a/aider/tools/view_files_matching.py +++ b/cecli/tools/view_files_matching.py @@ -1,7 +1,7 @@ import fnmatch import re -from aider.tools.utils.base_tool import BaseTool +from cecli.tools.utils.base_tool import BaseTool class Tool(BaseTool): diff --git a/aider/tools/view_files_with_symbol.py b/cecli/tools/view_files_with_symbol.py similarity index 99% rename from aider/tools/view_files_with_symbol.py rename to cecli/tools/view_files_with_symbol.py index 59eedca69a6..5faf08ffff2 100644 --- a/aider/tools/view_files_with_symbol.py +++ b/cecli/tools/view_files_with_symbol.py @@ -1,4 +1,4 @@ -from aider.tools.utils.base_tool import BaseTool +from cecli.tools.utils.base_tool import BaseTool class Tool(BaseTool): diff --git a/aider/tui/__init__.py b/cecli/tui/__init__.py similarity index 95% rename from aider/tui/__init__.py rename to cecli/tui/__init__.py index 2b108bc8202..2793a64c092 100644 --- a/aider/tui/__init__.py +++ b/cecli/tui/__init__.py @@ -1,7 +1,7 @@ -"""Textual TUI interface for Aider. +"""Textual TUI interface for cecli. -This package provides an experimental TUI (Terminal User Interface) for aider -using the Textual framework. Launch with: aider-ce --tui +This package provides an experimental TUI (Terminal User Interface) for cecli +using the Textual framework. Launch with: cecli --tui """ import queue diff --git a/aider/tui/app.py b/cecli/tui/app.py similarity index 98% rename from aider/tui/app.py rename to cecli/tui/app.py index 2d1c50a38d0..f656decd06f 100644 --- a/aider/tui/app.py +++ b/cecli/tui/app.py @@ -1,4 +1,4 @@ -"""Main Textual application for Aider TUI.""" +"""Main Textual application for cecli TUI.""" import concurrent.futures import json @@ -10,14 +10,14 @@ from textual.containers import Vertical from textual.theme import Theme -from aider.editor import pipe_editor +from cecli.editor import pipe_editor from .widgets import ( - AiderFooter, CompletionBar, FileList, InputArea, KeyHints, + MainFooter, OutputContainer, StatusBar, ) @@ -25,7 +25,7 @@ class TUI(App): - """Main Textual application for Aider TUI.""" + """Main Textual application for cecli TUI.""" CSS_PATH = "styles.tcss" @@ -36,7 +36,7 @@ class TUI(App): ] def __init__(self, coder_worker, output_queue, input_queue, args): - """Initialize the Aider TUI app.""" + """Initialize the cecli TUI app.""" super().__init__() self.worker = coder_worker self.output_queue = output_queue @@ -48,7 +48,7 @@ def __init__(self, coder_worker, output_queue, input_queue, args): self.tui_config = self._get_config() - # Register and set aider theme using config colors + # Register and set cecli theme using config colors colors = self.tui_config.get("colors", {}) other = self.tui_config.get("other", {}) BASE_THEME = Theme( @@ -235,7 +235,7 @@ def compose(self) -> ComposeResult: """Create child widgets.""" coder = self.worker.coder model_name = coder.main_model.name if coder.main_model else "Unknown" - aider_mode = getattr(coder, "edit_format", "code") or "code" + coder_mode = getattr(coder, "edit_format", "code") or "code" # Get project name (just the folder name, not full path) project_name = "" @@ -261,11 +261,11 @@ def compose(self) -> ComposeResult: id="input-container", ) yield KeyHints(id="key-hints") - yield AiderFooter( + yield MainFooter( model_name=model_name, project_name=project_name, git_branch="", # Loaded async in on_mount - aider_mode=aider_mode, + coder_mode=coder_mode, id="footer", ) @@ -321,7 +321,7 @@ def update_key_hints(self, generating=False): def _load_git_info(self): """Load git branch and dirty count (deferred to avoid blocking startup).""" - footer = self.query_one(AiderFooter) + footer = self.query_one(MainFooter) if self.worker.coder.repo: try: branch = self.worker.coder.repo.get_head_branch_name() or "main" @@ -370,19 +370,19 @@ def handle_output_message(self, msg): self.update_spinner(msg) elif msg_type == "ready_for_input": self.enable_input(msg) - footer = self.query_one(AiderFooter) + footer = self.query_one(MainFooter) footer.stop_spinner() elif msg_type == "error": self.show_error(msg["message"]) elif msg_type == "cost_update": - footer = self.query_one(AiderFooter) + footer = self.query_one(MainFooter) footer.update_cost(msg.get("cost", 0)) elif msg_type == "exit": # Graceful exit requested - let Textual clean up terminal properly self.action_quit() elif msg_type == "mode_change": # Update footer with new chat mode - footer = self.query_one(AiderFooter) + footer = self.query_one(MainFooter) footer.update_mode(msg.get("mode", "code")) def add_output(self, text, task_id=None): @@ -456,7 +456,7 @@ def enable_input(self, msg): def update_spinner(self, msg): """Update spinner in footer.""" - footer = self.query_one(AiderFooter) + footer = self.query_one(MainFooter) action = msg.get("action", "start") if action == "start": @@ -510,7 +510,7 @@ def on_input_area_submit(self, message: InputArea.Submit): self.add_user_message(user_input) # Update footer to show processing - footer = self.query_one(AiderFooter) + footer = self.query_one(MainFooter) footer.start_spinner("Processing...") self.update_key_hints(generating=True) @@ -642,7 +642,7 @@ def wrapper(): def on_cost_update(self, message: CostUpdate): """Handle cost update from output.""" - footer = self.query_one(AiderFooter) + footer = self.query_one(MainFooter) footer.cost = message.cost footer.refresh() diff --git a/aider/tui/io.py b/cecli/tui/io.py similarity index 99% rename from aider/tui/io.py rename to cecli/tui/io.py index dc6e5497195..6ae017eaeb6 100644 --- a/aider/tui/io.py +++ b/cecli/tui/io.py @@ -5,7 +5,7 @@ from rich.console import Console -from aider.io import InputOutput, get_rel_fname +from cecli.io import InputOutput, get_rel_fname class TextualInputOutput(InputOutput): diff --git a/aider/tui/styles.tcss b/cecli/tui/styles.tcss similarity index 100% rename from aider/tui/styles.tcss rename to cecli/tui/styles.tcss diff --git a/aider/tui/widgets/__init__.py b/cecli/tui/widgets/__init__.py similarity index 80% rename from aider/tui/widgets/__init__.py rename to cecli/tui/widgets/__init__.py index 0750d40727b..34c569435d8 100644 --- a/aider/tui/widgets/__init__.py +++ b/cecli/tui/widgets/__init__.py @@ -1,15 +1,15 @@ -"""Widgets for the Aider TUI.""" +"""Widgets for the cecli TUI.""" from .completion_bar import CompletionBar from .file_list import FileList -from .footer import AiderFooter +from .footer import MainFooter from .input_area import InputArea from .key_hints import KeyHints from .output import OutputContainer from .status_bar import StatusBar __all__ = [ - "AiderFooter", + "MainFooter", "CompletionBar", "InputArea", "KeyHints", diff --git a/aider/tui/widgets/completion_bar.py b/cecli/tui/widgets/completion_bar.py similarity index 100% rename from aider/tui/widgets/completion_bar.py rename to cecli/tui/widgets/completion_bar.py diff --git a/aider/tui/widgets/file_list.py b/cecli/tui/widgets/file_list.py similarity index 100% rename from aider/tui/widgets/file_list.py rename to cecli/tui/widgets/file_list.py diff --git a/aider/tui/widgets/footer.py b/cecli/tui/widgets/footer.py similarity index 92% rename from aider/tui/widgets/footer.py rename to cecli/tui/widgets/footer.py index 6f727cd178a..fbc550972fa 100644 --- a/aider/tui/widgets/footer.py +++ b/cecli/tui/widgets/footer.py @@ -1,15 +1,15 @@ -"""Footer widget for Aider TUI.""" +"""Footer widget for cecli TUI.""" from rich.text import Text from textual.reactive import reactive from textual.widgets import Static -class AiderFooter(Static): +class MainFooter(Static): """Footer showing mode, model, project, git, and cost.""" # Left side info - aider_mode = reactive("code") + coder_mode = reactive("code") model_name = reactive("") # Right side info @@ -30,7 +30,7 @@ def __init__( model_name: str = "", project_name: str = "", git_branch: str = "", - aider_mode: str = "code", + coder_mode: str = "code", **kwargs, ): """Initialize footer. @@ -39,13 +39,13 @@ def __init__( model_name: Name of the AI model project_name: Name of the project folder git_branch: Current git branch name - aider_mode: Current edit mode (code, agent, architect, etc.) + coder_mode: Current edit mode (code, agent, architect, etc.) """ super().__init__(**kwargs) self.model_name = model_name self.project_name = project_name self.git_branch = git_branch - self.aider_mode = aider_mode + self.coder_mode = coder_mode self._spinner_interval = None def on_mount(self): @@ -96,8 +96,8 @@ def render(self) -> Text: # Build right side: mode + model + project + git right = Text() - if self.aider_mode: - right.append(f"{self.aider_mode}") + if self.coder_mode: + right.append(f"{self.coder_mode}") right.append(" • ") # model_display = self._get_display_model() @@ -149,7 +149,7 @@ def update_git(self, branch: str, dirty_count: int = 0): def update_mode(self, mode: str): """Update the chat mode display.""" - self.aider_mode = mode + self.coder_mode = mode self.refresh() def start_spinner(self, text: str = ""): diff --git a/aider/tui/widgets/input_area.py b/cecli/tui/widgets/input_area.py similarity index 99% rename from aider/tui/widgets/input_area.py rename to cecli/tui/widgets/input_area.py index 67b8da021bd..ad00ff13044 100644 --- a/aider/tui/widgets/input_area.py +++ b/cecli/tui/widgets/input_area.py @@ -1,4 +1,4 @@ -"""Input widget for Aider TUI.""" +"""Input widget for cecli TUI.""" from prompt_toolkit.history import FileHistory from textual.message import Message diff --git a/aider/tui/widgets/key_hints.py b/cecli/tui/widgets/key_hints.py similarity index 100% rename from aider/tui/widgets/key_hints.py rename to cecli/tui/widgets/key_hints.py diff --git a/aider/tui/widgets/output.py b/cecli/tui/widgets/output.py similarity index 99% rename from aider/tui/widgets/output.py rename to cecli/tui/widgets/output.py index 376cfb6c561..bdbfac40172 100644 --- a/aider/tui/widgets/output.py +++ b/cecli/tui/widgets/output.py @@ -1,4 +1,4 @@ -"""Output widget for Aider TUI using Textual's RichLog widget.""" +"""Output widget for cecli TUI using Textual's RichLog widget.""" import re import textwrap diff --git a/aider/tui/widgets/status_bar.py b/cecli/tui/widgets/status_bar.py similarity index 100% rename from aider/tui/widgets/status_bar.py rename to cecli/tui/widgets/status_bar.py diff --git a/aider/tui/worker.py b/cecli/tui/worker.py similarity index 98% rename from aider/tui/worker.py rename to cecli/tui/worker.py index 4596200c9df..551c4047ed1 100644 --- a/aider/tui/worker.py +++ b/cecli/tui/worker.py @@ -6,8 +6,8 @@ import warnings from typing import Optional -from aider.coders import Coder -from aider.commands import SwitchCoder +from cecli.coders import Coder +from cecli.commands import SwitchCoder # Suppress asyncio task destroyed warnings during shutdown logging.getLogger("asyncio").setLevel(logging.CRITICAL) diff --git a/cecli/urls.py b/cecli/urls.py new file mode 100644 index 00000000000..9a5c7b59d8a --- /dev/null +++ b/cecli/urls.py @@ -0,0 +1,16 @@ +website = "https://cecli.dev/" +add_all_files = "https://cecli.dev/docs/faq.html#how-can-i-add-all-the-files-to-the-chat" +edit_errors = "https://cecli.dev/docs/troubleshooting/edit-errors.html" +git = "https://cecli.dev/docs/git.html" +enable_playwright = "https://cecli.dev/docs/install/optional.html#enable-playwright" +favicon = "https://cecli.dev/assets/icons/favicon-32x32.png" +model_warnings = "https://cecli.dev/docs/llms/warnings.html" +token_limits = "https://cecli.dev/docs/troubleshooting/token-limits.html" +llms = "https://cecli.dev/docs/llms.html" +large_repos = "https://cecli.dev/docs/faq.html#can-i-use-cecli-in-a-large-mono-repo" +github_issues = "https://github.com/dwash96/aider-ce/issues/new" +git_index_version = "https://github.com/Aider-AI/aider/issues/211" +install_properly = "https://cecli.dev/docs/troubleshooting/imports.html" +release_notes = "https://github.com/dwash96/aider-ce/releases/latest" +edit_formats = "https://cecli.dev/docs/more/edit-formats.html" +models_and_keys = "https://cecli.dev/docs/troubleshooting/models-and-keys.html" diff --git a/aider/utils.py b/cecli/utils.py similarity index 99% rename from aider/utils.py rename to cecli/utils.py index dbaede294a3..229525034ca 100644 --- a/aider/utils.py +++ b/cecli/utils.py @@ -9,8 +9,8 @@ import oslex -from aider.dump import dump # noqa: F401 -from aider.waiting import Spinner +from cecli.dump import dump # noqa: F401 +from cecli.waiting import Spinner IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".webp", ".pdf"} diff --git a/aider/versioncheck.py b/cecli/versioncheck.py similarity index 67% rename from aider/versioncheck.py rename to cecli/versioncheck.py index 81bc537df83..5e099b40a7c 100644 --- a/aider/versioncheck.py +++ b/cecli/versioncheck.py @@ -5,22 +5,22 @@ import packaging.version -import aider -from aider import utils -from aider.dump import dump # noqa: F401 +import cecli +from cecli import utils +from cecli.dump import dump # noqa +from cecli.helpers.file_searcher import handle_core_files -VERSION_CHECK_FNAME = Path.home() / ".aider" / "caches" / "versioncheck" +VERSION_CHECK_FNAME = handle_core_files(Path.home() / ".cecli" / "caches" / "versioncheck") async def install_from_main_branch(io): """ - Install the latest version of aider from the main branch of the GitHub repository. + Install the latest version of cecli from the main branch of the GitHub repository. """ - return await utils.check_pip_install_extra( io, None, - "Install the development version of aider from the main branch?", + "Install the development version of cecli from the main branch?", ["git+https://github.com/dwash96/aider-ce.git"], self_update=True, ) @@ -28,36 +28,23 @@ async def install_from_main_branch(io): async def install_upgrade(io, latest_version=None): """ - Install the latest version of aider from PyPI. + Install the latest version of cecli from PyPI. """ - if latest_version: - new_ver_text = f"Newer aider-ce version v{latest_version} is available." + new_ver_text = f"Newer cecli version v{latest_version} is available." else: - new_ver_text = "Install latest version of aider?" - - docker_image = os.environ.get("AIDER_DOCKER_IMAGE") + new_ver_text = "Install latest version of cecli?" + docker_image = os.environ.get("CECLIDOCKER_IMAGE") if docker_image: - text = f""" -{new_ver_text} To upgrade, run: - - docker pull {docker_image} -""" + text = f"\n{new_ver_text} To upgrade, run:\n\n docker pull {docker_image}\n" io.tool_warning(text) return True - success = await utils.check_pip_install_extra( - io, - None, - new_ver_text, - ["aider-ce"], - self_update=True, + io, None, new_ver_text, ["cecli"], self_update=True ) - if success: - io.tool_output("Re-run aider-ce to use new version.") + io.tool_output("Re-run cecli to use new version.") sys.exit() - return @@ -70,20 +57,16 @@ async def check_version(io, just_check=False, verbose=False): hours = since / 60 / 60 io.tool_output(f"Too soon to check version: {hours:.1f} hours") return - - # To keep startup fast, avoid importing this unless needed import requests try: - response = requests.get("https://pypi.org/pypi/aider-ce/json") + response = requests.get("https://pypi.org/pypi/cecli-ce/json") data = response.json() latest_version = data["info"]["version"] - current_version = aider.__version__ - + current_version = cecli.__version__ if just_check or verbose: io.tool_output(f"Current version: {current_version}") io.tool_output(f"Latest version: {latest_version}") - is_update_available = ( packaging.version.parse(latest_version).release > packaging.version.parse(current_version).release @@ -94,21 +77,14 @@ async def check_version(io, just_check=False, verbose=False): finally: VERSION_CHECK_FNAME.parent.mkdir(parents=True, exist_ok=True) VERSION_CHECK_FNAME.touch() - - ### - # is_update_available = True - if just_check or verbose: if is_update_available: io.tool_output("Update available") else: io.tool_output("No update available") - if just_check: return is_update_available - if not is_update_available: return False - await install_upgrade(io, latest_version) return True diff --git a/aider/voice.py b/cecli/voice.py similarity index 98% rename from aider/voice.py rename to cecli/voice.py index a6230f949b6..be0013f5a11 100644 --- a/aider/voice.py +++ b/cecli/voice.py @@ -36,7 +36,7 @@ def _run_record_process(stdin_fd, audio_format, device_name, history, language): import sounddevice as sd import soundfile as sf - from aider.llm import litellm + from cecli.llm import litellm # Re-link terminal input sys.stdin = os.fdopen(os.dup(stdin_fd)) diff --git a/aider/waiting.py b/cecli/waiting.py similarity index 100% rename from aider/waiting.py rename to cecli/waiting.py diff --git a/aider/watch.py b/cecli/watch.py similarity index 98% rename from aider/watch.py rename to cecli/watch.py index 0698119f885..bf1a323147d 100644 --- a/aider/watch.py +++ b/cecli/watch.py @@ -8,8 +8,8 @@ from pathspec.patterns import GitWildMatchPattern from watchfiles import watch -from aider.dump import dump # noqa -from aider.watch_prompts import watch_ask_prompt, watch_code_prompt +from cecli.dump import dump # noqa +from cecli.watch_prompts import watch_ask_prompt, watch_code_prompt def load_gitignores(gitignore_paths: list[Path]) -> Optional[PathSpec]: @@ -18,7 +18,7 @@ def load_gitignores(gitignore_paths: list[Path]) -> Optional[PathSpec]: return None patterns = [ - ".aider*", + ".cecli*", ".git", # Common editor backup/temp files "*~", # Emacs/vim backup diff --git a/aider/watch_prompts.py b/cecli/watch_prompts.py similarity index 100% rename from aider/watch_prompts.py rename to cecli/watch_prompts.py diff --git a/aider/website/Gemfile b/cecli/website/Gemfile similarity index 100% rename from aider/website/Gemfile rename to cecli/website/Gemfile diff --git a/aider/website/HISTORY.md b/cecli/website/HISTORY.md similarity index 100% rename from aider/website/HISTORY.md rename to cecli/website/HISTORY.md diff --git a/aider/website/_config.yml b/cecli/website/_config.yml similarity index 90% rename from aider/website/_config.yml rename to cecli/website/_config.yml index 8b88a561a28..08f8cb44473 100644 --- a/aider/website/_config.yml +++ b/cecli/website/_config.yml @@ -30,7 +30,7 @@ exclude: aux_links: "GitHub": - - "https://github.com/dwash96/aider-ce" + - "https://github.com/dwash96/cecli" "Discord": - "https://discord.gg/AX9ZEA7nJn" "Blog": @@ -38,11 +38,11 @@ aux_links: nav_external_links: - title: "GitHub" - url: "https://github.com/dwash96/aider-ce" + url: "https://github.com/dwash96/cecli" - title: "Discord" url: "https://discord.gg/AX9ZEA7nJn" -repository: dwash96/aider-ce +repository: dwash96/cecli callouts: tip: diff --git a/aider/website/_data/architect.yml b/cecli/website/_data/architect.yml similarity index 100% rename from aider/website/_data/architect.yml rename to cecli/website/_data/architect.yml diff --git a/aider/website/_data/blame.yml b/cecli/website/_data/blame.yml similarity index 100% rename from aider/website/_data/blame.yml rename to cecli/website/_data/blame.yml diff --git a/aider/website/_data/code-in-json.yml b/cecli/website/_data/code-in-json.yml similarity index 100% rename from aider/website/_data/code-in-json.yml rename to cecli/website/_data/code-in-json.yml diff --git a/aider/website/_data/deepseek-down.yml b/cecli/website/_data/deepseek-down.yml similarity index 100% rename from aider/website/_data/deepseek-down.yml rename to cecli/website/_data/deepseek-down.yml diff --git a/aider/website/_data/edit_leaderboard.yml b/cecli/website/_data/edit_leaderboard.yml similarity index 99% rename from aider/website/_data/edit_leaderboard.yml rename to cecli/website/_data/edit_leaderboard.yml index 08e333889b9..8b760761c63 100644 --- a/aider/website/_data/edit_leaderboard.yml +++ b/cecli/website/_data/edit_leaderboard.yml @@ -2006,20 +2006,20 @@ seconds_per_case: 35.8 total_cost: 0.0000 -- dirname: 2024-11-28-14-41-46--granite3-dense-8b-whole-1 - test_cases: 133 - model: ollama/granite3-dense:8b - edit_format: whole - commit_hash: 200295e - pass_rate_1: 17.3 - pass_rate_2: 20.3 - percent_cases_well_formed: 78.9 - exhausted_context_windows: 0 - command: aider --model ollama/granite3-dense:8b - date: 2024-11-28 - versions: 0.65.2.dev - seconds_per_case: 38.1 - total_cost: 0.0000 +- dirname: 2024-11-28-14-41-46--granite3-dense-8b-whole-1 + test_cases: 133 + model: ollama/granite3-dense:8b + edit_format: whole + commit_hash: 200295e + pass_rate_1: 17.3 + pass_rate_2: 20.3 + percent_cases_well_formed: 78.9 + exhausted_context_windows: 0 + command: aider --model ollama/granite3-dense:8b + date: 2024-11-28 + versions: 0.65.2.dev + seconds_per_case: 38.1 + total_cost: 0.0000 - dirname: 2024-12-04-13-53-03--nova-whole test_cases: 133 diff --git a/aider/website/_data/o1_polyglot_leaderboard.yml b/cecli/website/_data/o1_polyglot_leaderboard.yml similarity index 100% rename from aider/website/_data/o1_polyglot_leaderboard.yml rename to cecli/website/_data/o1_polyglot_leaderboard.yml diff --git a/aider/website/_data/o1_results.yml b/cecli/website/_data/o1_results.yml similarity index 100% rename from aider/website/_data/o1_results.yml rename to cecli/website/_data/o1_results.yml diff --git a/aider/website/_data/polyglot_leaderboard.yml b/cecli/website/_data/polyglot_leaderboard.yml similarity index 100% rename from aider/website/_data/polyglot_leaderboard.yml rename to cecli/website/_data/polyglot_leaderboard.yml diff --git a/aider/website/_data/quant.yml b/cecli/website/_data/quant.yml similarity index 100% rename from aider/website/_data/quant.yml rename to cecli/website/_data/quant.yml diff --git a/aider/website/_data/qwen3_leaderboard.yml b/cecli/website/_data/qwen3_leaderboard.yml similarity index 100% rename from aider/website/_data/qwen3_leaderboard.yml rename to cecli/website/_data/qwen3_leaderboard.yml diff --git a/aider/website/_data/qwq.yml b/cecli/website/_data/qwq.yml similarity index 100% rename from aider/website/_data/qwq.yml rename to cecli/website/_data/qwq.yml diff --git a/aider/website/_data/r1_architect.yml b/cecli/website/_data/r1_architect.yml similarity index 100% rename from aider/website/_data/r1_architect.yml rename to cecli/website/_data/r1_architect.yml diff --git a/aider/website/_data/refactor_leaderboard.yml b/cecli/website/_data/refactor_leaderboard.yml similarity index 100% rename from aider/website/_data/refactor_leaderboard.yml rename to cecli/website/_data/refactor_leaderboard.yml diff --git a/aider/website/_data/sonnet-fine.yml b/cecli/website/_data/sonnet-fine.yml similarity index 100% rename from aider/website/_data/sonnet-fine.yml rename to cecli/website/_data/sonnet-fine.yml diff --git a/aider/website/_includes/blame.md b/cecli/website/_includes/blame.md similarity index 100% rename from aider/website/_includes/blame.md rename to cecli/website/_includes/blame.md diff --git a/aider/website/_includes/code-in-json-benchmark.js b/cecli/website/_includes/code-in-json-benchmark.js similarity index 100% rename from aider/website/_includes/code-in-json-benchmark.js rename to cecli/website/_includes/code-in-json-benchmark.js diff --git a/aider/website/_includes/code-in-json-syntax.js b/cecli/website/_includes/code-in-json-syntax.js similarity index 100% rename from aider/website/_includes/code-in-json-syntax.js rename to cecli/website/_includes/code-in-json-syntax.js diff --git a/aider/website/_includes/footer_custom.html b/cecli/website/_includes/footer_custom.html similarity index 100% rename from aider/website/_includes/footer_custom.html rename to cecli/website/_includes/footer_custom.html diff --git a/aider/website/_includes/get-started.md b/cecli/website/_includes/get-started.md similarity index 100% rename from aider/website/_includes/get-started.md rename to cecli/website/_includes/get-started.md diff --git a/aider/website/_includes/head_custom.html b/cecli/website/_includes/head_custom.html similarity index 100% rename from aider/website/_includes/head_custom.html rename to cecli/website/_includes/head_custom.html diff --git a/aider/website/_includes/help-tip.md b/cecli/website/_includes/help-tip.md similarity index 100% rename from aider/website/_includes/help-tip.md rename to cecli/website/_includes/help-tip.md diff --git a/aider/website/_includes/help.md b/cecli/website/_includes/help.md similarity index 100% rename from aider/website/_includes/help.md rename to cecli/website/_includes/help.md diff --git a/aider/website/_includes/install.md b/cecli/website/_includes/install.md similarity index 100% rename from aider/website/_includes/install.md rename to cecli/website/_includes/install.md diff --git a/aider/website/_includes/keys.md b/cecli/website/_includes/keys.md similarity index 100% rename from aider/website/_includes/keys.md rename to cecli/website/_includes/keys.md diff --git a/aider/website/_includes/leaderboard.js b/cecli/website/_includes/leaderboard.js similarity index 100% rename from aider/website/_includes/leaderboard.js rename to cecli/website/_includes/leaderboard.js diff --git a/aider/website/_includes/leaderboard_graph.html b/cecli/website/_includes/leaderboard_graph.html similarity index 100% rename from aider/website/_includes/leaderboard_graph.html rename to cecli/website/_includes/leaderboard_graph.html diff --git a/aider/website/_includes/leaderboard_table.js b/cecli/website/_includes/leaderboard_table.js similarity index 100% rename from aider/website/_includes/leaderboard_table.js rename to cecli/website/_includes/leaderboard_table.js diff --git a/aider/website/_includes/model-warnings.md b/cecli/website/_includes/model-warnings.md similarity index 100% rename from aider/website/_includes/model-warnings.md rename to cecli/website/_includes/model-warnings.md diff --git a/aider/website/_includes/multi-line.md b/cecli/website/_includes/multi-line.md similarity index 100% rename from aider/website/_includes/multi-line.md rename to cecli/website/_includes/multi-line.md diff --git a/aider/website/_includes/nav_footer_custom.html b/cecli/website/_includes/nav_footer_custom.html similarity index 100% rename from aider/website/_includes/nav_footer_custom.html rename to cecli/website/_includes/nav_footer_custom.html diff --git a/aider/website/_includes/python-m-aider.md b/cecli/website/_includes/python-m-aider.md similarity index 100% rename from aider/website/_includes/python-m-aider.md rename to cecli/website/_includes/python-m-aider.md diff --git a/aider/website/_includes/quant-chart.js b/cecli/website/_includes/quant-chart.js similarity index 100% rename from aider/website/_includes/quant-chart.js rename to cecli/website/_includes/quant-chart.js diff --git a/aider/website/_includes/qwq-chart.js b/cecli/website/_includes/qwq-chart.js similarity index 100% rename from aider/website/_includes/qwq-chart.js rename to cecli/website/_includes/qwq-chart.js diff --git a/aider/website/_includes/recording.css b/cecli/website/_includes/recording.css similarity index 100% rename from aider/website/_includes/recording.css rename to cecli/website/_includes/recording.css diff --git a/aider/website/_includes/recording.js b/cecli/website/_includes/recording.js similarity index 100% rename from aider/website/_includes/recording.js rename to cecli/website/_includes/recording.js diff --git a/aider/website/_includes/recording.md b/cecli/website/_includes/recording.md similarity index 100% rename from aider/website/_includes/recording.md rename to cecli/website/_includes/recording.md diff --git a/aider/website/_includes/replit-pipx.md b/cecli/website/_includes/replit-pipx.md similarity index 100% rename from aider/website/_includes/replit-pipx.md rename to cecli/website/_includes/replit-pipx.md diff --git a/aider/website/_includes/works-best.md b/cecli/website/_includes/works-best.md similarity index 100% rename from aider/website/_includes/works-best.md rename to cecli/website/_includes/works-best.md diff --git a/aider/website/_layouts/redirect.html b/cecli/website/_layouts/redirect.html similarity index 100% rename from aider/website/_layouts/redirect.html rename to cecli/website/_layouts/redirect.html diff --git a/aider/website/_posts/2023-05-25-ctags.md b/cecli/website/_posts/2023-05-25-ctags.md similarity index 100% rename from aider/website/_posts/2023-05-25-ctags.md rename to cecli/website/_posts/2023-05-25-ctags.md diff --git a/aider/website/_posts/2023-07-02-benchmarks.md b/cecli/website/_posts/2023-07-02-benchmarks.md similarity index 100% rename from aider/website/_posts/2023-07-02-benchmarks.md rename to cecli/website/_posts/2023-07-02-benchmarks.md diff --git a/aider/website/_posts/2023-10-22-repomap.md b/cecli/website/_posts/2023-10-22-repomap.md similarity index 100% rename from aider/website/_posts/2023-10-22-repomap.md rename to cecli/website/_posts/2023-10-22-repomap.md diff --git a/aider/website/_posts/2023-11-06-benchmarks-1106.md b/cecli/website/_posts/2023-11-06-benchmarks-1106.md similarity index 100% rename from aider/website/_posts/2023-11-06-benchmarks-1106.md rename to cecli/website/_posts/2023-11-06-benchmarks-1106.md diff --git a/aider/website/_posts/2023-11-06-benchmarks-speed-1106.md b/cecli/website/_posts/2023-11-06-benchmarks-speed-1106.md similarity index 100% rename from aider/website/_posts/2023-11-06-benchmarks-speed-1106.md rename to cecli/website/_posts/2023-11-06-benchmarks-speed-1106.md diff --git a/aider/website/_posts/2023-12-21-unified-diffs.md b/cecli/website/_posts/2023-12-21-unified-diffs.md similarity index 100% rename from aider/website/_posts/2023-12-21-unified-diffs.md rename to cecli/website/_posts/2023-12-21-unified-diffs.md diff --git a/aider/website/_posts/2024-01-25-benchmarks-0125.md b/cecli/website/_posts/2024-01-25-benchmarks-0125.md similarity index 100% rename from aider/website/_posts/2024-01-25-benchmarks-0125.md rename to cecli/website/_posts/2024-01-25-benchmarks-0125.md diff --git a/aider/website/_posts/2024-03-08-claude-3.md b/cecli/website/_posts/2024-03-08-claude-3.md similarity index 100% rename from aider/website/_posts/2024-03-08-claude-3.md rename to cecli/website/_posts/2024-03-08-claude-3.md diff --git a/aider/website/_posts/2024-04-09-gpt-4-turbo.md b/cecli/website/_posts/2024-04-09-gpt-4-turbo.md similarity index 100% rename from aider/website/_posts/2024-04-09-gpt-4-turbo.md rename to cecli/website/_posts/2024-04-09-gpt-4-turbo.md diff --git a/aider/website/_posts/2024-05-02-browser.md b/cecli/website/_posts/2024-05-02-browser.md similarity index 100% rename from aider/website/_posts/2024-05-02-browser.md rename to cecli/website/_posts/2024-05-02-browser.md diff --git a/aider/website/_posts/2024-05-13-models-over-time.md b/cecli/website/_posts/2024-05-13-models-over-time.md similarity index 100% rename from aider/website/_posts/2024-05-13-models-over-time.md rename to cecli/website/_posts/2024-05-13-models-over-time.md diff --git a/aider/website/_posts/2024-05-22-draft.md b/cecli/website/_posts/2024-05-22-draft.md similarity index 100% rename from aider/website/_posts/2024-05-22-draft.md rename to cecli/website/_posts/2024-05-22-draft.md diff --git a/aider/website/_posts/2024-05-22-linting.md b/cecli/website/_posts/2024-05-22-linting.md similarity index 100% rename from aider/website/_posts/2024-05-22-linting.md rename to cecli/website/_posts/2024-05-22-linting.md diff --git a/aider/website/_posts/2024-05-22-swe-bench-lite.md b/cecli/website/_posts/2024-05-22-swe-bench-lite.md similarity index 100% rename from aider/website/_posts/2024-05-22-swe-bench-lite.md rename to cecli/website/_posts/2024-05-22-swe-bench-lite.md diff --git a/aider/website/_posts/2024-05-24-self-assembly.md b/cecli/website/_posts/2024-05-24-self-assembly.md similarity index 100% rename from aider/website/_posts/2024-05-24-self-assembly.md rename to cecli/website/_posts/2024-05-24-self-assembly.md diff --git a/aider/website/_posts/2024-06-02-main-swe-bench.md b/cecli/website/_posts/2024-06-02-main-swe-bench.md similarity index 100% rename from aider/website/_posts/2024-06-02-main-swe-bench.md rename to cecli/website/_posts/2024-06-02-main-swe-bench.md diff --git a/aider/website/_posts/2024-07-01-sonnet-not-lazy.md b/cecli/website/_posts/2024-07-01-sonnet-not-lazy.md similarity index 100% rename from aider/website/_posts/2024-07-01-sonnet-not-lazy.md rename to cecli/website/_posts/2024-07-01-sonnet-not-lazy.md diff --git a/aider/website/_posts/2024-07-25-new-models.md b/cecli/website/_posts/2024-07-25-new-models.md similarity index 100% rename from aider/website/_posts/2024-07-25-new-models.md rename to cecli/website/_posts/2024-07-25-new-models.md diff --git a/aider/website/_posts/2024-08-14-code-in-json.md b/cecli/website/_posts/2024-08-14-code-in-json.md similarity index 100% rename from aider/website/_posts/2024-08-14-code-in-json.md rename to cecli/website/_posts/2024-08-14-code-in-json.md diff --git a/aider/website/_posts/2024-08-26-sonnet-seems-fine.md b/cecli/website/_posts/2024-08-26-sonnet-seems-fine.md similarity index 100% rename from aider/website/_posts/2024-08-26-sonnet-seems-fine.md rename to cecli/website/_posts/2024-08-26-sonnet-seems-fine.md diff --git a/aider/website/_posts/2024-09-12-o1.md b/cecli/website/_posts/2024-09-12-o1.md similarity index 100% rename from aider/website/_posts/2024-09-12-o1.md rename to cecli/website/_posts/2024-09-12-o1.md diff --git a/aider/website/_posts/2024-09-26-architect.md b/cecli/website/_posts/2024-09-26-architect.md similarity index 100% rename from aider/website/_posts/2024-09-26-architect.md rename to cecli/website/_posts/2024-09-26-architect.md diff --git a/aider/website/_posts/2024-11-21-quantization.md b/cecli/website/_posts/2024-11-21-quantization.md similarity index 100% rename from aider/website/_posts/2024-11-21-quantization.md rename to cecli/website/_posts/2024-11-21-quantization.md diff --git a/aider/website/_posts/2024-12-03-qwq.md b/cecli/website/_posts/2024-12-03-qwq.md similarity index 100% rename from aider/website/_posts/2024-12-03-qwq.md rename to cecli/website/_posts/2024-12-03-qwq.md diff --git a/aider/website/_posts/2024-12-21-polyglot.md b/cecli/website/_posts/2024-12-21-polyglot.md similarity index 100% rename from aider/website/_posts/2024-12-21-polyglot.md rename to cecli/website/_posts/2024-12-21-polyglot.md diff --git a/aider/website/_posts/2025-01-15-uv.md b/cecli/website/_posts/2025-01-15-uv.md similarity index 100% rename from aider/website/_posts/2025-01-15-uv.md rename to cecli/website/_posts/2025-01-15-uv.md diff --git a/aider/website/_posts/2025-01-24-r1-sonnet.md b/cecli/website/_posts/2025-01-24-r1-sonnet.md similarity index 100% rename from aider/website/_posts/2025-01-24-r1-sonnet.md rename to cecli/website/_posts/2025-01-24-r1-sonnet.md diff --git a/aider/website/_posts/2025-01-28-deepseek-down.md b/cecli/website/_posts/2025-01-28-deepseek-down.md similarity index 100% rename from aider/website/_posts/2025-01-28-deepseek-down.md rename to cecli/website/_posts/2025-01-28-deepseek-down.md diff --git a/aider/website/_posts/2025-05-07-gemini-cost.md b/cecli/website/_posts/2025-05-07-gemini-cost.md similarity index 100% rename from aider/website/_posts/2025-05-07-gemini-cost.md rename to cecli/website/_posts/2025-05-07-gemini-cost.md diff --git a/aider/website/_posts/2025-05-08-qwen3.md b/cecli/website/_posts/2025-05-08-qwen3.md similarity index 100% rename from aider/website/_posts/2025-05-08-qwen3.md rename to cecli/website/_posts/2025-05-08-qwen3.md diff --git a/aider/website/_sass/custom/custom.scss b/cecli/website/_sass/custom/custom.scss similarity index 100% rename from aider/website/_sass/custom/custom.scss rename to cecli/website/_sass/custom/custom.scss diff --git a/aider/website/assets/2024-03-07-claude-3.jpg b/cecli/website/assets/2024-03-07-claude-3.jpg similarity index 100% rename from aider/website/assets/2024-03-07-claude-3.jpg rename to cecli/website/assets/2024-03-07-claude-3.jpg diff --git a/aider/website/assets/2024-03-07-claude-3.svg b/cecli/website/assets/2024-03-07-claude-3.svg similarity index 100% rename from aider/website/assets/2024-03-07-claude-3.svg rename to cecli/website/assets/2024-03-07-claude-3.svg diff --git a/aider/website/assets/2024-04-09-gpt-4-turbo-laziness.jpg b/cecli/website/assets/2024-04-09-gpt-4-turbo-laziness.jpg similarity index 100% rename from aider/website/assets/2024-04-09-gpt-4-turbo-laziness.jpg rename to cecli/website/assets/2024-04-09-gpt-4-turbo-laziness.jpg diff --git a/aider/website/assets/2024-04-09-gpt-4-turbo-laziness.svg b/cecli/website/assets/2024-04-09-gpt-4-turbo-laziness.svg similarity index 100% rename from aider/website/assets/2024-04-09-gpt-4-turbo-laziness.svg rename to cecli/website/assets/2024-04-09-gpt-4-turbo-laziness.svg diff --git a/aider/website/assets/2024-04-09-gpt-4-turbo.jpg b/cecli/website/assets/2024-04-09-gpt-4-turbo.jpg similarity index 100% rename from aider/website/assets/2024-04-09-gpt-4-turbo.jpg rename to cecli/website/assets/2024-04-09-gpt-4-turbo.jpg diff --git a/aider/website/assets/2024-04-09-gpt-4-turbo.svg b/cecli/website/assets/2024-04-09-gpt-4-turbo.svg similarity index 100% rename from aider/website/assets/2024-04-09-gpt-4-turbo.svg rename to cecli/website/assets/2024-04-09-gpt-4-turbo.svg diff --git a/aider/website/assets/2024-07-new-models.jpg b/cecli/website/assets/2024-07-new-models.jpg similarity index 100% rename from aider/website/assets/2024-07-new-models.jpg rename to cecli/website/assets/2024-07-new-models.jpg diff --git a/aider/website/assets/2025-05-08-qwen3.jpg b/cecli/website/assets/2025-05-08-qwen3.jpg similarity index 100% rename from aider/website/assets/2025-05-08-qwen3.jpg rename to cecli/website/assets/2025-05-08-qwen3.jpg diff --git a/aider/website/assets/Glass_TTY_VT220.ttf b/cecli/website/assets/Glass_TTY_VT220.ttf similarity index 100% rename from aider/website/assets/Glass_TTY_VT220.ttf rename to cecli/website/assets/Glass_TTY_VT220.ttf diff --git a/aider/website/assets/aider-browser-social.mp4 b/cecli/website/assets/aider-browser-social.mp4 similarity index 100% rename from aider/website/assets/aider-browser-social.mp4 rename to cecli/website/assets/aider-browser-social.mp4 diff --git a/aider/website/assets/aider-square.jpg b/cecli/website/assets/aider-square.jpg similarity index 100% rename from aider/website/assets/aider-square.jpg rename to cecli/website/assets/aider-square.jpg diff --git a/aider/website/assets/aider.jpg b/cecli/website/assets/aider.jpg similarity index 100% rename from aider/website/assets/aider.jpg rename to cecli/website/assets/aider.jpg diff --git a/aider/website/assets/architect.jpg b/cecli/website/assets/architect.jpg similarity index 100% rename from aider/website/assets/architect.jpg rename to cecli/website/assets/architect.jpg diff --git a/aider/website/assets/asciinema/asciinema-player.css b/cecli/website/assets/asciinema/asciinema-player.css similarity index 100% rename from aider/website/assets/asciinema/asciinema-player.css rename to cecli/website/assets/asciinema/asciinema-player.css diff --git a/aider/website/assets/asciinema/asciinema-player.min.js b/cecli/website/assets/asciinema/asciinema-player.min.js similarity index 100% rename from aider/website/assets/asciinema/asciinema-player.min.js rename to cecli/website/assets/asciinema/asciinema-player.min.js diff --git a/aider/website/assets/audio/auto-accept-architect/00-01.mp3 b/cecli/website/assets/audio/auto-accept-architect/00-01.mp3 similarity index 100% rename from aider/website/assets/audio/auto-accept-architect/00-01.mp3 rename to cecli/website/assets/audio/auto-accept-architect/00-01.mp3 diff --git a/aider/website/assets/audio/auto-accept-architect/00-11.mp3 b/cecli/website/assets/audio/auto-accept-architect/00-11.mp3 similarity index 100% rename from aider/website/assets/audio/auto-accept-architect/00-11.mp3 rename to cecli/website/assets/audio/auto-accept-architect/00-11.mp3 diff --git a/aider/website/assets/audio/auto-accept-architect/00-40.mp3 b/cecli/website/assets/audio/auto-accept-architect/00-40.mp3 similarity index 100% rename from aider/website/assets/audio/auto-accept-architect/00-40.mp3 rename to cecli/website/assets/audio/auto-accept-architect/00-40.mp3 diff --git a/aider/website/assets/audio/auto-accept-architect/00-48.mp3 b/cecli/website/assets/audio/auto-accept-architect/00-48.mp3 similarity index 100% rename from aider/website/assets/audio/auto-accept-architect/00-48.mp3 rename to cecli/website/assets/audio/auto-accept-architect/00-48.mp3 diff --git a/aider/website/assets/audio/auto-accept-architect/01-00.mp3 b/cecli/website/assets/audio/auto-accept-architect/01-00.mp3 similarity index 100% rename from aider/website/assets/audio/auto-accept-architect/01-00.mp3 rename to cecli/website/assets/audio/auto-accept-architect/01-00.mp3 diff --git a/aider/website/assets/audio/auto-accept-architect/01-28.mp3 b/cecli/website/assets/audio/auto-accept-architect/01-28.mp3 similarity index 100% rename from aider/website/assets/audio/auto-accept-architect/01-28.mp3 rename to cecli/website/assets/audio/auto-accept-architect/01-28.mp3 diff --git a/aider/website/assets/audio/auto-accept-architect/01-42.mp3 b/cecli/website/assets/audio/auto-accept-architect/01-42.mp3 similarity index 100% rename from aider/website/assets/audio/auto-accept-architect/01-42.mp3 rename to cecli/website/assets/audio/auto-accept-architect/01-42.mp3 diff --git a/aider/website/assets/audio/auto-accept-architect/02-00.mp3 b/cecli/website/assets/audio/auto-accept-architect/02-00.mp3 similarity index 100% rename from aider/website/assets/audio/auto-accept-architect/02-00.mp3 rename to cecli/website/assets/audio/auto-accept-architect/02-00.mp3 diff --git a/aider/website/assets/audio/auto-accept-architect/02-05.mp3 b/cecli/website/assets/audio/auto-accept-architect/02-05.mp3 similarity index 100% rename from aider/website/assets/audio/auto-accept-architect/02-05.mp3 rename to cecli/website/assets/audio/auto-accept-architect/02-05.mp3 diff --git a/aider/website/assets/audio/auto-accept-architect/metadata.json b/cecli/website/assets/audio/auto-accept-architect/metadata.json similarity index 100% rename from aider/website/assets/audio/auto-accept-architect/metadata.json rename to cecli/website/assets/audio/auto-accept-architect/metadata.json diff --git a/aider/website/assets/audio/dont-drop-original-read-files/00-01.mp3 b/cecli/website/assets/audio/dont-drop-original-read-files/00-01.mp3 similarity index 100% rename from aider/website/assets/audio/dont-drop-original-read-files/00-01.mp3 rename to cecli/website/assets/audio/dont-drop-original-read-files/00-01.mp3 diff --git a/aider/website/assets/audio/dont-drop-original-read-files/00-10.mp3 b/cecli/website/assets/audio/dont-drop-original-read-files/00-10.mp3 similarity index 100% rename from aider/website/assets/audio/dont-drop-original-read-files/00-10.mp3 rename to cecli/website/assets/audio/dont-drop-original-read-files/00-10.mp3 diff --git a/aider/website/assets/audio/dont-drop-original-read-files/00-20.mp3 b/cecli/website/assets/audio/dont-drop-original-read-files/00-20.mp3 similarity index 100% rename from aider/website/assets/audio/dont-drop-original-read-files/00-20.mp3 rename to cecli/website/assets/audio/dont-drop-original-read-files/00-20.mp3 diff --git a/aider/website/assets/audio/dont-drop-original-read-files/01-20.mp3 b/cecli/website/assets/audio/dont-drop-original-read-files/01-20.mp3 similarity index 100% rename from aider/website/assets/audio/dont-drop-original-read-files/01-20.mp3 rename to cecli/website/assets/audio/dont-drop-original-read-files/01-20.mp3 diff --git a/aider/website/assets/audio/dont-drop-original-read-files/01-30.mp3 b/cecli/website/assets/audio/dont-drop-original-read-files/01-30.mp3 similarity index 100% rename from aider/website/assets/audio/dont-drop-original-read-files/01-30.mp3 rename to cecli/website/assets/audio/dont-drop-original-read-files/01-30.mp3 diff --git a/aider/website/assets/audio/dont-drop-original-read-files/01-45.mp3 b/cecli/website/assets/audio/dont-drop-original-read-files/01-45.mp3 similarity index 100% rename from aider/website/assets/audio/dont-drop-original-read-files/01-45.mp3 rename to cecli/website/assets/audio/dont-drop-original-read-files/01-45.mp3 diff --git a/aider/website/assets/audio/dont-drop-original-read-files/02-10.mp3 b/cecli/website/assets/audio/dont-drop-original-read-files/02-10.mp3 similarity index 100% rename from aider/website/assets/audio/dont-drop-original-read-files/02-10.mp3 rename to cecli/website/assets/audio/dont-drop-original-read-files/02-10.mp3 diff --git a/aider/website/assets/audio/dont-drop-original-read-files/02-19.mp3 b/cecli/website/assets/audio/dont-drop-original-read-files/02-19.mp3 similarity index 100% rename from aider/website/assets/audio/dont-drop-original-read-files/02-19.mp3 rename to cecli/website/assets/audio/dont-drop-original-read-files/02-19.mp3 diff --git a/aider/website/assets/audio/dont-drop-original-read-files/02-50.mp3 b/cecli/website/assets/audio/dont-drop-original-read-files/02-50.mp3 similarity index 100% rename from aider/website/assets/audio/dont-drop-original-read-files/02-50.mp3 rename to cecli/website/assets/audio/dont-drop-original-read-files/02-50.mp3 diff --git a/aider/website/assets/audio/dont-drop-original-read-files/metadata.json b/cecli/website/assets/audio/dont-drop-original-read-files/metadata.json similarity index 100% rename from aider/website/assets/audio/dont-drop-original-read-files/metadata.json rename to cecli/website/assets/audio/dont-drop-original-read-files/metadata.json diff --git a/aider/website/assets/audio/model-accepts-settings/00-01.mp3 b/cecli/website/assets/audio/model-accepts-settings/00-01.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/00-01.mp3 rename to cecli/website/assets/audio/model-accepts-settings/00-01.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/00-25.mp3 b/cecli/website/assets/audio/model-accepts-settings/00-25.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/00-25.mp3 rename to cecli/website/assets/audio/model-accepts-settings/00-25.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/01-30.mp3 b/cecli/website/assets/audio/model-accepts-settings/01-30.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/01-30.mp3 rename to cecli/website/assets/audio/model-accepts-settings/01-30.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/01-45.mp3 b/cecli/website/assets/audio/model-accepts-settings/01-45.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/01-45.mp3 rename to cecli/website/assets/audio/model-accepts-settings/01-45.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/02-00.mp3 b/cecli/website/assets/audio/model-accepts-settings/02-00.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/02-00.mp3 rename to cecli/website/assets/audio/model-accepts-settings/02-00.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/03-00.mp3 b/cecli/website/assets/audio/model-accepts-settings/03-00.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/03-00.mp3 rename to cecli/website/assets/audio/model-accepts-settings/03-00.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/03-45.mp3 b/cecli/website/assets/audio/model-accepts-settings/03-45.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/03-45.mp3 rename to cecli/website/assets/audio/model-accepts-settings/03-45.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/04-45.mp3 b/cecli/website/assets/audio/model-accepts-settings/04-45.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/04-45.mp3 rename to cecli/website/assets/audio/model-accepts-settings/04-45.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/05-00.mp3 b/cecli/website/assets/audio/model-accepts-settings/05-00.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/05-00.mp3 rename to cecli/website/assets/audio/model-accepts-settings/05-00.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/05-10.mp3 b/cecli/website/assets/audio/model-accepts-settings/05-10.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/05-10.mp3 rename to cecli/website/assets/audio/model-accepts-settings/05-10.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/06-00.mp3 b/cecli/website/assets/audio/model-accepts-settings/06-00.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/06-00.mp3 rename to cecli/website/assets/audio/model-accepts-settings/06-00.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/07-43.mp3 b/cecli/website/assets/audio/model-accepts-settings/07-43.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/07-43.mp3 rename to cecli/website/assets/audio/model-accepts-settings/07-43.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/09-20.mp3 b/cecli/website/assets/audio/model-accepts-settings/09-20.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/09-20.mp3 rename to cecli/website/assets/audio/model-accepts-settings/09-20.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/10-20.mp3 b/cecli/website/assets/audio/model-accepts-settings/10-20.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/10-20.mp3 rename to cecli/website/assets/audio/model-accepts-settings/10-20.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/10-41.mp3 b/cecli/website/assets/audio/model-accepts-settings/10-41.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/10-41.mp3 rename to cecli/website/assets/audio/model-accepts-settings/10-41.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/10-55.mp3 b/cecli/website/assets/audio/model-accepts-settings/10-55.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/10-55.mp3 rename to cecli/website/assets/audio/model-accepts-settings/10-55.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/11-28.mp3 b/cecli/website/assets/audio/model-accepts-settings/11-28.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/11-28.mp3 rename to cecli/website/assets/audio/model-accepts-settings/11-28.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/12-00.mp3 b/cecli/website/assets/audio/model-accepts-settings/12-00.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/12-00.mp3 rename to cecli/website/assets/audio/model-accepts-settings/12-00.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/12-32.mp3 b/cecli/website/assets/audio/model-accepts-settings/12-32.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/12-32.mp3 rename to cecli/website/assets/audio/model-accepts-settings/12-32.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/12-48.mp3 b/cecli/website/assets/audio/model-accepts-settings/12-48.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/12-48.mp3 rename to cecli/website/assets/audio/model-accepts-settings/12-48.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/13-00.mp3 b/cecli/website/assets/audio/model-accepts-settings/13-00.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/13-00.mp3 rename to cecli/website/assets/audio/model-accepts-settings/13-00.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/14-30.mp3 b/cecli/website/assets/audio/model-accepts-settings/14-30.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/14-30.mp3 rename to cecli/website/assets/audio/model-accepts-settings/14-30.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/14-45.mp3 b/cecli/website/assets/audio/model-accepts-settings/14-45.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/14-45.mp3 rename to cecli/website/assets/audio/model-accepts-settings/14-45.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/14-59.mp3 b/cecli/website/assets/audio/model-accepts-settings/14-59.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/14-59.mp3 rename to cecli/website/assets/audio/model-accepts-settings/14-59.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/15-09.mp3 b/cecli/website/assets/audio/model-accepts-settings/15-09.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/15-09.mp3 rename to cecli/website/assets/audio/model-accepts-settings/15-09.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/15-34.mp3 b/cecli/website/assets/audio/model-accepts-settings/15-34.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/15-34.mp3 rename to cecli/website/assets/audio/model-accepts-settings/15-34.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/15-44.mp3 b/cecli/website/assets/audio/model-accepts-settings/15-44.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/15-44.mp3 rename to cecli/website/assets/audio/model-accepts-settings/15-44.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/16-04.mp3 b/cecli/website/assets/audio/model-accepts-settings/16-04.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/16-04.mp3 rename to cecli/website/assets/audio/model-accepts-settings/16-04.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/16-14.mp3 b/cecli/website/assets/audio/model-accepts-settings/16-14.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/16-14.mp3 rename to cecli/website/assets/audio/model-accepts-settings/16-14.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/16-29.mp3 b/cecli/website/assets/audio/model-accepts-settings/16-29.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/16-29.mp3 rename to cecli/website/assets/audio/model-accepts-settings/16-29.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/16-47.mp3 b/cecli/website/assets/audio/model-accepts-settings/16-47.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/16-47.mp3 rename to cecli/website/assets/audio/model-accepts-settings/16-47.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/16-55.mp3 b/cecli/website/assets/audio/model-accepts-settings/16-55.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/16-55.mp3 rename to cecli/website/assets/audio/model-accepts-settings/16-55.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/17-59.mp3 b/cecli/website/assets/audio/model-accepts-settings/17-59.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/17-59.mp3 rename to cecli/website/assets/audio/model-accepts-settings/17-59.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/18-35.mp3 b/cecli/website/assets/audio/model-accepts-settings/18-35.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/18-35.mp3 rename to cecli/website/assets/audio/model-accepts-settings/18-35.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/19-44.mp3 b/cecli/website/assets/audio/model-accepts-settings/19-44.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/19-44.mp3 rename to cecli/website/assets/audio/model-accepts-settings/19-44.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/19-54.mp3 b/cecli/website/assets/audio/model-accepts-settings/19-54.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/19-54.mp3 rename to cecli/website/assets/audio/model-accepts-settings/19-54.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/20-25.mp3 b/cecli/website/assets/audio/model-accepts-settings/20-25.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/20-25.mp3 rename to cecli/website/assets/audio/model-accepts-settings/20-25.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/20-55.mp3 b/cecli/website/assets/audio/model-accepts-settings/20-55.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/20-55.mp3 rename to cecli/website/assets/audio/model-accepts-settings/20-55.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/21-10.mp3 b/cecli/website/assets/audio/model-accepts-settings/21-10.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/21-10.mp3 rename to cecli/website/assets/audio/model-accepts-settings/21-10.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/22-32.mp3 b/cecli/website/assets/audio/model-accepts-settings/22-32.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/22-32.mp3 rename to cecli/website/assets/audio/model-accepts-settings/22-32.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/24-25.mp3 b/cecli/website/assets/audio/model-accepts-settings/24-25.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/24-25.mp3 rename to cecli/website/assets/audio/model-accepts-settings/24-25.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/24-56.mp3 b/cecli/website/assets/audio/model-accepts-settings/24-56.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/24-56.mp3 rename to cecli/website/assets/audio/model-accepts-settings/24-56.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/25-35.mp3 b/cecli/website/assets/audio/model-accepts-settings/25-35.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/25-35.mp3 rename to cecli/website/assets/audio/model-accepts-settings/25-35.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/26-20.mp3 b/cecli/website/assets/audio/model-accepts-settings/26-20.mp3 similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/26-20.mp3 rename to cecli/website/assets/audio/model-accepts-settings/26-20.mp3 diff --git a/aider/website/assets/audio/model-accepts-settings/metadata.json b/cecli/website/assets/audio/model-accepts-settings/metadata.json similarity index 100% rename from aider/website/assets/audio/model-accepts-settings/metadata.json rename to cecli/website/assets/audio/model-accepts-settings/metadata.json diff --git a/aider/website/assets/audio/tree-sitter-language-pack/00-01.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/00-01.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/00-01.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/00-01.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/00-10.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/00-10.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/00-10.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/00-10.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/01-00.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/01-00.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/01-00.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/01-00.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/01-10.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/01-10.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/01-10.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/01-10.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/01-29.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/01-29.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/01-29.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/01-29.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/01-45.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/01-45.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/01-45.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/01-45.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/02-05.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/02-05.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/02-05.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/02-05.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/03-37.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/03-37.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/03-37.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/03-37.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/04-19.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/04-19.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/04-19.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/04-19.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/05-02.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/05-02.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/05-02.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/05-02.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/05-55.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/05-55.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/05-55.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/05-55.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/06-12.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/06-12.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/06-12.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/06-12.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/06-30.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/06-30.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/06-30.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/06-30.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/09-02.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/09-02.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/09-02.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/09-02.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/09-45.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/09-45.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/09-45.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/09-45.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/10-15.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/10-15.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/10-15.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/10-15.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/11-15.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/11-15.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/11-15.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/11-15.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/12-00.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/12-00.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/12-00.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/12-00.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/13-00.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/13-00.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/13-00.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/13-00.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/14-00.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/14-00.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/14-00.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/14-00.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/16-07.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/16-07.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/16-07.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/16-07.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/16-16.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/16-16.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/16-16.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/16-16.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/16-33.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/16-33.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/16-33.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/16-33.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/17-01.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/17-01.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/17-01.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/17-01.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/17-12.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/17-12.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/17-12.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/17-12.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/19-04.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/19-04.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/19-04.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/19-04.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/19-28.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/19-28.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/19-28.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/19-28.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/20-20.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/20-20.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/20-20.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/20-20.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/20-50.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/20-50.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/20-50.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/20-50.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/21-30.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/21-30.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/21-30.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/21-30.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/24-39.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/24-39.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/24-39.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/24-39.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/26-55.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/26-55.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/26-55.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/26-55.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/27-10.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/27-10.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/27-10.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/27-10.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/27-19.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/27-19.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/27-19.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/27-19.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/27-50.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/27-50.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/27-50.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/27-50.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/28-12.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/28-12.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/28-12.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/28-12.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/28-52.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/28-52.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/28-52.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/28-52.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/29-27.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/29-27.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/29-27.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/29-27.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/30-25.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/30-25.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/30-25.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/30-25.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/30-37.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/30-37.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/30-37.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/30-37.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/31-52.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/31-52.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/31-52.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/31-52.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/32-27.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/32-27.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/32-27.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/32-27.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/32-36.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/32-36.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/32-36.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/32-36.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/32-42.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/32-42.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/32-42.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/32-42.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/32-54.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/32-54.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/32-54.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/32-54.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/33-05.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/33-05.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/33-05.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/33-05.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/33-20.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/33-20.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/33-20.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/33-20.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/34-10.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/34-10.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/34-10.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/34-10.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/35-00.mp3 b/cecli/website/assets/audio/tree-sitter-language-pack/35-00.mp3 similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/35-00.mp3 rename to cecli/website/assets/audio/tree-sitter-language-pack/35-00.mp3 diff --git a/aider/website/assets/audio/tree-sitter-language-pack/metadata.json b/cecli/website/assets/audio/tree-sitter-language-pack/metadata.json similarity index 100% rename from aider/website/assets/audio/tree-sitter-language-pack/metadata.json rename to cecli/website/assets/audio/tree-sitter-language-pack/metadata.json diff --git a/aider/website/assets/azure-deployment.png b/cecli/website/assets/azure-deployment.png similarity index 100% rename from aider/website/assets/azure-deployment.png rename to cecli/website/assets/azure-deployment.png diff --git a/aider/website/assets/benchmarks-0125.jpg b/cecli/website/assets/benchmarks-0125.jpg similarity index 100% rename from aider/website/assets/benchmarks-0125.jpg rename to cecli/website/assets/benchmarks-0125.jpg diff --git a/aider/website/assets/benchmarks-0125.svg b/cecli/website/assets/benchmarks-0125.svg similarity index 100% rename from aider/website/assets/benchmarks-0125.svg rename to cecli/website/assets/benchmarks-0125.svg diff --git a/aider/website/assets/benchmarks-1106.jpg b/cecli/website/assets/benchmarks-1106.jpg similarity index 100% rename from aider/website/assets/benchmarks-1106.jpg rename to cecli/website/assets/benchmarks-1106.jpg diff --git a/aider/website/assets/benchmarks-1106.svg b/cecli/website/assets/benchmarks-1106.svg similarity index 100% rename from aider/website/assets/benchmarks-1106.svg rename to cecli/website/assets/benchmarks-1106.svg diff --git a/aider/website/assets/benchmarks-speed-1106.jpg b/cecli/website/assets/benchmarks-speed-1106.jpg similarity index 100% rename from aider/website/assets/benchmarks-speed-1106.jpg rename to cecli/website/assets/benchmarks-speed-1106.jpg diff --git a/aider/website/assets/benchmarks-speed-1106.svg b/cecli/website/assets/benchmarks-speed-1106.svg similarity index 100% rename from aider/website/assets/benchmarks-speed-1106.svg rename to cecli/website/assets/benchmarks-speed-1106.svg diff --git a/aider/website/assets/benchmarks-udiff.jpg b/cecli/website/assets/benchmarks-udiff.jpg similarity index 100% rename from aider/website/assets/benchmarks-udiff.jpg rename to cecli/website/assets/benchmarks-udiff.jpg diff --git a/aider/website/assets/benchmarks-udiff.svg b/cecli/website/assets/benchmarks-udiff.svg similarity index 100% rename from aider/website/assets/benchmarks-udiff.svg rename to cecli/website/assets/benchmarks-udiff.svg diff --git a/aider/website/assets/benchmarks.jpg b/cecli/website/assets/benchmarks.jpg similarity index 100% rename from aider/website/assets/benchmarks.jpg rename to cecli/website/assets/benchmarks.jpg diff --git a/aider/website/assets/benchmarks.svg b/cecli/website/assets/benchmarks.svg similarity index 100% rename from aider/website/assets/benchmarks.svg rename to cecli/website/assets/benchmarks.svg diff --git a/aider/website/assets/blame.jpg b/cecli/website/assets/blame.jpg similarity index 100% rename from aider/website/assets/blame.jpg rename to cecli/website/assets/blame.jpg diff --git a/aider/website/assets/browser.jpg b/cecli/website/assets/browser.jpg similarity index 100% rename from aider/website/assets/browser.jpg rename to cecli/website/assets/browser.jpg diff --git a/aider/website/assets/code-in-json.jpg b/cecli/website/assets/code-in-json.jpg similarity index 100% rename from aider/website/assets/code-in-json.jpg rename to cecli/website/assets/code-in-json.jpg diff --git a/aider/website/assets/codespaces.jpg b/cecli/website/assets/codespaces.jpg similarity index 100% rename from aider/website/assets/codespaces.jpg rename to cecli/website/assets/codespaces.jpg diff --git a/aider/website/assets/codespaces.mp4 b/cecli/website/assets/codespaces.mp4 similarity index 100% rename from aider/website/assets/codespaces.mp4 rename to cecli/website/assets/codespaces.mp4 diff --git a/aider/website/assets/copypaste.jpg b/cecli/website/assets/copypaste.jpg similarity index 100% rename from aider/website/assets/copypaste.jpg rename to cecli/website/assets/copypaste.jpg diff --git a/aider/website/assets/copypaste.mp4 b/cecli/website/assets/copypaste.mp4 similarity index 100% rename from aider/website/assets/copypaste.mp4 rename to cecli/website/assets/copypaste.mp4 diff --git a/aider/website/assets/figure.png b/cecli/website/assets/figure.png similarity index 100% rename from aider/website/assets/figure.png rename to cecli/website/assets/figure.png diff --git a/aider/website/assets/home.css b/cecli/website/assets/home.css similarity index 100% rename from aider/website/assets/home.css rename to cecli/website/assets/home.css diff --git a/aider/website/assets/icons/android-chrome-192x192.png b/cecli/website/assets/icons/android-chrome-192x192.png similarity index 100% rename from aider/website/assets/icons/android-chrome-192x192.png rename to cecli/website/assets/icons/android-chrome-192x192.png diff --git a/aider/website/assets/icons/android-chrome-384x384.png b/cecli/website/assets/icons/android-chrome-384x384.png similarity index 100% rename from aider/website/assets/icons/android-chrome-384x384.png rename to cecli/website/assets/icons/android-chrome-384x384.png diff --git a/aider/website/assets/icons/apple-touch-icon.png b/cecli/website/assets/icons/apple-touch-icon.png similarity index 100% rename from aider/website/assets/icons/apple-touch-icon.png rename to cecli/website/assets/icons/apple-touch-icon.png diff --git a/aider/website/assets/icons/brain.svg b/cecli/website/assets/icons/brain.svg similarity index 100% rename from aider/website/assets/icons/brain.svg rename to cecli/website/assets/icons/brain.svg diff --git a/aider/website/assets/icons/browserconfig.xml b/cecli/website/assets/icons/browserconfig.xml similarity index 100% rename from aider/website/assets/icons/browserconfig.xml rename to cecli/website/assets/icons/browserconfig.xml diff --git a/aider/website/assets/icons/check-all.svg b/cecli/website/assets/icons/check-all.svg similarity index 100% rename from aider/website/assets/icons/check-all.svg rename to cecli/website/assets/icons/check-all.svg diff --git a/aider/website/assets/icons/code-tags.svg b/cecli/website/assets/icons/code-tags.svg similarity index 100% rename from aider/website/assets/icons/code-tags.svg rename to cecli/website/assets/icons/code-tags.svg diff --git a/aider/website/assets/icons/content-copy.svg b/cecli/website/assets/icons/content-copy.svg similarity index 100% rename from aider/website/assets/icons/content-copy.svg rename to cecli/website/assets/icons/content-copy.svg diff --git a/aider/website/assets/icons/favicon-16x16.png b/cecli/website/assets/icons/favicon-16x16.png similarity index 100% rename from aider/website/assets/icons/favicon-16x16.png rename to cecli/website/assets/icons/favicon-16x16.png diff --git a/aider/website/assets/icons/favicon-32x32.png b/cecli/website/assets/icons/favicon-32x32.png similarity index 100% rename from aider/website/assets/icons/favicon-32x32.png rename to cecli/website/assets/icons/favicon-32x32.png diff --git a/aider/website/assets/icons/favicon.ico b/cecli/website/assets/icons/favicon.ico similarity index 100% rename from aider/website/assets/icons/favicon.ico rename to cecli/website/assets/icons/favicon.ico diff --git a/aider/website/assets/icons/image-multiple.svg b/cecli/website/assets/icons/image-multiple.svg similarity index 100% rename from aider/website/assets/icons/image-multiple.svg rename to cecli/website/assets/icons/image-multiple.svg diff --git a/aider/website/assets/icons/map-outline.svg b/cecli/website/assets/icons/map-outline.svg similarity index 100% rename from aider/website/assets/icons/map-outline.svg rename to cecli/website/assets/icons/map-outline.svg diff --git a/aider/website/assets/icons/microphone.svg b/cecli/website/assets/icons/microphone.svg similarity index 100% rename from aider/website/assets/icons/microphone.svg rename to cecli/website/assets/icons/microphone.svg diff --git a/aider/website/assets/icons/monitor.svg b/cecli/website/assets/icons/monitor.svg similarity index 100% rename from aider/website/assets/icons/monitor.svg rename to cecli/website/assets/icons/monitor.svg diff --git a/aider/website/assets/icons/mstile-150x150.png b/cecli/website/assets/icons/mstile-150x150.png similarity index 100% rename from aider/website/assets/icons/mstile-150x150.png rename to cecli/website/assets/icons/mstile-150x150.png diff --git a/aider/website/assets/icons/safari-pinned-tab.svg b/cecli/website/assets/icons/safari-pinned-tab.svg similarity index 100% rename from aider/website/assets/icons/safari-pinned-tab.svg rename to cecli/website/assets/icons/safari-pinned-tab.svg diff --git a/aider/website/assets/icons/site.webmanifest b/cecli/website/assets/icons/site.webmanifest similarity index 100% rename from aider/website/assets/icons/site.webmanifest rename to cecli/website/assets/icons/site.webmanifest diff --git a/aider/website/assets/icons/source-branch.svg b/cecli/website/assets/icons/source-branch.svg similarity index 100% rename from aider/website/assets/icons/source-branch.svg rename to cecli/website/assets/icons/source-branch.svg diff --git a/aider/website/assets/install.jpg b/cecli/website/assets/install.jpg similarity index 100% rename from aider/website/assets/install.jpg rename to cecli/website/assets/install.jpg diff --git a/aider/website/assets/install.mp4 b/cecli/website/assets/install.mp4 similarity index 100% rename from aider/website/assets/install.mp4 rename to cecli/website/assets/install.mp4 diff --git a/aider/website/assets/leaderboard.jpg b/cecli/website/assets/leaderboard.jpg similarity index 100% rename from aider/website/assets/leaderboard.jpg rename to cecli/website/assets/leaderboard.jpg diff --git a/aider/website/assets/linting.jpg b/cecli/website/assets/linting.jpg similarity index 100% rename from aider/website/assets/linting.jpg rename to cecli/website/assets/linting.jpg diff --git a/aider/website/assets/llms.jpg b/cecli/website/assets/llms.jpg similarity index 100% rename from aider/website/assets/llms.jpg rename to cecli/website/assets/llms.jpg diff --git a/aider/website/assets/logo.svg b/cecli/website/assets/logo.svg similarity index 100% rename from aider/website/assets/logo.svg rename to cecli/website/assets/logo.svg diff --git a/aider/website/assets/models-over-time.png b/cecli/website/assets/models-over-time.png similarity index 100% rename from aider/website/assets/models-over-time.png rename to cecli/website/assets/models-over-time.png diff --git a/aider/website/assets/models-over-time.svg b/cecli/website/assets/models-over-time.svg similarity index 100% rename from aider/website/assets/models-over-time.svg rename to cecli/website/assets/models-over-time.svg diff --git a/aider/website/assets/o1-polyglot.jpg b/cecli/website/assets/o1-polyglot.jpg similarity index 100% rename from aider/website/assets/o1-polyglot.jpg rename to cecli/website/assets/o1-polyglot.jpg diff --git a/aider/website/assets/prompt-caching.jpg b/cecli/website/assets/prompt-caching.jpg similarity index 100% rename from aider/website/assets/prompt-caching.jpg rename to cecli/website/assets/prompt-caching.jpg diff --git a/aider/website/assets/quantization.jpg b/cecli/website/assets/quantization.jpg similarity index 100% rename from aider/website/assets/quantization.jpg rename to cecli/website/assets/quantization.jpg diff --git a/aider/website/assets/qwq.jpg b/cecli/website/assets/qwq.jpg similarity index 100% rename from aider/website/assets/qwq.jpg rename to cecli/website/assets/qwq.jpg diff --git a/aider/website/assets/r1-sonnet-sota.jpg b/cecli/website/assets/r1-sonnet-sota.jpg similarity index 100% rename from aider/website/assets/r1-sonnet-sota.jpg rename to cecli/website/assets/r1-sonnet-sota.jpg diff --git a/aider/website/assets/recordings.jpg b/cecli/website/assets/recordings.jpg similarity index 100% rename from aider/website/assets/recordings.jpg rename to cecli/website/assets/recordings.jpg diff --git a/aider/website/assets/robot-ast.png b/cecli/website/assets/robot-ast.png similarity index 100% rename from aider/website/assets/robot-ast.png rename to cecli/website/assets/robot-ast.png diff --git a/aider/website/assets/robot-flowchart.png b/cecli/website/assets/robot-flowchart.png similarity index 100% rename from aider/website/assets/robot-flowchart.png rename to cecli/website/assets/robot-flowchart.png diff --git a/aider/website/assets/sample-analytics.jsonl b/cecli/website/assets/sample-analytics.jsonl similarity index 100% rename from aider/website/assets/sample-analytics.jsonl rename to cecli/website/assets/sample-analytics.jsonl diff --git a/aider/website/assets/sample.aider.conf.yml b/cecli/website/assets/sample.aider.conf.yml similarity index 100% rename from aider/website/assets/sample.aider.conf.yml rename to cecli/website/assets/sample.aider.conf.yml diff --git a/aider/website/assets/sample.env b/cecli/website/assets/sample.env similarity index 100% rename from aider/website/assets/sample.env rename to cecli/website/assets/sample.env diff --git a/aider/website/assets/screencast.svg b/cecli/website/assets/screencast.svg similarity index 100% rename from aider/website/assets/screencast.svg rename to cecli/website/assets/screencast.svg diff --git a/aider/website/assets/screenshot.png b/cecli/website/assets/screenshot.png similarity index 100% rename from aider/website/assets/screenshot.png rename to cecli/website/assets/screenshot.png diff --git a/aider/website/assets/self-assembly.jpg b/cecli/website/assets/self-assembly.jpg similarity index 100% rename from aider/website/assets/self-assembly.jpg rename to cecli/website/assets/self-assembly.jpg diff --git a/aider/website/assets/shell-cmds-small.mp4 b/cecli/website/assets/shell-cmds-small.mp4 similarity index 100% rename from aider/website/assets/shell-cmds-small.mp4 rename to cecli/website/assets/shell-cmds-small.mp4 diff --git a/aider/website/assets/shell-cmds.jpg b/cecli/website/assets/shell-cmds.jpg similarity index 100% rename from aider/website/assets/shell-cmds.jpg rename to cecli/website/assets/shell-cmds.jpg diff --git a/aider/website/assets/sonnet-not-lazy.jpg b/cecli/website/assets/sonnet-not-lazy.jpg similarity index 100% rename from aider/website/assets/sonnet-not-lazy.jpg rename to cecli/website/assets/sonnet-not-lazy.jpg diff --git a/aider/website/assets/sonnet-seems-fine.jpg b/cecli/website/assets/sonnet-seems-fine.jpg similarity index 100% rename from aider/website/assets/sonnet-seems-fine.jpg rename to cecli/website/assets/sonnet-seems-fine.jpg diff --git a/aider/website/assets/swe_bench.jpg b/cecli/website/assets/swe_bench.jpg similarity index 100% rename from aider/website/assets/swe_bench.jpg rename to cecli/website/assets/swe_bench.jpg diff --git a/aider/website/assets/swe_bench.svg b/cecli/website/assets/swe_bench.svg similarity index 100% rename from aider/website/assets/swe_bench.svg rename to cecli/website/assets/swe_bench.svg diff --git a/aider/website/assets/swe_bench_lite.jpg b/cecli/website/assets/swe_bench_lite.jpg similarity index 100% rename from aider/website/assets/swe_bench_lite.jpg rename to cecli/website/assets/swe_bench_lite.jpg diff --git a/aider/website/assets/swe_bench_lite.svg b/cecli/website/assets/swe_bench_lite.svg similarity index 100% rename from aider/website/assets/swe_bench_lite.svg rename to cecli/website/assets/swe_bench_lite.svg diff --git a/aider/website/assets/thinking.jpg b/cecli/website/assets/thinking.jpg similarity index 100% rename from aider/website/assets/thinking.jpg rename to cecli/website/assets/thinking.jpg diff --git a/aider/website/assets/udiffs.jpg b/cecli/website/assets/udiffs.jpg similarity index 100% rename from aider/website/assets/udiffs.jpg rename to cecli/website/assets/udiffs.jpg diff --git a/aider/website/assets/watch.jpg b/cecli/website/assets/watch.jpg similarity index 100% rename from aider/website/assets/watch.jpg rename to cecli/website/assets/watch.jpg diff --git a/aider/website/assets/watch.mp4 b/cecli/website/assets/watch.mp4 similarity index 100% rename from aider/website/assets/watch.mp4 rename to cecli/website/assets/watch.mp4 diff --git a/aider/website/blog/index.html b/cecli/website/blog/index.html similarity index 100% rename from aider/website/blog/index.html rename to cecli/website/blog/index.html diff --git a/aider/website/docs/benchmarks-0125.md b/cecli/website/docs/benchmarks-0125.md similarity index 100% rename from aider/website/docs/benchmarks-0125.md rename to cecli/website/docs/benchmarks-0125.md diff --git a/aider/website/docs/benchmarks-1106.md b/cecli/website/docs/benchmarks-1106.md similarity index 100% rename from aider/website/docs/benchmarks-1106.md rename to cecli/website/docs/benchmarks-1106.md diff --git a/aider/website/docs/benchmarks-speed-1106.md b/cecli/website/docs/benchmarks-speed-1106.md similarity index 100% rename from aider/website/docs/benchmarks-speed-1106.md rename to cecli/website/docs/benchmarks-speed-1106.md diff --git a/aider/website/docs/benchmarks.md b/cecli/website/docs/benchmarks.md similarity index 100% rename from aider/website/docs/benchmarks.md rename to cecli/website/docs/benchmarks.md diff --git a/aider/website/docs/config.md b/cecli/website/docs/config.md similarity index 100% rename from aider/website/docs/config.md rename to cecli/website/docs/config.md diff --git a/aider/website/docs/config/adv-model-settings.md b/cecli/website/docs/config/adv-model-settings.md similarity index 100% rename from aider/website/docs/config/adv-model-settings.md rename to cecli/website/docs/config/adv-model-settings.md diff --git a/aider/website/docs/config/agent-mode.md b/cecli/website/docs/config/agent-mode.md similarity index 95% rename from aider/website/docs/config/agent-mode.md rename to cecli/website/docs/config/agent-mode.md index 2300406a053..30100782ced 100644 --- a/aider/website/docs/config/agent-mode.md +++ b/cecli/website/docs/config/agent-mode.md @@ -1,6 +1,6 @@ # Agent Mode -Agent Mode is an operational mode in aider-ce that enables autonomous codebase exploration and modification using local tools. Instead of relying on traditional edit formats, Agent Mode uses a tool-based approach where the LLM can discover, analyze, and modify files through a series of tool calls. +Agent Mode is an operational mode in cecli that enables autonomous codebase exploration and modification using local tools. Instead of relying on traditional edit formats, Agent Mode uses a tool-based approach where the LLM can discover, analyze, and modify files through a series of tool calls. Agent Mode can be activated in the following ways @@ -13,7 +13,7 @@ In the interface: In the command line: ``` -aider-ce ... --agent +cecli ... --agent ``` In the configuration files: @@ -202,7 +202,7 @@ The following context blocks are available by default and can be customized usin When `include_context_blocks` is specified, only the listed blocks will be included. When `exclude_context_blocks` is specified, the listed blocks will be removed from the default set. -#### Other Aider-CE CLI/Config Options for Agent Mode +#### Other CECLI CLI/Config Options for Agent Mode - `use-enhanced-map` - Use enhanced repo map that takes into account import relationships between files @@ -247,7 +247,7 @@ This configuration system allows for fine-grained control over which tools are a Agent Mode includes a powerful skills system that allows you to extend the AI's capabilities with custom instructions, reference materials, scripts, and assets. Skills are configured through the `agent-config` parameter in the YAML configuration file. -For complete documentation on creating and using skills, including skill directory structure, SKILL.md format, and best practices, see the [Skills documentation](https://github.com/dwash96/aider-ce/blob/main/aider/website/docs/config/skills.md). +For complete documentation on creating and using skills, including skill directory structure, SKILL.md format, and best practices, see the [Skills documentation](https://github.com/dwash96/cecli/blob/main/aider/website/docs/config/skills.md). ### Benefits diff --git a/aider/website/docs/config/aider_conf.md b/cecli/website/docs/config/aider_conf.md similarity index 100% rename from aider/website/docs/config/aider_conf.md rename to cecli/website/docs/config/aider_conf.md diff --git a/aider/website/docs/config/api-keys.md b/cecli/website/docs/config/api-keys.md similarity index 100% rename from aider/website/docs/config/api-keys.md rename to cecli/website/docs/config/api-keys.md diff --git a/aider/website/docs/config/dotenv.md b/cecli/website/docs/config/dotenv.md similarity index 100% rename from aider/website/docs/config/dotenv.md rename to cecli/website/docs/config/dotenv.md diff --git a/aider/website/docs/config/editor.md b/cecli/website/docs/config/editor.md similarity index 100% rename from aider/website/docs/config/editor.md rename to cecli/website/docs/config/editor.md diff --git a/aider/website/docs/config/mcp.md b/cecli/website/docs/config/mcp.md similarity index 98% rename from aider/website/docs/config/mcp.md rename to cecli/website/docs/config/mcp.md index 811c6a7a658..8d67ee28464 100644 --- a/aider/website/docs/config/mcp.md +++ b/cecli/website/docs/config/mcp.md @@ -18,7 +18,7 @@ You have two ways of sharing your MCP server configuration with Aider. {: .note } -> Today, Aider-CE/Cecli supports connecting to MCP servers using stdio, http, and sse transports. +> Today, CECLI/Cecli supports connecting to MCP servers using stdio, http, and sse transports. ### Config Files diff --git a/aider/website/docs/config/model-aliases.md b/cecli/website/docs/config/model-aliases.md similarity index 93% rename from aider/website/docs/config/model-aliases.md rename to cecli/website/docs/config/model-aliases.md index f87542ee60e..5a55fcfb2a2 100644 --- a/aider/website/docs/config/model-aliases.md +++ b/cecli/website/docs/config/model-aliases.md @@ -97,7 +97,7 @@ for alias, model in sorted(MODEL_ALIASES.items()): ## Advanced Model Settings -Aider-CE/Cecli supports model names with colon-separated suffixes (e.g., `gpt-5:high`) that map to additional configuration parameters defined in the relevant config.yml file. This allows you to create named configurations for different use cases. These configurations map precisely to the LiteLLM `completion()` method parameters [here](https://docs.litellm.ai/docs/completion/input), though more are supported for specific models and providers. +CECLI/Cecli supports model names with colon-separated suffixes (e.g., `gpt-5:high`) that map to additional configuration parameters defined in the relevant config.yml file. This allows you to create named configurations for different use cases. These configurations map precisely to the LiteLLM `completion()` method parameters [here](https://docs.litellm.ai/docs/completion/input), though more are supported for specific models and providers. ### Configuration File diff --git a/aider/website/docs/config/options.md b/cecli/website/docs/config/options.md similarity index 100% rename from aider/website/docs/config/options.md rename to cecli/website/docs/config/options.md diff --git a/aider/website/docs/config/reasoning.md b/cecli/website/docs/config/reasoning.md similarity index 100% rename from aider/website/docs/config/reasoning.md rename to cecli/website/docs/config/reasoning.md diff --git a/aider/website/docs/config/skills.md b/cecli/website/docs/config/skills.md similarity index 100% rename from aider/website/docs/config/skills.md rename to cecli/website/docs/config/skills.md diff --git a/aider/website/docs/config/tui.md b/cecli/website/docs/config/tui.md similarity index 98% rename from aider/website/docs/config/tui.md rename to cecli/website/docs/config/tui.md index 7843a3681cb..c3f4f9e05b0 100644 --- a/aider/website/docs/config/tui.md +++ b/cecli/website/docs/config/tui.md @@ -6,7 +6,7 @@ TUI (Textual User Interface) Mode provides a modern, visually rich terminal inte Command line: ``` -aider-ce ... --tui +cecli ... --tui ### OR! @@ -100,7 +100,7 @@ All key bindings use Textual's key syntax: ## Integration with Other Modes -TUI Mode works seamlessly with other aider-ce features: +TUI Mode works seamlessly with other cecli features: - **Agent Mode**: Visual feedback for tool calls and autonomous operations - **Skills**: Clean display of skill outputs and interactions diff --git a/aider/website/docs/ctags.md b/cecli/website/docs/ctags.md similarity index 100% rename from aider/website/docs/ctags.md rename to cecli/website/docs/ctags.md diff --git a/aider/website/docs/faq.md b/cecli/website/docs/faq.md similarity index 100% rename from aider/website/docs/faq.md rename to cecli/website/docs/faq.md diff --git a/aider/website/docs/git.md b/cecli/website/docs/git.md similarity index 100% rename from aider/website/docs/git.md rename to cecli/website/docs/git.md diff --git a/aider/website/docs/index.md b/cecli/website/docs/index.md similarity index 100% rename from aider/website/docs/index.md rename to cecli/website/docs/index.md diff --git a/aider/website/docs/install.md b/cecli/website/docs/install.md similarity index 100% rename from aider/website/docs/install.md rename to cecli/website/docs/install.md diff --git a/aider/website/docs/install/codespaces.md b/cecli/website/docs/install/codespaces.md similarity index 100% rename from aider/website/docs/install/codespaces.md rename to cecli/website/docs/install/codespaces.md diff --git a/aider/website/docs/install/docker.md b/cecli/website/docs/install/docker.md similarity index 87% rename from aider/website/docs/install/docker.md rename to cecli/website/docs/install/docker.md index cea11721761..6996103c65f 100644 --- a/aider/website/docs/install/docker.md +++ b/cecli/website/docs/install/docker.md @@ -7,16 +7,16 @@ nav_order: 100 Cecli is available as a docker image: -- `dustinwashington/aider-ce` installs the cecli core, a smaller image that's good to get started quickly. +- `dustinwashington/cecli` installs the cecli core, a smaller image that's good to get started quickly. ### Aider core ```bash -docker pull dustinwashington/aider-ce +docker pull dustinwashington/cecli docker run \ -it \ --user $(id -u):$(id -g) \ - --volume $(pwd):/app dustinwashington/aider-ce \ + --volume $(pwd):/app dustinwashington/cecli \ --volume $(pwd)/.aider.conf.yml:/.aider.conf.yml \ --volume $(pwd)/.aider.env:/.aider/.env \ [...other args...] \ diff --git a/aider/website/docs/install/optional.md b/cecli/website/docs/install/optional.md similarity index 100% rename from aider/website/docs/install/optional.md rename to cecli/website/docs/install/optional.md diff --git a/aider/website/docs/install/replit.md b/cecli/website/docs/install/replit.md similarity index 100% rename from aider/website/docs/install/replit.md rename to cecli/website/docs/install/replit.md diff --git a/aider/website/docs/languages.md b/cecli/website/docs/languages.md similarity index 99% rename from aider/website/docs/languages.md rename to cecli/website/docs/languages.md index 9bf2f20a793..b03024da798 100644 --- a/aider/website/docs/languages.md +++ b/cecli/website/docs/languages.md @@ -80,7 +80,7 @@ cog.out(get_supported_languages_md()) | clojure | .clj | ✓ | ✓ | | clojure | .cljc | ✓ | ✓ | | clojure | .cljs | ✓ | ✓ | -| clojure | .edn | ✓ | ✓ | +| clojure | .end | ✓ | ✓ | | cmake | .cmake | | ✓ | | cmake | CMakeLists.txt | | ✓ | | commonlisp | .cl | ✓ | ✓ | diff --git a/aider/website/docs/leaderboards/by-release-date.md b/cecli/website/docs/leaderboards/by-release-date.md similarity index 100% rename from aider/website/docs/leaderboards/by-release-date.md rename to cecli/website/docs/leaderboards/by-release-date.md diff --git a/aider/website/docs/leaderboards/contrib.md b/cecli/website/docs/leaderboards/contrib.md similarity index 100% rename from aider/website/docs/leaderboards/contrib.md rename to cecli/website/docs/leaderboards/contrib.md diff --git a/aider/website/docs/leaderboards/edit.md b/cecli/website/docs/leaderboards/edit.md similarity index 100% rename from aider/website/docs/leaderboards/edit.md rename to cecli/website/docs/leaderboards/edit.md diff --git a/aider/website/docs/leaderboards/index.md b/cecli/website/docs/leaderboards/index.md similarity index 100% rename from aider/website/docs/leaderboards/index.md rename to cecli/website/docs/leaderboards/index.md diff --git a/aider/website/docs/leaderboards/notes.md b/cecli/website/docs/leaderboards/notes.md similarity index 100% rename from aider/website/docs/leaderboards/notes.md rename to cecli/website/docs/leaderboards/notes.md diff --git a/aider/website/docs/leaderboards/refactor.md b/cecli/website/docs/leaderboards/refactor.md similarity index 100% rename from aider/website/docs/leaderboards/refactor.md rename to cecli/website/docs/leaderboards/refactor.md diff --git a/aider/website/docs/legal/contributor-agreement.md b/cecli/website/docs/legal/contributor-agreement.md similarity index 100% rename from aider/website/docs/legal/contributor-agreement.md rename to cecli/website/docs/legal/contributor-agreement.md diff --git a/aider/website/docs/legal/privacy.md b/cecli/website/docs/legal/privacy.md similarity index 100% rename from aider/website/docs/legal/privacy.md rename to cecli/website/docs/legal/privacy.md diff --git a/aider/website/docs/llms.md b/cecli/website/docs/llms.md similarity index 100% rename from aider/website/docs/llms.md rename to cecli/website/docs/llms.md diff --git a/aider/website/docs/llms/anthropic.md b/cecli/website/docs/llms/anthropic.md similarity index 100% rename from aider/website/docs/llms/anthropic.md rename to cecli/website/docs/llms/anthropic.md diff --git a/aider/website/docs/llms/azure.md b/cecli/website/docs/llms/azure.md similarity index 100% rename from aider/website/docs/llms/azure.md rename to cecli/website/docs/llms/azure.md diff --git a/aider/website/docs/llms/bedrock.md b/cecli/website/docs/llms/bedrock.md similarity index 100% rename from aider/website/docs/llms/bedrock.md rename to cecli/website/docs/llms/bedrock.md diff --git a/aider/website/docs/llms/cohere.md b/cecli/website/docs/llms/cohere.md similarity index 100% rename from aider/website/docs/llms/cohere.md rename to cecli/website/docs/llms/cohere.md diff --git a/aider/website/docs/llms/deepseek.md b/cecli/website/docs/llms/deepseek.md similarity index 100% rename from aider/website/docs/llms/deepseek.md rename to cecli/website/docs/llms/deepseek.md diff --git a/aider/website/docs/llms/gemini.md b/cecli/website/docs/llms/gemini.md similarity index 100% rename from aider/website/docs/llms/gemini.md rename to cecli/website/docs/llms/gemini.md diff --git a/aider/website/docs/llms/github.md b/cecli/website/docs/llms/github.md similarity index 100% rename from aider/website/docs/llms/github.md rename to cecli/website/docs/llms/github.md diff --git a/aider/website/docs/llms/groq.md b/cecli/website/docs/llms/groq.md similarity index 100% rename from aider/website/docs/llms/groq.md rename to cecli/website/docs/llms/groq.md diff --git a/aider/website/docs/llms/lm-studio.md b/cecli/website/docs/llms/lm-studio.md similarity index 100% rename from aider/website/docs/llms/lm-studio.md rename to cecli/website/docs/llms/lm-studio.md diff --git a/aider/website/docs/llms/ollama.md b/cecli/website/docs/llms/ollama.md similarity index 100% rename from aider/website/docs/llms/ollama.md rename to cecli/website/docs/llms/ollama.md diff --git a/aider/website/docs/llms/openai-compat.md b/cecli/website/docs/llms/openai-compat.md similarity index 100% rename from aider/website/docs/llms/openai-compat.md rename to cecli/website/docs/llms/openai-compat.md diff --git a/aider/website/docs/llms/openai.md b/cecli/website/docs/llms/openai.md similarity index 100% rename from aider/website/docs/llms/openai.md rename to cecli/website/docs/llms/openai.md diff --git a/aider/website/docs/llms/openrouter.md b/cecli/website/docs/llms/openrouter.md similarity index 100% rename from aider/website/docs/llms/openrouter.md rename to cecli/website/docs/llms/openrouter.md diff --git a/aider/website/docs/llms/other.md b/cecli/website/docs/llms/other.md similarity index 100% rename from aider/website/docs/llms/other.md rename to cecli/website/docs/llms/other.md diff --git a/aider/website/docs/llms/vertex.md b/cecli/website/docs/llms/vertex.md similarity index 100% rename from aider/website/docs/llms/vertex.md rename to cecli/website/docs/llms/vertex.md diff --git a/aider/website/docs/llms/warnings.md b/cecli/website/docs/llms/warnings.md similarity index 100% rename from aider/website/docs/llms/warnings.md rename to cecli/website/docs/llms/warnings.md diff --git a/aider/website/docs/llms/xai.md b/cecli/website/docs/llms/xai.md similarity index 100% rename from aider/website/docs/llms/xai.md rename to cecli/website/docs/llms/xai.md diff --git a/aider/website/docs/more-info.md b/cecli/website/docs/more-info.md similarity index 100% rename from aider/website/docs/more-info.md rename to cecli/website/docs/more-info.md diff --git a/aider/website/docs/more/analytics.md b/cecli/website/docs/more/analytics.md similarity index 100% rename from aider/website/docs/more/analytics.md rename to cecli/website/docs/more/analytics.md diff --git a/aider/website/docs/more/edit-formats.md b/cecli/website/docs/more/edit-formats.md similarity index 100% rename from aider/website/docs/more/edit-formats.md rename to cecli/website/docs/more/edit-formats.md diff --git a/aider/website/docs/more/infinite-output.md b/cecli/website/docs/more/infinite-output.md similarity index 100% rename from aider/website/docs/more/infinite-output.md rename to cecli/website/docs/more/infinite-output.md diff --git a/aider/website/docs/recordings/auto-accept-architect.md b/cecli/website/docs/recordings/auto-accept-architect.md similarity index 100% rename from aider/website/docs/recordings/auto-accept-architect.md rename to cecli/website/docs/recordings/auto-accept-architect.md diff --git a/aider/website/docs/recordings/dont-drop-original-read-files.md b/cecli/website/docs/recordings/dont-drop-original-read-files.md similarity index 100% rename from aider/website/docs/recordings/dont-drop-original-read-files.md rename to cecli/website/docs/recordings/dont-drop-original-read-files.md diff --git a/aider/website/docs/recordings/index.md b/cecli/website/docs/recordings/index.md similarity index 100% rename from aider/website/docs/recordings/index.md rename to cecli/website/docs/recordings/index.md diff --git a/aider/website/docs/recordings/model-accepts-settings.md b/cecli/website/docs/recordings/model-accepts-settings.md similarity index 100% rename from aider/website/docs/recordings/model-accepts-settings.md rename to cecli/website/docs/recordings/model-accepts-settings.md diff --git a/aider/website/docs/recordings/tree-sitter-language-pack.md b/cecli/website/docs/recordings/tree-sitter-language-pack.md similarity index 100% rename from aider/website/docs/recordings/tree-sitter-language-pack.md rename to cecli/website/docs/recordings/tree-sitter-language-pack.md diff --git a/aider/website/docs/repomap.md b/cecli/website/docs/repomap.md similarity index 100% rename from aider/website/docs/repomap.md rename to cecli/website/docs/repomap.md diff --git a/aider/website/docs/scripting.md b/cecli/website/docs/scripting.md similarity index 100% rename from aider/website/docs/scripting.md rename to cecli/website/docs/scripting.md diff --git a/aider/website/docs/sessions.md b/cecli/website/docs/sessions.md similarity index 100% rename from aider/website/docs/sessions.md rename to cecli/website/docs/sessions.md diff --git a/aider/website/docs/troubleshooting.md b/cecli/website/docs/troubleshooting.md similarity index 100% rename from aider/website/docs/troubleshooting.md rename to cecli/website/docs/troubleshooting.md diff --git a/aider/website/docs/troubleshooting/aider-not-found.md b/cecli/website/docs/troubleshooting/aider-not-found.md similarity index 100% rename from aider/website/docs/troubleshooting/aider-not-found.md rename to cecli/website/docs/troubleshooting/aider-not-found.md diff --git a/aider/website/docs/troubleshooting/edit-errors.md b/cecli/website/docs/troubleshooting/edit-errors.md similarity index 100% rename from aider/website/docs/troubleshooting/edit-errors.md rename to cecli/website/docs/troubleshooting/edit-errors.md diff --git a/aider/website/docs/troubleshooting/imports.md b/cecli/website/docs/troubleshooting/imports.md similarity index 100% rename from aider/website/docs/troubleshooting/imports.md rename to cecli/website/docs/troubleshooting/imports.md diff --git a/aider/website/docs/troubleshooting/models-and-keys.md b/cecli/website/docs/troubleshooting/models-and-keys.md similarity index 100% rename from aider/website/docs/troubleshooting/models-and-keys.md rename to cecli/website/docs/troubleshooting/models-and-keys.md diff --git a/aider/website/docs/troubleshooting/support.md b/cecli/website/docs/troubleshooting/support.md similarity index 100% rename from aider/website/docs/troubleshooting/support.md rename to cecli/website/docs/troubleshooting/support.md diff --git a/aider/website/docs/troubleshooting/token-limits.md b/cecli/website/docs/troubleshooting/token-limits.md similarity index 100% rename from aider/website/docs/troubleshooting/token-limits.md rename to cecli/website/docs/troubleshooting/token-limits.md diff --git a/aider/website/docs/troubleshooting/warnings.md b/cecli/website/docs/troubleshooting/warnings.md similarity index 100% rename from aider/website/docs/troubleshooting/warnings.md rename to cecli/website/docs/troubleshooting/warnings.md diff --git a/aider/website/docs/unified-diffs.md b/cecli/website/docs/unified-diffs.md similarity index 100% rename from aider/website/docs/unified-diffs.md rename to cecli/website/docs/unified-diffs.md diff --git a/aider/website/docs/usage.md b/cecli/website/docs/usage.md similarity index 100% rename from aider/website/docs/usage.md rename to cecli/website/docs/usage.md diff --git a/aider/website/docs/usage/browser.md b/cecli/website/docs/usage/browser.md similarity index 100% rename from aider/website/docs/usage/browser.md rename to cecli/website/docs/usage/browser.md diff --git a/aider/website/docs/usage/caching.md b/cecli/website/docs/usage/caching.md similarity index 100% rename from aider/website/docs/usage/caching.md rename to cecli/website/docs/usage/caching.md diff --git a/aider/website/docs/usage/commands.md b/cecli/website/docs/usage/commands.md similarity index 100% rename from aider/website/docs/usage/commands.md rename to cecli/website/docs/usage/commands.md diff --git a/aider/website/docs/usage/conventions.md b/cecli/website/docs/usage/conventions.md similarity index 100% rename from aider/website/docs/usage/conventions.md rename to cecli/website/docs/usage/conventions.md diff --git a/aider/website/docs/usage/copypaste.md b/cecli/website/docs/usage/copypaste.md similarity index 100% rename from aider/website/docs/usage/copypaste.md rename to cecli/website/docs/usage/copypaste.md diff --git a/aider/website/docs/usage/images-urls.md b/cecli/website/docs/usage/images-urls.md similarity index 100% rename from aider/website/docs/usage/images-urls.md rename to cecli/website/docs/usage/images-urls.md diff --git a/aider/website/docs/usage/lint-test.md b/cecli/website/docs/usage/lint-test.md similarity index 100% rename from aider/website/docs/usage/lint-test.md rename to cecli/website/docs/usage/lint-test.md diff --git a/aider/website/docs/usage/modes.md b/cecli/website/docs/usage/modes.md similarity index 100% rename from aider/website/docs/usage/modes.md rename to cecli/website/docs/usage/modes.md diff --git a/aider/website/docs/usage/not-code.md b/cecli/website/docs/usage/not-code.md similarity index 100% rename from aider/website/docs/usage/not-code.md rename to cecli/website/docs/usage/not-code.md diff --git a/aider/website/docs/usage/notifications.md b/cecli/website/docs/usage/notifications.md similarity index 100% rename from aider/website/docs/usage/notifications.md rename to cecli/website/docs/usage/notifications.md diff --git a/aider/website/docs/usage/tips.md b/cecli/website/docs/usage/tips.md similarity index 100% rename from aider/website/docs/usage/tips.md rename to cecli/website/docs/usage/tips.md diff --git a/aider/website/docs/usage/tutorials.md b/cecli/website/docs/usage/tutorials.md similarity index 100% rename from aider/website/docs/usage/tutorials.md rename to cecli/website/docs/usage/tutorials.md diff --git a/aider/website/docs/usage/voice.md b/cecli/website/docs/usage/voice.md similarity index 100% rename from aider/website/docs/usage/voice.md rename to cecli/website/docs/usage/voice.md diff --git a/aider/website/docs/usage/watch.md b/cecli/website/docs/usage/watch.md similarity index 100% rename from aider/website/docs/usage/watch.md rename to cecli/website/docs/usage/watch.md diff --git a/aider/website/examples/2048-game.md b/cecli/website/examples/2048-game.md similarity index 100% rename from aider/website/examples/2048-game.md rename to cecli/website/examples/2048-game.md diff --git a/aider/website/examples/README.md b/cecli/website/examples/README.md similarity index 100% rename from aider/website/examples/README.md rename to cecli/website/examples/README.md diff --git a/aider/website/examples/add-test.md b/cecli/website/examples/add-test.md similarity index 100% rename from aider/website/examples/add-test.md rename to cecli/website/examples/add-test.md diff --git a/aider/website/examples/asciinema.md b/cecli/website/examples/asciinema.md similarity index 100% rename from aider/website/examples/asciinema.md rename to cecli/website/examples/asciinema.md diff --git a/aider/website/examples/census.md b/cecli/website/examples/census.md similarity index 100% rename from aider/website/examples/census.md rename to cecli/website/examples/census.md diff --git a/aider/website/examples/chat-transcript-css.md b/cecli/website/examples/chat-transcript-css.md similarity index 100% rename from aider/website/examples/chat-transcript-css.md rename to cecli/website/examples/chat-transcript-css.md diff --git a/aider/website/examples/complex-change.md b/cecli/website/examples/complex-change.md similarity index 100% rename from aider/website/examples/complex-change.md rename to cecli/website/examples/complex-change.md diff --git a/aider/website/examples/css-exercises.md b/cecli/website/examples/css-exercises.md similarity index 100% rename from aider/website/examples/css-exercises.md rename to cecli/website/examples/css-exercises.md diff --git a/aider/website/examples/hello-world-flask.md b/cecli/website/examples/hello-world-flask.md similarity index 100% rename from aider/website/examples/hello-world-flask.md rename to cecli/website/examples/hello-world-flask.md diff --git a/aider/website/examples/hello.md b/cecli/website/examples/hello.md similarity index 100% rename from aider/website/examples/hello.md rename to cecli/website/examples/hello.md diff --git a/aider/website/examples/no-color.md b/cecli/website/examples/no-color.md similarity index 100% rename from aider/website/examples/no-color.md rename to cecli/website/examples/no-color.md diff --git a/aider/website/examples/pong.md b/cecli/website/examples/pong.md similarity index 100% rename from aider/website/examples/pong.md rename to cecli/website/examples/pong.md diff --git a/aider/website/examples/semantic-search-replace.md b/cecli/website/examples/semantic-search-replace.md similarity index 100% rename from aider/website/examples/semantic-search-replace.md rename to cecli/website/examples/semantic-search-replace.md diff --git a/aider/website/examples/update-docs.md b/cecli/website/examples/update-docs.md similarity index 100% rename from aider/website/examples/update-docs.md rename to cecli/website/examples/update-docs.md diff --git a/aider/website/index.html b/cecli/website/index.html similarity index 99% rename from aider/website/index.html rename to cecli/website/index.html index 9a674dd6a0f..abbe7eaa37b 100644 --- a/aider/website/index.html +++ b/cecli/website/index.html @@ -28,7 +28,7 @@ Getting Started Documentation Discord - GitHub + GitHub diff --git a/aider/website/install.ps1 b/cecli/website/install.ps1 similarity index 100% rename from aider/website/install.ps1 rename to cecli/website/install.ps1 diff --git a/aider/website/install.sh b/cecli/website/install.sh similarity index 100% rename from aider/website/install.sh rename to cecli/website/install.sh diff --git a/aider/website/share/index.md b/cecli/website/share/index.md similarity index 100% rename from aider/website/share/index.md rename to cecli/website/share/index.md diff --git a/docker/Dockerfile b/docker/Dockerfile index 7e223d07ae7..75004668169 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -28,14 +28,14 @@ ENV PLAYWRIGHT_BROWSERS_PATH=/home/appuser/pw-browsers ENV PLAYWRIGHT_SKIP_BROWSER_GC=1 # Create directories with proper permissions -RUN mkdir -p /home/appuser/.aider /home/appuser/.cache /home/appuser/pw-browsers && \ +RUN mkdir -p /home/appuser/.cecli /home/appuser/.cache /home/appuser/pw-browsers && \ chown -R appuser:appuser /home/appuser /app /venv && \ - chmod -R 777 /home/appuser/.aider /home/appuser/.cache /home/appuser/pw-browsers + chmod -R 777 /home/appuser/.cecli /home/appuser/.cache /home/appuser/pw-browsers # So git doesn't complain about unusual permissions RUN git config --system --add safe.directory /app -# This puts the container's ~/.aider into the host's project directory (usually host's cwd). +# This puts the container's ~/.cecli into the host's project directory (usually host's cwd). # That way caches, version checks, etc get stored in the host filesystem not # simply discarded every time the container exits. ENV HOME=/app @@ -43,7 +43,7 @@ ENV HOME=/app ######################### FROM base AS aider-ce -ENV AIDER_DOCKER_IMAGE=dustinwashington/aider-ce +ENV CECLI_DOCKER_IMAGE=dustinwashington/aider-ce # Copy requirements files COPY requirements.txt /tmp/aider/ @@ -70,4 +70,4 @@ RUN uv pip install . && \ # Switch to appuser USER appuser -ENTRYPOINT ["/venv/bin/aider-ce"] +ENTRYPOINT ["/venv/bin/cecli"] diff --git a/docker/Dockerfile.local.nvidia.cuda.ubuntu b/docker/Dockerfile.local.nvidia.cuda.ubuntu index a95b46ed38f..2c31c818aa0 100644 --- a/docker/Dockerfile.local.nvidia.cuda.ubuntu +++ b/docker/Dockerfile.local.nvidia.cuda.ubuntu @@ -34,9 +34,9 @@ ENV PLAYWRIGHT_BROWSERS_PATH=${OLDHOME}/pw-browsers ENV PLAYWRIGHT_SKIP_BROWSER_GC=1 # Create directories with proper permissions -RUN mkdir -p ${OLDHOME}/.aider ${OLDHOME}/.cache ${OLDHOME}/pw-browsers && \ +RUN mkdir -p ${OLDHOME}/.cecli ${OLDHOME}/.cache ${OLDHOME}/pw-browsers && \ chown -R ${DEFAULTUSER}:${DEFAULTGROUP} ${OLDHOME} /app /venv && \ - chmod -R 777 ${OLDHOME}/.aider ${OLDHOME}/.cache ${OLDHOME}/pw-browsers + chmod -R 777 ${OLDHOME}/.cecli ${OLDHOME}/.cache ${OLDHOME}/pw-browsers # So git doesn't complain about unusual permissions RUN git config --system --add safe.directory /app @@ -47,12 +47,12 @@ RUN git config --system --add safe.directory /app ENV HOME=/app # Copy requirements files -COPY requirements.txt /tmp/aider/ -COPY requirements/ /tmp/aider/requirements/ +COPY requirements.txt /tmp/cecli/ +COPY requirements/ /tmp/cecli/requirements/ # Install dependencies as root -RUN uv pip install --no-cache-dir -r /tmp/aider/requirements.txt && \ - rm -rf /tmp/aider +RUN uv pip install --no-cache-dir -r /tmp/cecli/requirements.txt && \ + rm -rf /tmp/cecli # Install playwright browsers RUN uv pip install --no-cache-dir playwright && \ @@ -79,7 +79,7 @@ echo '* Checking available models' curl -sS http://localhost:11434/v1/models | jq .data[].id | tr -d '\"' echo '* Start cecli with stream: false, see https://github.com/Aider-AI/aider/issues/4594' echo 'cecli --no-stream --model ollama/' -echo ' later you can best add it to .aider.conf.yml, see https://aider.chat/docs/config/aider_conf.html' +echo ' later you can best add it to .cecli.conf.yml, see https://aider.chat/docs/config/aider_conf.html' EOF # switch to non-root and set the host-mounted /app to HOME to persist ~/.aider diff --git a/pyproject.toml b/pyproject.toml index a41dd027c94..86fe73126f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,9 +22,9 @@ dynamic = ["dependencies", "optional-dependencies", "version"] Homepage = "https://github.com/dwash96/aider-ce" [project.scripts] -aider-ce = "aider.main:main" -"cecli" = "aider.main:main" -"ce.cli" = "aider.main:main" +aider-ce = "cecli.main:main" +"cecli" = "cecli.main:main" +"ce.cli" = "cecli.main:main" [tool.setuptools.dynamic] dependencies = { file = "requirements/requirements.in" } @@ -39,16 +39,16 @@ tui = { file = "requirements/requirements-tui.in" } include-package-data = true [tool.setuptools.packages.find] -include = ["aider*"] +include = ["cecli*"] [build-system] requires = ["setuptools>=68", "setuptools_scm[toml]>=8"] build-backend = "setuptools.build_meta" [tool.setuptools_scm] -write_to = "aider/_version.py" +write_to = "cecli/_version.py" local_scheme = "no-local-version" [tool.codespell] -skip = "*.svg,Gemfile.lock,tests/fixtures/*,aider/website/assets/*" +skip = "*.svg,Gemfile.lock,tests/fixtures/*,cecli/website/assets/*" write-changes = true diff --git a/scripts/30k-image.py b/scripts/30k-image.py index 29924d424e3..c5e9d093d6b 100644 --- a/scripts/30k-image.py +++ b/scripts/30k-image.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # flake8: noqa: E501 """ -Generate a celebratory SVG image for Aider reaching 30,000 GitHub stars. +Generate a celebratory SVG image for cecli reaching 30,000 GitHub stars. This creates a shareable social media graphic with confetti animation. """ @@ -12,8 +12,8 @@ from pathlib import Path # Default colors for the celebration image -AIDER_GREEN = "#14b014" -AIDER_BLUE = "#4C6EF5" +CECLIGREEN = "#14b014" +CECLIBLUE = "#4C6EF5" DARK_COLOR = "#212529" LIGHT_COLOR = "#F8F9FA" GOLD_COLOR = "#f1c40f" @@ -27,7 +27,7 @@ def embed_font(): """Returns base64 encoded font data for the GlassTTYVT220 font.""" # Path to the font file font_path = ( - Path(__file__).parent.parent / "aider" / "website" / "assets" / "Glass_TTY_VT220.ttf" + Path(__file__).parent.parent / "cecli" / "website" / "assets" / "Glass_TTY_VT220.ttf" ) # If font file doesn't exist, return empty string @@ -46,7 +46,7 @@ def embed_font(): def generate_confetti(count=150, width=DEFAULT_WIDTH, height=DEFAULT_HEIGHT): """Generate SVG confetti elements for the celebration.""" confetti = [] - colors = [AIDER_GREEN, AIDER_BLUE, GOLD_COLOR, "#e74c3c", "#9b59b6", "#3498db", "#2ecc71"] + colors = [CECLIGREEN, CECLIBLUE, GOLD_COLOR, "#e74c3c", "#9b59b6", "#3498db", "#2ecc71"] # Define text safe zones # Main content safe zone (centered area) @@ -169,7 +169,7 @@ def generate_celebration_svg(output_path=None, width=DEFAULT_WIDTH, height=DEFAU