Merge branch 'main' into watch

This commit is contained in:
Paul Gauthier 2024-10-31 14:19:37 -07:00
commit 5a974483b9
39 changed files with 1421 additions and 122 deletions

View file

@ -1,6 +1,22 @@
# Release history
### main branch
- Load and save aider slash-commands to files:
- `/save <fname>` command will make a file of `/add` and `/read-only` commands that recreate the current file context in the chat.
- `/load <fname>` will replay the commands in the file.
- You can use `/load` to run any arbitrary set of slash-commands, not just `/add` and `/read-only`.
- Use `--load <fname>` to run a list of commands on launch, before the interactive chat begins.
- Anonymous, opt-in [analytics](https://aider.chat/docs/more/analytics.html) with no personal data sharing.
- Aider follows litellm's `supports_vision` attribute to enable image support for models.
- Bugfix for when diff mode flexibly handles the model using the wrong filename.
- Displays filenames in sorted order for `/add` and `/read-only`.
- New `--no-fancy-input` switch disables prompt toolkit input, now still available with `--no-pretty`.
- Properly support all o1 models, regardless of provider.
- Improved handling of API errors, especially when accessing the weak model.
- Aider wrote 70% of the code in this release.
### Aider v0.60.1
- Enable image support for Sonnet 10/22.

164
aider/analytics.py Normal file
View file

@ -0,0 +1,164 @@
import json
import platform
import sys
import time
import uuid
from pathlib import Path
from mixpanel import Mixpanel
from posthog import Posthog
from aider import __version__
from aider.dump import dump # noqa: F401
from aider.models import model_info_manager
mixpanel_project_token = "6da9a43058a5d1b9f3353153921fb04d"
posthog_project_api_key = "phc_99T7muzafUMMZX15H8XePbMSreEUzahHbtWjy3l5Qbv"
posthog_host = "https://us.i.posthog.com"
class Analytics:
# providers
mp = None
ph = None
# saved
user_id = None
permanently_disable = None
asked_opt_in = None
# ephemeral
logfile = None
def __init__(self, logfile=None, permanently_disable=False):
self.logfile = logfile
self.get_or_create_uuid()
if self.permanently_disable or permanently_disable or not self.asked_opt_in:
self.disable(permanently_disable)
def enable(self):
if not self.user_id:
self.disable(False)
return
if self.permanently_disable:
self.disable(True)
return
if not self.asked_opt_in:
self.disable(False)
return
self.mp = Mixpanel(mixpanel_project_token)
self.ph = Posthog(project_api_key=posthog_project_api_key, host=posthog_host)
def disable(self, permanently):
self.mp = None
self.ph = None
if permanently:
self.asked_opt_in = True
self.permanently_disable = True
self.save_data()
def need_to_ask(self):
return not self.asked_opt_in and not self.permanently_disable
def get_data_file_path(self):
data_file = Path.home() / ".aider" / "analytics.json"
data_file.parent.mkdir(parents=True, exist_ok=True)
return data_file
def get_or_create_uuid(self):
self.load_data()
if self.user_id:
return
self.user_id = str(uuid.uuid4())
self.save_data()
def load_data(self):
data_file = self.get_data_file_path()
if data_file.exists():
try:
data = json.loads(data_file.read_text())
self.permanently_disable = data.get("permanently_disable")
self.user_id = data.get("uuid")
self.asked_opt_in = data.get("asked_opt_in", False)
except (json.decoder.JSONDecodeError, OSError):
self.disable(permanently=False)
def save_data(self):
data_file = self.get_data_file_path()
data = dict(
uuid=self.user_id,
permanently_disable=self.permanently_disable,
asked_opt_in=self.asked_opt_in,
)
# Allow exceptions; crash if we can't record permanently_disabled=True, etc
data_file.write_text(json.dumps(data, indent=4))
def get_system_info(self):
return {
"python_version": sys.version.split()[0],
"os_platform": platform.system(),
"os_release": platform.release(),
"machine": platform.machine(),
}
def _redact_model_name(self, model):
if not model:
return None
info = model_info_manager.get_model_from_cached_json_db(model.name)
if info:
return model.name
elif "/" in model.name:
return model.name.split("/")[0] + "/REDACTED"
return None
def event(self, event_name, main_model=None, **kwargs):
if not (self.mp or self.ph) and not self.logfile:
return
properties = {}
if main_model:
properties["main_model"] = self._redact_model_name(main_model)
properties["weak_model"] = self._redact_model_name(main_model.weak_model)
properties["editor_model"] = self._redact_model_name(main_model.editor_model)
properties.update(kwargs)
properties.update(self.get_system_info()) # Add system info to all events
# Handle numeric values
for key, value in properties.items():
if isinstance(value, (int, float)):
properties[key] = value
else:
properties[key] = str(value)
properties["aider_version"] = __version__
if self.mp:
self.mp.track(self.user_id, event_name, dict(properties))
if self.ph:
self.ph.capture(self.user_id, event_name, dict(properties))
if self.logfile:
log_entry = {
"event": event_name,
"properties": properties,
"user_id": self.user_id,
"time": int(time.time()),
}
with open(self.logfile, "a") as f:
json.dump(log_entry, f)
f.write("\n")
def __del__(self):
if self.ph:
self.ph.shutdown()

View file

@ -550,6 +550,25 @@ def get_parser(default_config_files, git_root):
)
##########
group = parser.add_argument_group("Analytics")
group.add_argument(
"--analytics",
action=argparse.BooleanOptionalAction,
default=False,
help="Enable/disable analytics for one session (default: False)",
)
group.add_argument(
"--analytics-log",
metavar="ANALYTICS_LOG_FILE",
help="Specify a file to log analytics events",
)
group.add_argument(
"--analytics-disable",
action="store_true",
help="Permanently disable analytics",
default=False,
)
group = parser.add_argument_group("Other Settings")
group.add_argument(
"--file",
@ -660,6 +679,11 @@ def get_parser(default_config_files, git_root):
" (disables chat mode)"
),
)
group.add_argument(
"--load",
metavar="LOAD_FILE",
help="Load and execute /commands from a file on launch",
)
group.add_argument(
"--encoding",
default="utf-8",

View file

@ -13,12 +13,15 @@ import sys
import threading
import time
import traceback
import webbrowser
from collections import defaultdict
from datetime import datetime
from json.decoder import JSONDecodeError
from pathlib import Path
from typing import List
from aider import __version__, models, prompts, urls, utils
from aider.analytics import Analytics
from aider.commands import Commands
from aider.history import ChatSummary
from aider.io import ConfirmGroup, InputOutput
@ -258,12 +261,17 @@ class Coder:
commands=None,
summarizer=None,
total_cost=0.0,
analytics=None,
map_refresh="auto",
cache_prompts=False,
num_cache_warming_pings=0,
suggest_shell_commands=True,
chat_language=None,
):
# Fill in a dummy Analytics if needed, but it is never .enable()'d
self.analytics = analytics if analytics is not None else Analytics()
self.event = self.analytics.event
self.chat_language = chat_language
self.commit_before_message = []
self.aider_commit_hashes = set()
@ -782,13 +790,26 @@ class Coder:
self.num_reflections += 1
message = self.reflected_message
def check_for_urls(self, inp):
def check_and_open_urls(self, text: str) -> List[str]:
"""Check text for URLs and offer to open them in a browser."""
url_pattern = re.compile(r"(https?://[^\s/$.?#].[^\s]*)")
urls = list(set(url_pattern.findall(text))) # Use set to remove duplicates
for url in urls:
url = url.rstrip(".',\"")
if self.io.confirm_ask("Open URL for more info about this error?", subject=url):
webbrowser.open(url)
return urls
def check_for_urls(self, inp: str) -> List[str]:
"""Check input for URLs and offer to add them to the chat."""
url_pattern = re.compile(r"(https?://[^\s/$.?#].[^\s]*[^\s,.])")
urls = list(set(url_pattern.findall(inp))) # Use set to remove duplicates
added_urls = []
group = ConfirmGroup(urls)
for url in urls:
if url not in self.rejected_urls:
url = url.rstrip(".',\"")
if self.io.confirm_ask(
"Add URL to the chat?", subject=url, group=group, allow_never=True
):
@ -1101,6 +1122,8 @@ class Coder:
return chunks
def send_message(self, inp):
import openai # for error codes below
self.cur_messages += [
dict(role="user", content=inp),
]
@ -1129,9 +1152,18 @@ class Coder:
yield from self.send(messages, functions=self.functions)
break
except retry_exceptions() as err:
self.io.tool_warning(str(err))
# Print the error and its base classes
err_msg = str(err)
# base_classes = []
# for cls in err.__class__.__mro__: # Skip the class itself
# base_classes.append(cls.__name__)
# if base_classes:
# err_msg += f"\nBase classes: {' -> '.join(base_classes)}"
self.io.tool_error(err_msg)
retry_delay *= 2
if retry_delay > RETRY_TIMEOUT:
self.mdstream = None
self.check_and_open_urls(err_msg)
break
self.io.tool_output(f"Retrying in {retry_delay:.1f} seconds...")
time.sleep(retry_delay)
@ -1160,10 +1192,14 @@ class Coder:
messages.append(
dict(role="assistant", content=self.multi_response_content, prefix=True)
)
except (openai.APIError, openai.APIStatusError) as err:
self.mdstream = None
self.io.tool_error(str(err))
self.check_and_open_urls(str(err))
except Exception as err:
self.io.tool_error(f"Unexpected error: {err}")
lines = traceback.format_exception(type(err), err, err.__traceback__)
self.io.tool_error("".join(lines))
self.io.tool_warning("".join(lines))
self.io.tool_error(str(err))
return
finally:
if self.mdstream:
@ -1638,11 +1674,27 @@ class Coder:
self.usage_report = tokens_report + sep + cost_report
def show_usage_report(self):
if self.usage_report:
self.io.tool_output(self.usage_report)
self.message_cost = 0.0
self.message_tokens_sent = 0
self.message_tokens_received = 0
if not self.usage_report:
return
self.io.tool_output(self.usage_report)
prompt_tokens = self.message_tokens_sent
completion_tokens = self.message_tokens_received
self.event(
"message_send",
main_model=self.main_model,
edit_format=self.edit_format,
prompt_tokens=prompt_tokens,
completion_tokens=completion_tokens,
total_tokens=prompt_tokens + completion_tokens,
cost=self.message_cost,
total_cost=self.total_cost,
)
self.message_cost = 0.0
self.message_tokens_sent = 0
self.message_tokens_received = 0
def get_multi_response_content(self, final=False):
cur = self.multi_response_content or ""
@ -1811,8 +1863,10 @@ class Coder:
edited = set()
try:
edits = self.get_edits()
edits = self.apply_edits_dry_run(edits)
edits = self.prepare_to_edit(edits)
edited = set(edit[0] for edit in edits)
self.apply_edits(edits)
except ValueError as err:
self.num_malformed_responses += 1
@ -1940,6 +1994,9 @@ class Coder:
def apply_edits(self, edits):
return
def apply_edits_dry_run(self, edits):
return edits
def run_shell_commands(self):
if not self.suggest_shell_commands:
return ""

View file

@ -35,9 +35,13 @@ class EditBlockCoder(Coder):
return edits
def apply_edits(self, edits):
def apply_edits_dry_run(self, edits):
return self.apply_edits(edits, dry_run=True)
def apply_edits(self, edits, dry_run=False):
failed = []
passed = []
updated_edits = []
for edit in edits:
path, original, updated = edit
@ -50,14 +54,21 @@ class EditBlockCoder(Coder):
content = self.io.read_text(full_path)
new_content = do_replace(full_path, content, original, updated, self.fence)
if new_content:
path = self.get_rel_fname(full_path)
break
updated_edits.append((path, original, updated))
if new_content:
self.io.write_text(full_path, new_content)
if not dry_run:
self.io.write_text(full_path, new_content)
passed.append(edit)
else:
failed.append(edit)
if dry_run:
return updated_edits
if not failed:
return

View file

@ -223,6 +223,7 @@ class Commands:
def run(self, inp):
if inp.startswith("!"):
self.coder.event("command_run")
return self.do_run("run", inp[1:])
res = self.matching_commands(inp)
@ -230,9 +231,13 @@ class Commands:
return
matching_commands, first_word, rest_inp = res
if len(matching_commands) == 1:
return self.do_run(matching_commands[0][1:], rest_inp)
command = matching_commands[0][1:]
self.coder.event(f"command_{command}")
return self.do_run(command, rest_inp)
elif first_word in matching_commands:
return self.do_run(first_word[1:], rest_inp)
command = first_word[1:]
self.coder.event(f"command_{command}")
return self.do_run(command, rest_inp)
elif len(matching_commands) > 1:
self.io.tool_error(f"Ambiguous command: {', '.join(matching_commands)}")
else:
@ -963,6 +968,7 @@ class Commands:
self.basic_help()
return
self.coder.event("interactive help")
from aider.coders import Coder
if not self.help:
@ -1246,6 +1252,62 @@ class Commands:
output = f"{announcements}\n{settings}"
self.io.tool_output(output)
def completions_raw_load(self, document, complete_event):
return self.completions_raw_read_only(document, complete_event)
def cmd_load(self, args):
"Load and execute commands from a file"
if not args.strip():
self.io.tool_error("Please provide a filename containing commands to load.")
return
try:
with open(args.strip(), "r", encoding=self.io.encoding, errors="replace") as f:
commands = f.readlines()
except FileNotFoundError:
self.io.tool_error(f"File not found: {args}")
return
except Exception as e:
self.io.tool_error(f"Error reading file: {e}")
return
for cmd in commands:
cmd = cmd.strip()
if not cmd or cmd.startswith("#"):
continue
self.io.tool_output(f"\nExecuting: {cmd}")
self.run(cmd)
def completions_raw_save(self, document, complete_event):
return self.completions_raw_read_only(document, complete_event)
def cmd_save(self, args):
"Save commands to a file that can reconstruct the current chat session's files"
if not args.strip():
self.io.tool_error("Please provide a filename to save the commands to.")
return
try:
with open(args.strip(), "w", encoding=self.io.encoding) as f:
# Write commands to add editable files
for fname in sorted(self.coder.abs_fnames):
rel_fname = self.coder.get_rel_fname(fname)
f.write(f"/add {rel_fname}\n")
# Write commands to add read-only files
for fname in sorted(self.coder.abs_read_only_fnames):
# Use absolute path for files outside repo root, relative path for files inside
if Path(fname).is_relative_to(self.coder.root):
rel_fname = self.coder.get_rel_fname(fname)
f.write(f"/read-only {rel_fname}\n")
else:
f.write(f"/read-only {fname}\n")
self.io.tool_output(f"Saved commands to {args.strip()}")
except Exception as e:
self.io.tool_error(f"Error saving commands to file: {e}")
def cmd_copy(self, args):
"Copy the last assistant message to the clipboard"
all_messages = self.coder.done_messages + self.coder.cur_messages

View file

@ -245,8 +245,9 @@ class InputOutput:
"output": self.output,
"lexer": PygmentsLexer(MarkdownLexer),
"editing_mode": self.editingmode,
"cursor": ModalCursorShapeConfig(),
}
if self.editingmode == EditingMode.VI:
session_kwargs["cursor"] = ModalCursorShapeConfig()
if self.input_history_file is not None:
session_kwargs["history"] = FileHistory(self.input_history_file)
try:
@ -741,11 +742,9 @@ class InputOutput:
try:
with self.chat_history_file.open("a", encoding=self.encoding, errors="ignore") as f:
f.write(text)
except (PermissionError, OSError):
self.tool_error(
f"Warning: Unable to write to chat history file {self.chat_history_file}."
" Permission denied."
)
except (PermissionError, OSError) as err:
print(f"Warning: Unable to write to chat history file {self.chat_history_file}.")
print(err)
self.chat_history_file = None # Disable further attempts to write
def format_files_for_input(self, rel_fnames, rel_read_only_fnames):

View file

@ -5,6 +5,7 @@ import re
import sys
import threading
import traceback
import webbrowser
from pathlib import Path
import git
@ -13,6 +14,7 @@ from dotenv import load_dotenv
from prompt_toolkit.enums import EditingMode
from aider import __version__, models, urls, utils
from aider.analytics import Analytics
from aider.args import get_parser
from aider.coders import Coder
from aider.commands import Commands, SwitchCoder
@ -194,7 +196,10 @@ def launch_gui(args):
"--server.runOnSave=false",
]
if "-dev" in __version__:
# https://github.com/Aider-AI/aider/issues/2193
is_dev = "-dev" in str(__version__)
if is_dev:
print("Watching for file changes.")
else:
st_args += [
@ -362,7 +367,8 @@ def sanity_check_repo(repo, io):
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.")
io.tool_output(urls.git_index_version)
if io.confirm_ask("Open documentation url for more info?", subject=urls.git_index_version):
webbrowser.open(urls.git_index_version)
return False
io.tool_error("Unable to read git repository, it may be corrupt?")
@ -423,6 +429,11 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
# Parse again to include any arguments that might have been defined in .env
args = parser.parse_args(argv)
if args.analytics_disable:
analytics = Analytics(permanently_disable=True)
print("Analytics have been permanently disabled.")
return
if not args.verify_ssl:
import httpx
@ -484,9 +495,35 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
io = get_io(False)
io.tool_warning("Terminal does not support pretty output (UnicodeDecodeError)")
analytics = Analytics(logfile=args.analytics_log, permanently_disable=args.analytics_disable)
if args.analytics:
if analytics.need_to_ask():
io.tool_output(
"Aider respects your privacy and never collects your code, chat messages, keys or"
" personal info."
)
io.tool_output(f"For more info: {urls.analytics}")
disable = not io.confirm_ask(
"Allow collection of anonymous analytics to help improve aider?"
)
analytics.asked_opt_in = True
if disable:
analytics.disable(permanently=True)
io.tool_output("Analytics have been permanently disabled.")
analytics.save_data()
io.tool_output()
# This is a no-op if the user has opted out
analytics.enable()
analytics.event("launched")
if args.gui and not return_coder:
if not check_streamlit_install(io):
return
analytics.event("gui session")
launch_gui(argv)
return
@ -601,11 +638,15 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
if args.show_model_warnings:
problem = models.sanity_check_models(io, main_model)
if problem:
analytics.event("model warning", main_model=main_model)
io.tool_output("You can skip this check with --no-show-model-warnings")
io.tool_output()
try:
if not io.confirm_ask("Proceed anyway?"):
return 1
if io.confirm_ask(
"Open documentation url for more info?", subject=urls.model_warnings
):
webbrowser.open(urls.model_warnings)
io.tool_output()
except KeyboardInterrupt:
return 1
@ -674,6 +715,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
test_cmd=args.test_cmd,
commands=commands,
summarizer=summarizer,
analytics=analytics,
map_refresh=args.map_refresh,
cache_prompts=args.cache_prompts,
map_mul_no_files=args.map_multiplier_no_files,
@ -747,6 +789,9 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
io.tool_output(f"Cur working dir: {Path.cwd()}")
io.tool_output(f"Git working dir: {git_root}")
if args.load:
commands.cmd_load(args.load)
if args.message:
io.add_to_input_history(args.message)
io.tool_output()
@ -772,6 +817,8 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
if args.exit:
return
analytics.event("cli session", main_model=main_model, edit_format=main_model.edit_format)
while True:
try:
coder.run()
@ -819,7 +866,11 @@ def check_and_load_imports(io, verbose=False):
except Exception as err:
io.tool_error(str(err))
io.tool_output("Error loading required imports. Did you install aider properly?")
io.tool_output("https://aider.chat/docs/install/install.html")
if io.confirm_ask(
"Open documentation url for more info?", subject=urls.install_properly
):
webbrowser.open(urls.install_properly)
sys.exit(1)
installs[str(key)] = True

View file

@ -13,7 +13,6 @@ import json5
import yaml
from PIL import Image
from aider import urls
from aider.dump import dump # noqa: F401
from aider.llm import litellm
@ -633,77 +632,77 @@ MODEL_SETTINGS = [
]
model_info_url = (
"https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"
)
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
def __init__(self):
self.cache_dir = Path.home() / ".aider" / "caches"
self.cache_file = self.cache_dir / "model_prices_and_context_window.json"
self.content = None
self._load_cache()
def get_model_flexible(model, content):
info = content.get(model, dict())
if info:
return info
pieces = model.split("/")
if len(pieces) == 2:
info = content.get(pieces[1])
if info and info.get("litellm_provider") == pieces[0]:
return info
return dict()
def get_model_info(model):
if not litellm._lazy_module:
cache_dir = Path.home() / ".aider" / "caches"
cache_file = cache_dir / "model_prices_and_context_window.json"
def _load_cache(self):
try:
cache_dir.mkdir(parents=True, exist_ok=True)
use_cache = True
self.cache_dir.mkdir(parents=True, exist_ok=True)
if self.cache_file.exists():
cache_age = time.time() - self.cache_file.stat().st_mtime
if cache_age < self.CACHE_TTL:
self.content = json.loads(self.cache_file.read_text())
except OSError:
# If we can't create the cache directory, we'll skip using the cache
use_cache = False
if use_cache:
current_time = time.time()
cache_age = (
current_time - cache_file.stat().st_mtime if cache_file.exists() else float("inf")
)
if cache_age < 60 * 60 * 24:
try:
content = json.loads(cache_file.read_text())
res = get_model_flexible(model, content)
if res:
return res
except Exception as ex:
print(str(ex))
import requests
pass
def _update_cache(self):
try:
response = requests.get(model_info_url, timeout=5)
import requests
response = requests.get(self.MODEL_INFO_URL, timeout=5)
if response.status_code == 200:
content = response.json()
if use_cache:
try:
cache_file.write_text(json.dumps(content, indent=4))
except OSError:
# If we can't write to the cache file, we'll just skip caching
pass
res = get_model_flexible(model, content)
if res:
return res
self.content = response.json()
try:
self.cache_file.write_text(json.dumps(self.content, indent=4))
except OSError:
pass
except Exception as ex:
print(str(ex))
# If all else fails, do it the slow way...
try:
info = litellm.get_model_info(model)
return info
except Exception:
def get_model_from_cached_json_db(self, model):
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):
if not litellm._lazy_module:
info = self.get_model_from_cached_json_db(model)
if info:
return info
# If all else fails, do it the slow way...
try:
return litellm.get_model_info(model)
except Exception:
return dict()
model_info_manager = ModelInfoManager()
class Model(ModelSettings):
def __init__(self, model, weak_model=None, editor_model=None, editor_edit_format=None):
@ -737,7 +736,7 @@ class Model(ModelSettings):
self.get_editor_model(editor_model, editor_edit_format)
def get_model_info(self, model):
return get_model_info(model)
return model_info_manager.get_model_info(model)
def configure_model_settings(self, model):
for ms in MODEL_SETTINGS:
@ -778,6 +777,11 @@ class Model(ModelSettings):
self.examples_as_sys_msg = True
self.reminder = "user"
if model.startswith("o1-") or "/o1-" in model:
self.use_system_prompt = False
self.use_temperature = False
self.streaming = False
# use the defaults
if self.edit_format == "diff":
self.use_repo_map = True
@ -1037,9 +1041,6 @@ def sanity_check_model(io, model):
for match in possible_matches:
io.tool_output(f"- {match}")
if show:
io.tool_output(f"For more info, see: {urls.model_warnings}")
return show

View file

@ -2,6 +2,7 @@ import colorsys
import math
import os
import random
import shutil
import sqlite3
import sys
import time
@ -166,13 +167,52 @@ class RepoMap:
# Just return the full fname.
return fname
def tags_cache_error(self, original_error=None):
"""Handle SQLite errors by trying to recreate cache, falling back to dict if needed"""
if self.verbose and original_error:
self.io.tool_warning(f"Tags cache error: {str(original_error)}")
if isinstance(getattr(self, "TAGS_CACHE", None), dict):
return
path = Path(self.root) / self.TAGS_CACHE_DIR
# Try to recreate the cache
try:
# Delete existing cache dir
if path.exists():
shutil.rmtree(path)
# Try to create new cache
new_cache = Cache(path)
# Test that it works
test_key = "test"
new_cache[test_key] = "test"
_ = new_cache[test_key]
del new_cache[test_key]
# If we got here, the new cache works
self.TAGS_CACHE = new_cache
return
except (SQLITE_ERRORS, OSError) as e:
# If anything goes wrong, warn and fall back to dict
self.io.tool_warning(
f"Unable to use tags cache at {path}, falling back to memory cache"
)
if self.verbose:
self.io.tool_warning(f"Cache recreation error: {str(e)}")
self.TAGS_CACHE = dict()
def load_tags_cache(self):
path = Path(self.root) / self.TAGS_CACHE_DIR
try:
self.TAGS_CACHE = Cache(path)
except SQLITE_ERRORS:
self.io.tool_warning(f"Unable to use tags cache, delete {path} to resolve.")
self.TAGS_CACHE = dict()
except SQLITE_ERRORS as e:
self.tags_cache_error(e)
def save_tags_cache(self):
pass
@ -190,9 +230,18 @@ class RepoMap:
return []
cache_key = fname
val = self.TAGS_CACHE.get(cache_key) # Issue #1308
try:
val = self.TAGS_CACHE.get(cache_key) # Issue #1308
except SQLITE_ERRORS as e:
self.tags_cache_error(e)
val = self.TAGS_CACHE.get(cache_key)
if val is not None and val.get("mtime") == file_mtime:
return self.TAGS_CACHE[cache_key]["data"]
try:
return self.TAGS_CACHE[cache_key]["data"]
except SQLITE_ERRORS as e:
self.tags_cache_error(e)
return self.TAGS_CACHE[cache_key]["data"]
# miss!
data = list(self.get_tags_raw(fname, rel_fname))
@ -201,8 +250,9 @@ class RepoMap:
try:
self.TAGS_CACHE[cache_key] = {"mtime": file_mtime, "data": data}
self.save_tags_cache()
except SQLITE_ERRORS:
pass
except SQLITE_ERRORS as e:
self.tags_cache_error(e)
self.TAGS_CACHE[cache_key] = {"mtime": file_mtime, "data": data}
return data
@ -302,7 +352,13 @@ class RepoMap:
# https://networkx.org/documentation/stable/_modules/networkx/algorithms/link_analysis/pagerank_alg.html#pagerank
personalize = 100 / len(fnames)
if len(fnames) - len(self.TAGS_CACHE) > 100:
try:
cache_size = len(self.TAGS_CACHE)
except SQLITE_ERRORS as e:
self.tags_cache_error(e)
cache_size = len(self.TAGS_CACHE)
if len(fnames) - cache_size > 100:
self.io.tool_output(
"Initial repo scan can be slow in larger repos, but only happens once."
)

View file

@ -42,8 +42,8 @@ def retry_exceptions():
openai.UnprocessableEntityError,
openai.RateLimitError,
openai.APIConnectionError,
openai.APIError,
openai.APIStatusError,
# openai.APIError,
# openai.APIStatusError,
openai.InternalServerError,
)

View file

@ -10,3 +10,5 @@ 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/Aider-AI/aider/issues/new"
git_index_version = "https://github.com/Aider-AI/aider/issues/211"
install_properly = "https://aider.chat/docs/troubleshooting/imports.html"
analytics = "https://aider.chat/docs/more/analytics.html"

View file

@ -1,7 +1,7 @@
---
title: Release history
parent: More info
nav_order: 999
nav_order: 900
highlight_image: /assets/blame.jpg
description: Release notes and stats on aider writing its own code.
---
@ -19,6 +19,22 @@ cog.out(text)
### main branch
- Load and save aider slash-commands to files:
- `/save <fname>` command will make a file of `/add` and `/read-only` commands that recreate the current file context in the chat.
- `/load <fname>` will replay the commands in the file.
- You can use `/load` to run any arbitrary set of slash-commands, not just `/add` and `/read-only`.
- Use `--load <fname>` to run a list of commands on launch, before the interactive chat begins.
- Anonymous, opt-in [analytics](https://aider.chat/docs/more/analytics.html) with no personal data sharing.
- Aider follows litellm's `supports_vision` attribute to enable image support for models.
- Bugfix for when diff mode flexibly handles the model using the wrong filename.
- Displays filenames in sorted order for `/add` and `/read-only`.
- New `--no-fancy-input` switch disables prompt toolkit input, now still available with `--no-pretty`.
- Properly support all o1 models, regardless of provider.
- Improved handling of API errors, especially when accessing the weak model.
- Aider wrote 70% of the code in this release.
### Aider v0.60.1
- Enable image support for Sonnet 10/22.

View file

@ -0,0 +1,147 @@
{"event": "launched", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": "52b5cc12-f16d-45f3-b30c-95ab4ae904ee", "time": 1723560031}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20240620", "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": "52b5cc12-f16d-45f3-b30c-95ab4ae904ee", "time": 1723560032}
{"event": "launched", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": "52b5cc12-f16d-45f3-b30c-95ab4ae904ee", "time": 1723560039}
{"event": "cli session", "properties": {"main_model": "gpt-4o-mini", "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": "52b5cc12-f16d-45f3-b30c-95ab4ae904ee", "time": 1723560039}
{"event": "message_send", "properties": {"main_model": "gpt-4o-mini", "prompt_tokens": 638, "completion_tokens": 25, "total_tokens": 663, "cost": 0.0001107, "total_cost": 0.0001107, "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": "52b5cc12-f16d-45f3-b30c-95ab4ae904ee", "time": 1723560043}
{"event": "command_exit", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": "52b5cc12-f16d-45f3-b30c-95ab4ae904ee", "time": 1723560044}
{"event": "launched", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": "52b5cc12-f16d-45f3-b30c-95ab4ae904ee", "time": 1723560239}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20240620", "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": "52b5cc12-f16d-45f3-b30c-95ab4ae904ee", "time": 1723560239}
{"event": "command_add", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": "52b5cc12-f16d-45f3-b30c-95ab4ae904ee", "time": 1723560263}
{"event": "message_send", "properties": {"main_model": "claude-3-5-sonnet-20240620", "prompt_tokens": 15025, "completion_tokens": 462, "total_tokens": 15487, "cost": 0.052005, "total_cost": 0.052005, "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": "52b5cc12-f16d-45f3-b30c-95ab4ae904ee", "time": 1723560308}
{"event": "message_send", "properties": {"main_model": "claude-3-5-sonnet-20240620", "prompt_tokens": 16242, "completion_tokens": 269, "total_tokens": 16511, "cost": 0.052761, "total_cost": 0.104766, "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": "52b5cc12-f16d-45f3-b30c-95ab4ae904ee", "time": 1723560334}
{"event": "command_exit", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": "52b5cc12-f16d-45f3-b30c-95ab4ae904ee", "time": 1723560339}
{"event": "launched", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": "52b5cc12-f16d-45f3-b30c-95ab4ae904ee", "time": 1723560733}
{"event": "cli session", "properties": {"main_model": "gpt-4o-mini", "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": "52b5cc12-f16d-45f3-b30c-95ab4ae904ee", "time": 1723560733}
{"event": "message_send", "properties": {"main_model": "gpt-4o-mini", "prompt_tokens": 638, "completion_tokens": 25, "total_tokens": 663, "cost": 0.0001107, "total_cost": 0.0001107, "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": "52b5cc12-f16d-45f3-b30c-95ab4ae904ee", "time": 1723560737}
{"event": "command_exit", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": "52b5cc12-f16d-45f3-b30c-95ab4ae904ee", "time": 1723560765}
{"event": "launched", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": null, "time": 1723560805}
{"event": "cli session", "properties": {"main_model": "gpt-4o-mini", "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": null, "time": 1723560805}
{"event": "message_send", "properties": {"main_model": "gpt-4o-mini", "prompt_tokens": 638, "completion_tokens": 25, "total_tokens": 663, "cost": 0.0001107, "total_cost": 0.0001107, "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": null, "time": 1723560812}
{"event": "launched", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": "8f9e4550-33c4-4417-b152-e35ace897f13", "time": 1723560903}
{"event": "cli session", "properties": {"main_model": "gpt-4o-mini", "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": "8f9e4550-33c4-4417-b152-e35ace897f13", "time": 1723560903}
{"event": "launched", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": "bc3e1ea0-29a7-43ef-85fd-94694f8acebb", "time": 1723560920}
{"event": "cli session", "properties": {"main_model": "gpt-4o-mini", "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": "bc3e1ea0-29a7-43ef-85fd-94694f8acebb", "time": 1723560920}
{"event": "launched", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": null, "time": 1723560994}
{"event": "cli session", "properties": {"main_model": "gpt-4o-mini", "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": null, "time": 1723560994}
{"event": "launched", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": null, "time": 1723561016}
{"event": "cli session", "properties": {"main_model": "gpt-4o-mini", "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": null, "time": 1723561017}
{"event": "launched", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": null, "time": 1723561046}
{"event": "cli session", "properties": {"main_model": "gpt-4o-mini", "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": null, "time": 1723561046}
{"event": "launched", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": null, "time": 1723561049}
{"event": "cli session", "properties": {"main_model": "gpt-4o-mini", "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": null, "time": 1723561049}
{"event": "launched", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": "6547b0bb-4248-4d40-8269-dc59e9624e0f", "time": 1723561234}
{"event": "cli session", "properties": {"main_model": "gpt-4o-mini", "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": "6547b0bb-4248-4d40-8269-dc59e9624e0f", "time": 1723561235}
{"event": "message_send", "properties": {"main_model": "gpt-4o-mini", "prompt_tokens": 638, "completion_tokens": 25, "total_tokens": 663, "cost": 0.0001107, "total_cost": 0.0001107, "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": "6547b0bb-4248-4d40-8269-dc59e9624e0f", "time": 1723561241}
{"event": "launched", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": null, "time": 1723561304}
{"event": "cli session", "properties": {"main_model": "gpt-4o-mini", "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": null, "time": 1723561304}
{"event": "message_send", "properties": {"main_model": "gpt-4o-mini", "prompt_tokens": 638, "completion_tokens": 25, "total_tokens": 663, "cost": 0.0001107, "total_cost": 0.0001107, "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": null, "time": 1723561307}
{"event": "command_exit", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": null, "time": 1723561665}
{"event": "launched", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": null, "time": 1723561679}
{"event": "launched", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": null, "time": 1723561841}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20240620", "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": null, "time": 1723561841}
{"event": "message_send", "properties": {"main_model": "claude-3-5-sonnet-20240620", "prompt_tokens": 12148, "completion_tokens": 269, "total_tokens": 12417, "cost": 0.040479, "total_cost": 0.040479, "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": null, "time": 1723561858}
{"event": "command_undo", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": null, "time": 1723561925}
{"event": "launched", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": null, "time": 1723568624}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20240620", "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": null, "time": 1723568624}
{"event": "message_send", "properties": {"main_model": "claude-3-5-sonnet-20240620", "prompt_tokens": 14217, "completion_tokens": 217, "total_tokens": 14434, "cost": 0.045906, "total_cost": 0.045906, "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.49.2-dev"}, "user_id": null, "time": 1723568667}
{"event": "launched", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.1-dev"}, "user_id": null, "time": 1723579444}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20240620", "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.1-dev"}, "user_id": null, "time": 1723579445}
{"event": "launched", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.1-dev"}, "user_id": null, "time": 1723579738}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20240620", "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.1-dev"}, "user_id": null, "time": 1723579738}
{"event": "launched", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.1-dev", "$lib": "posthog-python", "$lib_version": "3.5.0", "$geoip_disable": true}, "user_id": "5218a941-50b3-405f-9f75-1bf42b282b6b", "time": 1723579757}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20240620", "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.1-dev", "$lib": "posthog-python", "$lib_version": "3.5.0", "$geoip_disable": true}, "user_id": "5218a941-50b3-405f-9f75-1bf42b282b6b", "time": 1723579757}
{"event": "launched", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.1-dev", "$lib": "posthog-python", "$lib_version": "3.5.0", "$geoip_disable": true}, "user_id": "5218a941-50b3-405f-9f75-1bf42b282b6b", "time": 1723579779}
{"event": "launched", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.1-dev", "$lib": "posthog-python", "$lib_version": "3.5.0", "$geoip_disable": true}, "user_id": "5218a941-50b3-405f-9f75-1bf42b282b6b", "time": 1723579940}
{"event": "cli session", "properties": {"main_model": "gpt-4o-mini", "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.1-dev", "$lib": "posthog-python", "$lib_version": "3.5.0", "$geoip_disable": true}, "user_id": "5218a941-50b3-405f-9f75-1bf42b282b6b", "time": 1723579940}
{"event": "message_send", "properties": {"main_model": "gpt-4o-mini", "edit_format": "whole", "prompt_tokens": 638, "completion_tokens": 25, "total_tokens": 663, "cost": 0.0001107, "total_cost": 0.0001107, "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.1-dev", "$lib": "posthog-python", "$lib_version": "3.5.0", "$geoip_disable": true}, "user_id": "5218a941-50b3-405f-9f75-1bf42b282b6b", "time": 1723579944}
{"event": "message_send", "properties": {"main_model": "gpt-4o-mini", "edit_format": "whole", "prompt_tokens": 673, "completion_tokens": 28, "total_tokens": 701, "cost": 0.00011775, "total_cost": 0.00022845, "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.1-dev", "$lib": "posthog-python", "$lib_version": "3.5.0", "$geoip_disable": true}, "user_id": "5218a941-50b3-405f-9f75-1bf42b282b6b", "time": 1723579948}
{"event": "command_exit", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.1-dev", "$lib": "posthog-python", "$lib_version": "3.5.0", "$geoip_disable": true}, "user_id": "5218a941-50b3-405f-9f75-1bf42b282b6b", "time": 1723579952}
{"event": "launched", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.1-dev", "$lib": "posthog-python", "$lib_version": "3.5.0", "$geoip_disable": true}, "user_id": "5218a941-50b3-405f-9f75-1bf42b282b6b", "time": 1723584128}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20240620", "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.1-dev", "$lib": "posthog-python", "$lib_version": "3.5.0", "$geoip_disable": true}, "user_id": "5218a941-50b3-405f-9f75-1bf42b282b6b", "time": 1723584128}
{"event": "message_send", "properties": {"main_model": "claude-3-5-sonnet-20240620", "edit_format": "diff", "prompt_tokens": 3461, "completion_tokens": 289, "total_tokens": 3750, "cost": 0.014718, "total_cost": 0.014718, "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.1-dev", "$lib": "posthog-python", "$lib_version": "3.5.0", "$geoip_disable": true}, "user_id": "5218a941-50b3-405f-9f75-1bf42b282b6b", "time": 1723584158}
{"event": "command_diff", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.1-dev", "$lib": "posthog-python", "$lib_version": "3.5.0", "$geoip_disable": true}, "user_id": "5218a941-50b3-405f-9f75-1bf42b282b6b", "time": 1723584162}
{"event": "command_exit", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.1-dev", "$lib": "posthog-python", "$lib_version": "3.5.0", "$geoip_disable": true}, "user_id": "5218a941-50b3-405f-9f75-1bf42b282b6b", "time": 1723584173}
{"event": "launched", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.1-dev", "$lib": "posthog-python", "$lib_version": "3.5.0", "$geoip_disable": true}, "user_id": "5218a941-50b3-405f-9f75-1bf42b282b6b", "time": 1723593477}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20240620", "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.1-dev", "$lib": "posthog-python", "$lib_version": "3.5.0", "$geoip_disable": true}, "user_id": "5218a941-50b3-405f-9f75-1bf42b282b6b", "time": 1723593477}
{"event": "command_chat-mode", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.1-dev", "$lib": "posthog-python", "$lib_version": "3.5.0", "$geoip_disable": true}, "user_id": "5218a941-50b3-405f-9f75-1bf42b282b6b", "time": 1723593516}
{"event": "message_send", "properties": {"main_model": "claude-3-5-sonnet-20240620", "edit_format": "ask", "prompt_tokens": 9262, "completion_tokens": 223, "total_tokens": 9485, "cost": 0.031131000000000002, "total_cost": 0.031131000000000002, "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.1-dev", "$lib": "posthog-python", "$lib_version": "3.5.0", "$geoip_disable": true}, "user_id": "5218a941-50b3-405f-9f75-1bf42b282b6b", "time": 1723593580}
{"event": "launched", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.1-dev", "$lib": "posthog-python", "$lib_version": "3.5.0", "$geoip_disable": true}, "user_id": "5218a941-50b3-405f-9f75-1bf42b282b6b", "time": 1723593593}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20240620", "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.1-dev", "$lib": "posthog-python", "$lib_version": "3.5.0", "$geoip_disable": true}, "user_id": "5218a941-50b3-405f-9f75-1bf42b282b6b", "time": 1723593593}
{"event": "message_send", "properties": {"main_model": "claude-3-5-sonnet-20240620", "edit_format": "ask", "prompt_tokens": 2054, "completion_tokens": 370, "total_tokens": 2424, "cost": 0.011712, "total_cost": 0.011712, "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.1-dev", "$lib": "posthog-python", "$lib_version": "3.5.0", "$geoip_disable": true}, "user_id": "5218a941-50b3-405f-9f75-1bf42b282b6b", "time": 1723593607}
{"event": "launched", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.2-dev"}, "user_id": "cbbee83e-62da-4629-9a3c-04d37a26f7a7", "time": 1723832819}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20240620", "python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.2-dev"}, "user_id": "cbbee83e-62da-4629-9a3c-04d37a26f7a7", "time": 1723832821}
{"event": "command_exit", "properties": {"python_version": "3.12.4", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.50.2-dev"}, "user_id": "cbbee83e-62da-4629-9a3c-04d37a26f7a7", "time": 1723832823}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7", "$lib": "posthog-python", "$lib_version": "3.7.0", "$geoip_disable": true}, "user_id": "1f30456a-1f79-4f19-9720-fb0b8a304b0a", "time": 1730316502}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20241022", "python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7", "$lib": "posthog-python", "$lib_version": "3.7.0", "$geoip_disable": true}, "user_id": "1f30456a-1f79-4f19-9720-fb0b8a304b0a", "time": 1730316502}
{"event": "command_exit", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7", "$lib": "posthog-python", "$lib_version": "3.7.0", "$geoip_disable": true}, "user_id": "1f30456a-1f79-4f19-9720-fb0b8a304b0a", "time": 1730316583}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7", "$lib": "posthog-python", "$lib_version": "3.7.0", "$geoip_disable": true}, "user_id": "1f30456a-1f79-4f19-9720-fb0b8a304b0a", "time": 1730316586}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20241022", "python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7", "$lib": "posthog-python", "$lib_version": "3.7.0", "$geoip_disable": true}, "user_id": "1f30456a-1f79-4f19-9720-fb0b8a304b0a", "time": 1730316586}
{"event": "command_exit", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7", "$lib": "posthog-python", "$lib_version": "3.7.0", "$geoip_disable": true}, "user_id": "1f30456a-1f79-4f19-9720-fb0b8a304b0a", "time": 1730316589}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "1f30456a-1f79-4f19-9720-fb0b8a304b0a", "time": 1730316644}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20241022", "python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "1f30456a-1f79-4f19-9720-fb0b8a304b0a", "time": 1730316645}
{"event": "command_settings", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "1f30456a-1f79-4f19-9720-fb0b8a304b0a", "time": 1730316661}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7", "$lib": "posthog-python", "$lib_version": "3.7.0", "$geoip_disable": true}, "user_id": "3645b476-b3d5-46d6-aa89-864bf75dfb5b", "time": 1730317188}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20241022", "python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7", "$lib": "posthog-python", "$lib_version": "3.7.0", "$geoip_disable": true}, "user_id": "3645b476-b3d5-46d6-aa89-864bf75dfb5b", "time": 1730317189}
{"event": "command_exit", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7", "$lib": "posthog-python", "$lib_version": "3.7.0", "$geoip_disable": true}, "user_id": "3645b476-b3d5-46d6-aa89-864bf75dfb5b", "time": 1730317192}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7", "$lib": "posthog-python", "$lib_version": "3.7.0", "$geoip_disable": true}, "user_id": "3645b476-b3d5-46d6-aa89-864bf75dfb5b", "time": 1730317294}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20241022", "python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7", "$lib": "posthog-python", "$lib_version": "3.7.0", "$geoip_disable": true}, "user_id": "3645b476-b3d5-46d6-aa89-864bf75dfb5b", "time": 1730317294}
{"event": "command_exit", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7", "$lib": "posthog-python", "$lib_version": "3.7.0", "$geoip_disable": true}, "user_id": "3645b476-b3d5-46d6-aa89-864bf75dfb5b", "time": 1730317302}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "3645b476-b3d5-46d6-aa89-864bf75dfb5b", "time": 1730317724}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20241022", "python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "3645b476-b3d5-46d6-aa89-864bf75dfb5b", "time": 1730317725}
{"event": "command_exit", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "3645b476-b3d5-46d6-aa89-864bf75dfb5b", "time": 1730317726}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "092dd9f6-5445-42cd-b90c-a9e456b37a74", "time": 1730317730}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20241022", "python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "092dd9f6-5445-42cd-b90c-a9e456b37a74", "time": 1730317730}
{"event": "command_exit", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "092dd9f6-5445-42cd-b90c-a9e456b37a74", "time": 1730317731}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "092dd9f6-5445-42cd-b90c-a9e456b37a74", "time": 1730317749}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20241022", "python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "092dd9f6-5445-42cd-b90c-a9e456b37a74", "time": 1730317749}
{"event": "command_exit", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "092dd9f6-5445-42cd-b90c-a9e456b37a74", "time": 1730317751}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7", "$lib": "posthog-python", "$lib_version": "3.7.0", "$geoip_disable": true}, "user_id": "092dd9f6-5445-42cd-b90c-a9e456b37a74", "time": 1730317753}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20241022", "python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7", "$lib": "posthog-python", "$lib_version": "3.7.0", "$geoip_disable": true}, "user_id": "092dd9f6-5445-42cd-b90c-a9e456b37a74", "time": 1730317753}
{"event": "command_exit", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7", "$lib": "posthog-python", "$lib_version": "3.7.0", "$geoip_disable": true}, "user_id": "092dd9f6-5445-42cd-b90c-a9e456b37a74", "time": 1730317754}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "092dd9f6-5445-42cd-b90c-a9e456b37a74", "time": 1730318254}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20241022", "python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "092dd9f6-5445-42cd-b90c-a9e456b37a74", "time": 1730318254}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730318258}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20241022", "python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730318259}
{"event": "command_exit", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730318260}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7", "$lib": "posthog-python", "$lib_version": "3.7.0", "$geoip_disable": true}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730318328}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20241022", "python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7", "$lib": "posthog-python", "$lib_version": "3.7.0", "$geoip_disable": true}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730318328}
{"event": "command_exit", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7", "$lib": "posthog-python", "$lib_version": "3.7.0", "$geoip_disable": true}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730318331}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730318336}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20241022", "python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730318337}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730318367}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20241022", "python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730318367}
{"event": "command_exit", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730318371}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730318373}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20241022", "python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730318373}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730318398}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20241022", "python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730318398}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730318403}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20241022", "python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730318404}
{"event": "command_exit", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730318407}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730318451}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20241022", "edit_format": "diff", "python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730318451}
{"event": "command_exit", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730318455}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730319350}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730319838}
{"event": "gui session", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730319839}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730323755}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730323810}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730323820}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20241022", "edit_format": "diff", "python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730323821}
{"event": "command_exit", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730323824}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730323831}
{"event": "cli session", "properties": {"main_model": "some/REDACTED", "edit_format": "whole", "python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730323851}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730323974}
{"event": "model warning", "properties": {"main_model": "some/REDACTED", "python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730323975}
{"event": "cli session", "properties": {"main_model": "some/REDACTED", "edit_format": "whole", "python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730323985}
{"event": "command_exit", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730324000}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730324063}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730337491}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20241022", "edit_format": "diff", "python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "5e16241e-8c66-479e-9954-1fbee80560a3", "time": 1730337491}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "cd4d7b34-79ca-4ffe-9fba-7557dbeb8a88", "time": 1730394556}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20241022", "weak_model": "claude-3-5-sonnet-20241022", "editor_model": "claude-3-5-sonnet-20241022", "edit_format": "diff", "python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "cd4d7b34-79ca-4ffe-9fba-7557dbeb8a88", "time": 1730394556}
{"event": "command_exit", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "cd4d7b34-79ca-4ffe-9fba-7557dbeb8a88", "time": 1730394558}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "feeaed68-f237-48ad-ac13-807e1692cc40", "time": 1730400360}
{"event": "cli session", "properties": {"main_model": "claude-3-5-sonnet-20241022", "weak_model": "claude-3-5-sonnet-20241022", "editor_model": "claude-3-5-sonnet-20241022", "edit_format": "diff", "python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "feeaed68-f237-48ad-ac13-807e1692cc40", "time": 1730400360}
{"event": "launched", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "feeaed68-f237-48ad-ac13-807e1692cc40", "time": 1730405507}
{"event": "gui session", "properties": {"python_version": "3.12.6", "os_platform": "Darwin", "os_release": "23.6.0", "machine": "x86_64", "aider_version": "0.60.2.dev13+g9e7995b7"}, "user_id": "feeaed68-f237-48ad-ac13-807e1692cc40", "time": 1730405508}

View file

@ -265,6 +265,18 @@
## Run tests and fix problems found
#test: false
############
# Analytics:
## Enable/disable analytics for one session (default: False)
#analytics: false
## Specify a file to log analytics events
#analytics-log: xxx
## Permanently disable analytics
#analytics-disable: false
#################
# Other Settings:
@ -329,6 +341,9 @@
## Specify a file containing the message to send the LLM, process reply, then exit (disables chat mode)
#message-file: xxx
## Load and execute /commands from a file on launch
#load: xxx
## Specify the encoding for input and output (default: utf-8)
#encoding: utf-8

View file

@ -264,6 +264,18 @@
## Run tests and fix problems found
#AIDER_TEST=false
############
# Analytics:
## Enable/disable analytics for one session (default: False)
#AIDER_ANALYTICS=false
## Specify a file to log analytics events
#AIDER_ANALYTICS_LOG=
## Permanently disable analytics
#AIDER_ANALYTICS_DISABLE=false
#################
# Other Settings:
@ -315,6 +327,9 @@
## Specify a file containing the message to send the LLM, process reply, then exit (disables chat mode)
#AIDER_MESSAGE_FILE=
## Load and execute /commands from a file on launch
#AIDER_LOAD=
## Specify the encoding for input and output (default: utf-8)
#AIDER_ENCODING=utf-8

View file

@ -321,6 +321,18 @@ cog.outl("```")
## Run tests and fix problems found
#test: false
############
# Analytics:
## Enable/disable analytics for one session (default: False)
#analytics: false
## Specify a file to log analytics events
#analytics-log: xxx
## Permanently disable analytics
#analytics-disable: false
#################
# Other Settings:
@ -385,6 +397,9 @@ cog.outl("```")
## Specify a file containing the message to send the LLM, process reply, then exit (disables chat mode)
#message-file: xxx
## Load and execute /commands from a file on launch
#load: xxx
## Specify the encoding for input and output (default: utf-8)
#encoding: utf-8

View file

@ -306,6 +306,18 @@ cog.outl("```")
## Run tests and fix problems found
#AIDER_TEST=false
############
# Analytics:
## Enable/disable analytics for one session (default: False)
#AIDER_ANALYTICS=false
## Specify a file to log analytics events
#AIDER_ANALYTICS_LOG=
## Permanently disable analytics
#AIDER_ANALYTICS_DISABLE=false
#################
# Other Settings:
@ -357,6 +369,9 @@ cog.outl("```")
## Specify a file containing the message to send the LLM, process reply, then exit (disables chat mode)
#AIDER_MESSAGE_FILE=
## Load and execute /commands from a file on launch
#AIDER_LOAD=
## Specify the encoding for input and output (default: utf-8)
#AIDER_ENCODING=utf-8

View file

@ -61,14 +61,15 @@ usage: aider [-h] [--openai-api-key] [--anthropic-api-key] [--model]
[--commit] [--commit-prompt] [--dry-run | --no-dry-run]
[--skip-sanity-check-repo] [--lint] [--lint-cmd]
[--auto-lint | --no-auto-lint] [--test-cmd]
[--auto-test | --no-auto-test] [--test] [--file]
[--read] [--vim] [--chat-language] [--version]
[--just-check-update]
[--auto-test | --no-auto-test] [--test]
[--analytics | --no-analytics] [--analytics-log]
[--analytics-disable] [--file] [--read] [--vim]
[--chat-language] [--version] [--just-check-update]
[--check-update | --no-check-update]
[--install-main-branch] [--upgrade] [--apply]
[--yes-always] [-v] [--show-repo-map] [--show-prompts]
[--exit] [--message] [--message-file] [--encoding] [-c]
[--gui]
[--exit] [--message] [--message-file] [--load]
[--encoding] [-c] [--gui]
[--suggest-shell-commands | --no-suggest-shell-commands]
[--fancy-input | --no-fancy-input] [--voice-format]
[--voice-language]
@ -501,6 +502,25 @@ Run tests and fix problems found
Default: False
Environment variable: `AIDER_TEST`
## Analytics:
### `--analytics`
Enable/disable analytics for one session (default: False)
Default: False
Environment variable: `AIDER_ANALYTICS`
Aliases:
- `--analytics`
- `--no-analytics`
### `--analytics-log ANALYTICS_LOG_FILE`
Specify a file to log analytics events
Environment variable: `AIDER_ANALYTICS_LOG`
### `--analytics-disable`
Permanently disable analytics
Default: False
Environment variable: `AIDER_ANALYTICS_DISABLE`
## Other Settings:
### `--file FILE`
@ -595,6 +615,10 @@ Aliases:
- `--message-file MESSAGE_FILE`
- `-f MESSAGE_FILE`
### `--load LOAD_FILE`
Load and execute /commands from a file on launch
Environment variable: `AIDER_LOAD`
### `--encoding VALUE`
Specify the encoding for input and output (default: utf-8)
Default: utf-8

View file

@ -220,3 +220,13 @@ Aider is
[open source and available on GitHub](https://github.com/Aider-AI/aider)
under an
[Apache 2.0 license](https://github.com/Aider-AI/aider/blob/main/LICENSE.txt).
## Can I edit files myself while aider is running?
Yes. Aider always reads the latest copy of files from the file
system when you send each message.
While you're waiting for aider's reply to complete, it's probably unwise to
edit files that you've added to the chat.
Your edits and aider's edits might conflict.

View file

@ -1,6 +1,6 @@
---
parent: More info
nav_order: 800
nav_order: 100
description: Aider is tightly integrated with git.
---

View file

@ -1,6 +1,6 @@
---
parent: More info
nav_order: 900
nav_order: 200
description: Aider supports pretty much all popular coding languages.
---
# Supported languages

View file

@ -0,0 +1,104 @@
---
parent: More info
nav_order: 500
---
# Privacy policy
[Aider AI LLC](/docs/faq.html#what-is-aider-ai-llc)
(“Aider,” “we,” “our,” and/or “us”) values the privacy of individuals who use our website, programming tools, and related services (collectively, our “Services”). This privacy policy (the “Privacy Policy”) explains how we collect, use, and disclose information from users of our Services. By using our Services, you agree to the collection, use, disclosure, and procedures this Privacy Policy describes.
### Information We Collect
We may collect a variety of information from or about you or your devices from various sources, as described below.
### A. Information You Provide to Us.
**Communications.** If you contact us directly, we may receive additional information about you, such as your name, email address, the contents of a message or attachments that you may send to us, and other information you choose to provide.
### B. Information We Collect When You Use Our Services.
**Device Information.** We may receive information about the device and software you use to access our Services, including IP address, device type, device identifiers, web browser type and version, and operating system version.
**Usage Information.** We may automatically receive information about your interactions with our Services, like the pages or other content you view, referrer information (the website you visited before coming to our Services), and the dates and times of your visits.
**Analytics Information.** If you use our programming tools, we may receive information about your interactions with the tools, such as how often certain features or commands are used, information about exceptions and errors, and which large language models are used. This information is associated with a randomly generated identifier, not any directly identifiable user information such as your name or email address. Please see the “Your Choices” section below for information on how to disable the collection of this information.
**Information from Cookies and Other Tracking Technologies.** We and our third-party partners may collect information about your activities on our Services using cookies, pixel tags, SDKs, or other tracking technologies. Our third-party partners, such as analytics and security partners, may also use these technologies to collect information about your online activities over time and across different services.
### How We Use the Information We Collect
We use the information we collect:
- To provide, maintain, improve, and enhance our Services;
- To understand and analyze how you use our Services and develop new products, services, features, and functionality;
- To communicate with you, provide you with updates and other information relating to our Services, provide information that you request, respond to comments and questions, and otherwise provide customer support;
- To generate anonymized or aggregate data containing only de-identified, non-personal information that we may use for any lawful purposes such as to publish reports;
- To find and prevent fraud and abuse, and respond to trust and safety issues that may arise;
- For compliance purposes, including enforcing our legal rights, or as may be required by applicable laws and regulations or requested by any judicial process or governmental agency; and
- For other purposes for which we provide specific notice at the time the information is collected.
### How We Disclose the Information We Collect
**Affiliates.** We may disclose any information we receive to our current or future affiliates for any of the purposes described in this Privacy Policy.
**Vendors and Service Providers.** We may disclose any information we receive to vendors and service providers retained in connection with the provision of our Services.
**Analytics Partners.** We may use analytics services to collect and process certain analytics data to improve our Services, such as by improving the ability of our programming tools to work with LLMs, edit code, and complete user requests.
**As Required By Law and Similar Disclosures.** We may access, preserve, and disclose your information if we believe doing so is required or appropriate to: (a) comply with law enforcement requests and legal process, such as a court order or subpoena; (b) respond to your requests; or (c) protect your, our, or others rights, property, or safety. For the avoidance of doubt, the disclosure of your information may occur if you post any objectionable content on or through the Services.
**Merger, Sale, or Other Asset Transfers.** We may transfer your information to service providers, advisors, potential transactional partners, or other third parties in connection with the consideration, negotiation, or completion of a corporate transaction in which we are acquired by or merged with another company or we sell, liquidate, or transfer all or a portion of our assets. The use of your information following any of these events will be governed by the provisions of this Privacy Policy in effect at the time the applicable information was collected.
**Consent.** We may also disclose your information with your permission.
### Your Choices
**Analytics Information.** You can turn off analytics collection when using our programming tools. Please visit this
[documentation page](/docs/more/analytics.html)
for more information about the data collected and your options.
### Third Parties
Our Services may contain links to other websites, products, or services that we do not own or operate. We are not responsible for the privacy practices of these third parties. Please be aware that this Privacy Policy does not apply to your activities on these third-party services or any information you disclose to these third parties. We encourage you to read their privacy policies before providing any information to them.
### Security
We make reasonable efforts to protect your information by using physical and electronic safeguards designed to improve the security of the information we maintain. However, because no electronic transmission or storage of information can be entirely secure, we can make no guarantees as to the security or privacy of your information.
### Childrens Privacy
We do not knowingly collect, maintain, or use personal information from children under 18 years of age, and no part of our Service(s) is directed to children. If you learn that a child has provided us with personal information in violation of this Privacy Policy, then you may alert us at [INSERT EMAIL ADDRESS].
### International Visitors
Our Services are hosted in the United States and intended for visitors located within the United States. If you choose to use the Services from the European Union or other regions of the world with laws governing data collection and use that may differ from U.S. law, then please note that you are transferring your personal information outside of those regions to the U.S. for storage and processing. We may also transfer your data from the U.S. to other countries or regions in connection with storage and processing of data, fulfilling your requests, and operating the Services. By providing any information, including personal information, on or to the Services, you consent to such transfer, storage, and processing.
### Changes to this Privacy Policy
We will post any adjustments to the Privacy Policy on this page, and the revised version will be effective when it is posted. If we materially change the ways in which we use or disclose personal information previously collected from you through the Services, we will notify you through the Services, by email, or other communication.
### Contact Information
If you have any questions, comments, or concerns about our processing activities, please email us at privacy@aider.chat.
----
<p class="post-date">
Last updated
<!--[[[cog
import subprocess
import datetime
result = subprocess.run(['git', 'log', '-1', '--format=%ct', 'aider/website/docs/legal/privacy.md'], capture_output=True, text=True)
if result.returncode == 0:
timestamp = int(result.stdout.strip())
date = datetime.datetime.fromtimestamp(timestamp)
cog.out(f"{date.strftime('%B %d, %Y.')}")
]]]-->
October 31, 2024.
<!--[[[end]]]-->
</p>

View file

@ -0,0 +1,109 @@
---
parent: More info
nav_order: 500
---
# Analytics
Aider can collect anonymous analytics to help
improve aider's ability to work with LLMs, edit code and complete user requests.
## Opt-in, anonymous, no personal info
Analytics are only collected if you agree and opt-in.
Aider respects your privacy and never collects your code, chat messages, keys or
personal info.
Aider collects information on:
- which LLMs are used and with how many tokens,
- which of aider's edit formats are used,
- how often features and commands are used,
- information about exceptions and errors,
- etc
These analytics are associated with an anonymous,
randomly generated UUID4 user identifier.
This information helps improve aider by identifying which models, edit formats,
features and commands are most used.
It also helps uncover bugs that users are experiencing, so that they can be fixed
in upcoming releases.
## Enabling & disabling analytics
You can opt out of analytics forever by running this command one time:
```
aider --analytics-disable
```
To enable analytics for a single session, you can run aider with `--analytics`.
This will *not* have any effect if you have permanently disabled analytics with the previous command.
The first time, you will need to agree to opt-in.
```
aider --analytics
Aider respects your privacy and never collects your code, prompts, chats, keys or any personal
info.
For more info: https://aider.chat/docs/more/analytics.html
Allow collection of anonymous analytics to help improve aider? (Y)es/(N)o [Yes]:
```
If you've added `analytics: true` to your
[yaml config file](/docs/config/aider_conf.html),
you can disable analytics for a single session, you can run:
```
aider --no-analytics
```
## Details about data being collected
### Sample analytics data
To get a better sense of what type of data is collected, you can review some
[sample analytics logs](https://github.com/aider-ai/aider/blob/main/aider/website/assets/sample-analytics.jsonl).
These are the last 1,000 analytics events from the author's
personal use of aider, updated regularly.
### Analytics code
Since aider is open source, all the places where aider collects analytics
are visible in the source code.
They can be viewed using
[GitHub search](https://github.com/search?q=repo%3Aaider-ai%2Faider+%22.event%28%22&type=code).
### Logging and inspecting analytics
You can get a full log of the analytics that aider is collecting,
in case you would like to audit or inspect this data.
```
aider --analytics-log filename.jsonl
```
If you want to just log analytics without reporting them, you can do:
```
aider --analytics-log filename.jsonl --no-analytics
```
## Reporting issues
If you have concerns about any of the analytics that aider is collecting
or our data practices
please contact us by opening a
[GitHub Issue](https://github.com/paul-gauthier/aider/issues).
## Privacy policy
Please see aider's
[privacy policy](/docs/legal/privacy.html)
for more details.

View file

@ -1,6 +1,6 @@
---
parent: More info
nav_order: 960
nav_order: 490
description: Aider uses various "edit formats" to let LLMs edit source files.
---

View file

@ -1,6 +1,6 @@
---
parent: More info
nav_order: 920
nav_order: 480
description: Aider can handle "infinite output" from models that support prefill.
---

View file

@ -1,7 +1,7 @@
---
parent: More info
highlight_image: /assets/robot-ast.png
nav_order: 900
nav_order: 300
description: Aider uses a map of your git repository to provide code context to LLMs.
---

View file

@ -1,6 +1,6 @@
---
parent: More info
nav_order: 900
nav_order: 400
description: You can script aider via the command line or python.
---

View file

@ -28,6 +28,7 @@ cog.out(get_help_md())
| **/git** | Run a git command (output excluded from chat) |
| **/help** | Ask questions about aider |
| **/lint** | Lint and fix in-chat files or all dirty files if none in chat |
| **/load** | Load and execute commands from a file |
| **/ls** | List all known files and indicate which are included in the chat session |
| **/map** | Print out the current repository map |
| **/map-refresh** | Force a refresh of the repository map |
@ -39,6 +40,7 @@ cog.out(get_help_md())
| **/report** | Report a problem by opening a GitHub Issue |
| **/reset** | Drop all files and clear the chat history |
| **/run** | Run a shell command and optionally add the output to the chat (alias: !) |
| **/save** | Save commands to a file that can reconstruct the current chat session's files |
| **/settings** | Print out the current settings |
| **/test** | Run a shell command and add the output to the chat on non-zero exit code |
| **/tokens** | Report on the number of tokens used by the current chat context |

View file

@ -22,7 +22,9 @@ attrs==24.2.0
# jsonschema
# referencing
backoff==2.2.1
# via -r requirements/requirements.in
# via
# -r requirements/requirements.in
# posthog
beautifulsoup4==4.12.3
# via -r requirements/requirements.in
certifi==2024.8.30
@ -104,6 +106,10 @@ mccabe==0.7.0
# via flake8
mdurl==0.1.2
# via markdown-it-py
mixpanel==4.10.1
# via -r requirements/requirements.in
monotonic==1.6
# via posthog
multidict==6.1.0
# via
# aiohttp
@ -128,6 +134,8 @@ pexpect==4.9.0
# via -r requirements/requirements.in
pillow==11.0.0
# via -r requirements/requirements.in
posthog==3.7.0
# via -r requirements/requirements.in
prompt-toolkit==3.0.48
# via -r requirements/requirements.in
propcache==0.2.0
@ -156,6 +164,8 @@ pypandoc==1.14
# via -r requirements/requirements.in
pyperclip==1.9.0
# via -r requirements/requirements.in
python-dateutil==2.9.0.post0
# via posthog
python-dotenv==1.0.1
# via litellm
pyyaml==6.0.2
@ -172,6 +182,8 @@ requests==2.32.3
# via
# huggingface-hub
# litellm
# mixpanel
# posthog
# tiktoken
rich==13.9.3
# via -r requirements/requirements.in
@ -181,6 +193,11 @@ rpds-py==0.20.0
# referencing
scipy==1.13.1
# via -r requirements/requirements.in
six==1.16.0
# via
# mixpanel
# posthog
# python-dateutil
smmap==5.0.1
# via gitdb
sniffio==1.3.1
@ -217,7 +234,9 @@ typing-extensions==4.12.2
# pydantic
# pydantic-core
urllib3==2.2.3
# via requests
# via
# mixpanel
# requests
wcwidth==0.2.13
# via prompt-toolkit
yarl==1.16.0

View file

@ -104,6 +104,7 @@ pympler==1.1
# via streamlit
python-dateutil==2.9.0.post0
# via
# -c requirements/../requirements.txt
# pandas
# streamlit
pytz==2024.2
@ -127,7 +128,9 @@ rpds-py==0.20.0
# jsonschema
# referencing
six==1.16.0
# via python-dateutil
# via
# -c requirements/../requirements.txt
# python-dateutil
smmap==5.0.1
# via
# -c requirements/../requirements.txt

View file

@ -135,6 +135,7 @@ pytest==8.3.3
# via -r requirements/requirements-dev.in
python-dateutil==2.9.0.post0
# via
# -c requirements/../requirements.txt
# matplotlib
# pandas
pytz==2024.2
@ -156,7 +157,9 @@ semver==3.0.2
shellingham==1.5.4
# via typer
six==1.16.0
# via python-dateutil
# via
# -c requirements/../requirements.txt
# python-dateutil
snowballstemmer==2.2.0
# via sphinx
sphinx==8.1.3

View file

@ -24,6 +24,8 @@ litellm
flake8
importlib_resources
pyperclip
posthog
mixpanel
pexpect
json5
psutil

View file

@ -9,6 +9,10 @@ else
ARG=$1
fi
if [ "$ARG" != "--check" ]; then
tail -1000 ~/.aider/analytics.jsonl > aider/website/assets/sample-analytics.jsonl
fi
# README.md before index.md, because index.md uses cog to include README.md
cog $ARG \
README.md \
@ -22,4 +26,5 @@ cog $ARG \
aider/website/docs/config/adv-model-settings.md \
aider/website/docs/leaderboards/index.md \
aider/website/docs/llms/other.md \
aider/website/docs/more/infinite-output.md
aider/website/docs/more/infinite-output.md \
aider/website/docs/legal/privacy.md

View file

@ -0,0 +1,100 @@
import json
import os
import tempfile
from pathlib import Path
from unittest.mock import patch
import pytest
from aider.analytics import Analytics
@pytest.fixture
def temp_analytics_file():
with tempfile.NamedTemporaryFile(delete=False) as f:
yield f.name
os.unlink(f.name)
@pytest.fixture
def temp_data_dir(monkeypatch):
with tempfile.TemporaryDirectory() as tmpdir:
temp_dir = Path(tmpdir)
monkeypatch.setattr(Path, "home", lambda: temp_dir)
yield temp_dir
def test_analytics_initialization(temp_data_dir):
analytics = Analytics(permanently_disable=True)
assert analytics.mp is None
assert analytics.ph is None
assert analytics.permanently_disable is True
assert analytics.user_id is not None
def test_analytics_enable_disable(temp_data_dir):
analytics = Analytics()
analytics.asked_opt_in = True
analytics.enable()
assert analytics.mp is not None
assert analytics.ph is not None
analytics.disable(permanently=False)
assert analytics.mp is None
assert analytics.ph is None
assert analytics.permanently_disable is not True
analytics.disable(permanently=True)
assert analytics.permanently_disable is True
def test_analytics_data_persistence(temp_data_dir):
analytics1 = Analytics()
user_id = analytics1.user_id
analytics2 = Analytics()
assert analytics2.user_id == user_id
def test_analytics_event_logging(temp_analytics_file, temp_data_dir):
analytics = Analytics(logfile=temp_analytics_file)
analytics.asked_opt_in = True
analytics.enable()
test_event = "test_event"
test_properties = {"test_key": "test_value"}
with patch.object(analytics.mp, "track") as mock_mp_track:
with patch.object(analytics.ph, "capture") as mock_ph_capture:
analytics.event(test_event, **test_properties)
mock_mp_track.assert_called_once()
mock_ph_capture.assert_called_once()
# Verify logfile
with open(temp_analytics_file) as f:
log_entry = json.loads(f.read().strip())
assert log_entry["event"] == test_event
assert "test_key" in log_entry["properties"]
def test_system_info(temp_data_dir):
analytics = Analytics()
sys_info = analytics.get_system_info()
assert "python_version" in sys_info
assert "os_platform" in sys_info
assert "os_release" in sys_info
assert "machine" in sys_info
def test_need_to_ask(temp_data_dir):
analytics = Analytics()
assert analytics.need_to_ask() is True
analytics.asked_opt_in = True
assert analytics.need_to_ask() is False
analytics.permanently_disable = True
assert analytics.need_to_ask() is False

View file

@ -1,5 +1,6 @@
import codecs
import os
import re
import shutil
import sys
import tempfile
@ -642,6 +643,238 @@ class TestCommands(TestCase):
del commands
del repo
def test_cmd_save_and_load(self):
with GitTemporaryDirectory() as repo_dir:
io = InputOutput(pretty=False, fancy_input=False, yes=True)
coder = Coder.create(self.GPT35, None, io)
commands = Commands(io, coder)
# Create some test files
test_files = {
"file1.txt": "Content of file 1",
"file2.py": "print('Content of file 2')",
"subdir/file3.md": "# Content of file 3",
}
for file_path, content in test_files.items():
full_path = Path(repo_dir) / file_path
full_path.parent.mkdir(parents=True, exist_ok=True)
full_path.write_text(content)
# Add some files as editable and some as read-only
commands.cmd_add("file1.txt file2.py")
commands.cmd_read_only("subdir/file3.md")
# Save the session to a file
session_file = "test_session.txt"
commands.cmd_save(session_file)
# Verify the session file was created and contains the expected commands
self.assertTrue(Path(session_file).exists())
with open(session_file, encoding=io.encoding) as f:
commands_text = f.read().splitlines()
# Convert paths to absolute for comparison
abs_file1 = str(Path("file1.txt").resolve())
abs_file2 = str(Path("file2.py").resolve())
abs_file3 = str(Path("subdir/file3.md").resolve())
# Check each line for matching paths using os.path.samefile
found_file1 = found_file2 = found_file3 = False
for line in commands_text:
if line.startswith("/add "):
path = Path(line[5:].strip()).resolve()
if os.path.samefile(str(path), abs_file1):
found_file1 = True
elif os.path.samefile(str(path), abs_file2):
found_file2 = True
elif line.startswith("/read-only "):
path = Path(line[11:]).resolve()
if os.path.samefile(str(path), abs_file3):
found_file3 = True
self.assertTrue(found_file1, "file1.txt not found in commands")
self.assertTrue(found_file2, "file2.py not found in commands")
self.assertTrue(found_file3, "file3.md not found in commands")
# Clear the current session
commands.cmd_reset("")
self.assertEqual(len(coder.abs_fnames), 0)
self.assertEqual(len(coder.abs_read_only_fnames), 0)
# Load the session back
commands.cmd_load(session_file)
# Verify files were restored correctly
added_files = {Path(coder.get_rel_fname(f)).as_posix() for f in coder.abs_fnames}
read_only_files = {
Path(coder.get_rel_fname(f)).as_posix() for f in coder.abs_read_only_fnames
}
self.assertEqual(added_files, {"file1.txt", "file2.py"})
self.assertEqual(read_only_files, {"subdir/file3.md"})
# Clean up
Path(session_file).unlink()
def test_cmd_save_and_load_with_external_file(self):
with tempfile.NamedTemporaryFile(mode="w", delete=False) as external_file:
external_file.write("External file content")
external_file_path = external_file.name
try:
with GitTemporaryDirectory() as repo_dir:
io = InputOutput(pretty=False, fancy_input=False, yes=True)
coder = Coder.create(self.GPT35, None, io)
commands = Commands(io, coder)
# Create some test files in the repo
test_files = {
"file1.txt": "Content of file 1",
"file2.py": "print('Content of file 2')",
}
for file_path, content in test_files.items():
full_path = Path(repo_dir) / file_path
full_path.parent.mkdir(parents=True, exist_ok=True)
full_path.write_text(content)
# Add some files as editable and some as read-only
commands.cmd_add(str(Path("file1.txt")))
commands.cmd_read_only(external_file_path)
# Save the session to a file
session_file = str(Path("test_session.txt"))
commands.cmd_save(session_file)
# Verify the session file was created and contains the expected commands
self.assertTrue(Path(session_file).exists())
with open(session_file, encoding=io.encoding) as f:
commands_text = f.read()
commands_text = re.sub(
r"/add +", "/add ", commands_text
) # Normalize add command spaces
self.assertIn("/add file1.txt", commands_text)
# Split commands and check each one
for line in commands_text.splitlines():
if line.startswith("/read-only "):
saved_path = line.split(" ", 1)[1]
if os.path.samefile(saved_path, external_file_path):
break
else:
self.fail(f"No matching read-only command found for {external_file_path}")
# Clear the current session
commands.cmd_reset("")
self.assertEqual(len(coder.abs_fnames), 0)
self.assertEqual(len(coder.abs_read_only_fnames), 0)
# Load the session back
commands.cmd_load(session_file)
# Verify files were restored correctly
added_files = {coder.get_rel_fname(f) for f in coder.abs_fnames}
read_only_files = {coder.get_rel_fname(f) for f in coder.abs_read_only_fnames}
self.assertEqual(added_files, {str(Path("file1.txt"))})
self.assertTrue(
any(os.path.samefile(external_file_path, f) for f in read_only_files)
)
# Clean up
Path(session_file).unlink()
finally:
os.unlink(external_file_path)
def test_cmd_save_and_load_with_multiple_external_files(self):
with (
tempfile.NamedTemporaryFile(mode="w", delete=False) as external_file1,
tempfile.NamedTemporaryFile(mode="w", delete=False) as external_file2,
):
external_file1.write("External file 1 content")
external_file2.write("External file 2 content")
external_file1_path = external_file1.name
external_file2_path = external_file2.name
try:
with GitTemporaryDirectory() as repo_dir:
io = InputOutput(pretty=False, fancy_input=False, yes=True)
coder = Coder.create(self.GPT35, None, io)
commands = Commands(io, coder)
# Create some test files in the repo
test_files = {
"internal1.txt": "Content of internal file 1",
"internal2.txt": "Content of internal file 2",
}
for file_path, content in test_files.items():
full_path = Path(repo_dir) / file_path
full_path.parent.mkdir(parents=True, exist_ok=True)
full_path.write_text(content)
# Add files as editable and read-only
commands.cmd_add(str(Path("internal1.txt")))
commands.cmd_read_only(external_file1_path)
commands.cmd_read_only(external_file2_path)
# Save the session to a file
session_file = str(Path("test_session.txt"))
commands.cmd_save(session_file)
# Verify the session file was created and contains the expected commands
self.assertTrue(Path(session_file).exists())
with open(session_file, encoding=io.encoding) as f:
commands_text = f.read()
commands_text = re.sub(
r"/add +", "/add ", commands_text
) # Normalize add command spaces
self.assertIn("/add internal1.txt", commands_text)
# Split commands and check each one
for line in commands_text.splitlines():
if line.startswith("/read-only "):
saved_path = line.split(" ", 1)[1]
if os.path.samefile(saved_path, external_file1_path):
break
else:
self.fail(f"No matching read-only command found for {external_file1_path}")
# Split commands and check each one
for line in commands_text.splitlines():
if line.startswith("/read-only "):
saved_path = line.split(" ", 1)[1]
if os.path.samefile(saved_path, external_file2_path):
break
else:
self.fail(f"No matching read-only command found for {external_file2_path}")
# Clear the current session
commands.cmd_reset("")
self.assertEqual(len(coder.abs_fnames), 0)
self.assertEqual(len(coder.abs_read_only_fnames), 0)
# Load the session back
commands.cmd_load(session_file)
# Verify files were restored correctly
added_files = {coder.get_rel_fname(f) for f in coder.abs_fnames}
read_only_files = {coder.get_rel_fname(f) for f in coder.abs_read_only_fnames}
self.assertEqual(added_files, {str(Path("internal1.txt"))})
self.assertTrue(
all(
any(os.path.samefile(external_path, fname) for fname in read_only_files)
for external_path in [external_file1_path, external_file2_path]
)
)
# Clean up
Path(session_file).unlink()
finally:
os.unlink(external_file1_path)
os.unlink(external_file2_path)
def test_cmd_read_only_with_glob_pattern(self):
with GitTemporaryDirectory() as repo_dir:
io = InputOutput(pretty=False, fancy_input=False, yes=False)
@ -1064,7 +1297,10 @@ class TestCommands(TestCase):
external_file_path = external_file.name
try:
with GitTemporaryDirectory():
with GitTemporaryDirectory() as repo_dir:
# Create a test file in the repo
repo_file = Path(repo_dir) / "repo_file.txt"
repo_file.write_text("Repo file content")
io = InputOutput(pretty=False, fancy_input=False, yes=False)
coder = Coder.create(self.GPT35, None, io)
commands = Commands(io, coder)

View file

@ -32,6 +32,8 @@ class TestMain(TestCase):
os.environ["HOME"] = self.homedir_obj.name
self.input_patcher = patch("builtins.input", return_value=None)
self.mock_input = self.input_patcher.start()
self.webbrowser_patcher = patch("webbrowser.open")
self.mock_webbrowser = self.webbrowser_patcher.start()
def tearDown(self):
os.chdir(self.original_cwd)
@ -40,6 +42,7 @@ class TestMain(TestCase):
os.environ.clear()
os.environ.update(self.original_env)
self.input_patcher.stop()
self.webbrowser_patcher.stop()
def test_main_with_empty_dir_no_files_on_command(self):
main(["--no-git", "--exit"], input=DummyInput(), output=DummyOutput())

View file

@ -1,12 +1,18 @@
import unittest
from unittest.mock import ANY, MagicMock, patch
from aider.models import Model, get_model_info, sanity_check_model, sanity_check_models
from aider.models import (
Model,
ModelInfoManager,
sanity_check_model,
sanity_check_models,
)
class TestModels(unittest.TestCase):
def test_get_model_info_nonexistent(self):
info = get_model_info("non-existent-model")
manager = ModelInfoManager()
info = manager.get_model_info("non-existent-model")
self.assertEqual(info, {})
def test_max_context_tokens(self):
@ -73,8 +79,11 @@ class TestModels(unittest.TestCase):
result
) # Should return True because there's a problem with the editor model
mock_io.tool_warning.assert_called_with(ANY) # Ensure a warning was issued
self.assertGreaterEqual(mock_io.tool_warning.call_count, 2) # Expect two warnings
warning_messages = [call.args[0] for call in mock_io.tool_warning.call_args_list]
print("Warning messages:", warning_messages) # Add this line
self.assertGreaterEqual(mock_io.tool_warning.call_count, 1) # Expect two warnings
self.assertTrue(
any("bogus-model" in msg for msg in warning_messages)
) # Check that one of the warnings mentions the bogus model

View file

@ -6,6 +6,7 @@ from unittest import mock
import pytest
from git import GitError, Repo
from aider import urls
from aider.main import sanity_check_repo
@ -99,7 +100,8 @@ def test_detached_head_state(create_repo, mock_io):
mock_io.tool_output.assert_not_called()
def test_git_index_version_greater_than_2(create_repo, mock_io):
@mock.patch("webbrowser.open")
def test_git_index_version_greater_than_2(mock_browser, create_repo, mock_io):
repo_path, repo = create_repo
# Set the git index version to 3
set_git_index_version(str(repo_path), 3)
@ -125,7 +127,9 @@ def test_git_index_version_greater_than_2(create_repo, mock_io):
"You may be able to convert your repo: git update-index --index-version=2"
)
mock_io.tool_output.assert_any_call("Or run aider --no-git to proceed without using git.")
mock_io.tool_output.assert_any_call("https://github.com/Aider-AI/aider/issues/211")
mock_io.confirm_ask.assert_any_call(
"Open documentation url for more info?", subject=urls.git_index_version
)
def test_bare_repository(create_repo, mock_io, tmp_path):