mirror of
https://github.com/Aider-AI/aider.git
synced 2025-06-01 18:25:00 +00:00
Merge branch 'main' into watch
This commit is contained in:
commit
5a974483b9
39 changed files with 1421 additions and 122 deletions
16
HISTORY.md
16
HISTORY.md
|
@ -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
164
aider/analytics.py
Normal 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()
|
|
@ -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",
|
||||
|
|
|
@ -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 ""
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
11
aider/io.py
11
aider/io.py
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
129
aider/models.py
129
aider/models.py
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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."
|
||||
)
|
||||
|
|
|
@ -42,8 +42,8 @@ def retry_exceptions():
|
|||
openai.UnprocessableEntityError,
|
||||
openai.RateLimitError,
|
||||
openai.APIConnectionError,
|
||||
openai.APIError,
|
||||
openai.APIStatusError,
|
||||
# openai.APIError,
|
||||
# openai.APIStatusError,
|
||||
openai.InternalServerError,
|
||||
)
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
|
147
aider/website/assets/sample-analytics.jsonl
Normal file
147
aider/website/assets/sample-analytics.jsonl
Normal 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}
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
parent: More info
|
||||
nav_order: 800
|
||||
nav_order: 100
|
||||
description: Aider is tightly integrated with git.
|
||||
---
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
parent: More info
|
||||
nav_order: 900
|
||||
nav_order: 200
|
||||
description: Aider supports pretty much all popular coding languages.
|
||||
---
|
||||
# Supported languages
|
||||
|
|
104
aider/website/docs/legal/privacy.md
Normal file
104
aider/website/docs/legal/privacy.md
Normal 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.
|
||||
|
||||
### Children’s 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>
|
109
aider/website/docs/more/analytics.md
Normal file
109
aider/website/docs/more/analytics.md
Normal 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.
|
||||
|
|
@ -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.
|
||||
---
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
parent: More info
|
||||
nav_order: 920
|
||||
nav_order: 480
|
||||
description: Aider can handle "infinite output" from models that support prefill.
|
||||
---
|
||||
|
||||
|
|
|
@ -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.
|
||||
---
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
parent: More info
|
||||
nav_order: 900
|
||||
nav_order: 400
|
||||
description: You can script aider via the command line or python.
|
||||
---
|
||||
|
||||
|
|
|
@ -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 |
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -24,6 +24,8 @@ litellm
|
|||
flake8
|
||||
importlib_resources
|
||||
pyperclip
|
||||
posthog
|
||||
mixpanel
|
||||
pexpect
|
||||
json5
|
||||
psutil
|
||||
|
|
|
@ -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
|
||||
|
|
100
tests/basic/test_analytics.py
Normal file
100
tests/basic/test_analytics.py
Normal 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
|
|
@ -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)
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue