chatmcp-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aider/__init__.py +20 -0
- aider/__main__.py +4 -0
- aider/_version.py +21 -0
- aider/analytics.py +250 -0
- aider/args.py +926 -0
- aider/args_formatter.py +228 -0
- aider/coders/__init__.py +34 -0
- aider/coders/architect_coder.py +48 -0
- aider/coders/architect_prompts.py +40 -0
- aider/coders/ask_coder.py +9 -0
- aider/coders/ask_prompts.py +35 -0
- aider/coders/base_coder.py +2483 -0
- aider/coders/base_prompts.py +60 -0
- aider/coders/chat_chunks.py +64 -0
- aider/coders/context_coder.py +53 -0
- aider/coders/context_prompts.py +75 -0
- aider/coders/editblock_coder.py +657 -0
- aider/coders/editblock_fenced_coder.py +10 -0
- aider/coders/editblock_fenced_prompts.py +143 -0
- aider/coders/editblock_func_coder.py +141 -0
- aider/coders/editblock_func_prompts.py +27 -0
- aider/coders/editblock_prompts.py +174 -0
- aider/coders/editor_diff_fenced_coder.py +9 -0
- aider/coders/editor_diff_fenced_prompts.py +11 -0
- aider/coders/editor_editblock_coder.py +8 -0
- aider/coders/editor_editblock_prompts.py +18 -0
- aider/coders/editor_whole_coder.py +8 -0
- aider/coders/editor_whole_prompts.py +10 -0
- aider/coders/help_coder.py +16 -0
- aider/coders/help_prompts.py +46 -0
- aider/coders/patch_coder.py +706 -0
- aider/coders/patch_prompts.py +161 -0
- aider/coders/search_replace.py +757 -0
- aider/coders/shell.py +37 -0
- aider/coders/single_wholefile_func_coder.py +102 -0
- aider/coders/single_wholefile_func_prompts.py +27 -0
- aider/coders/udiff_coder.py +429 -0
- aider/coders/udiff_prompts.py +115 -0
- aider/coders/udiff_simple.py +14 -0
- aider/coders/udiff_simple_prompts.py +25 -0
- aider/coders/wholefile_coder.py +144 -0
- aider/coders/wholefile_func_coder.py +134 -0
- aider/coders/wholefile_func_prompts.py +27 -0
- aider/coders/wholefile_prompts.py +67 -0
- aider/commands.py +1665 -0
- aider/copypaste.py +72 -0
- aider/deprecated.py +126 -0
- aider/diffs.py +128 -0
- aider/dump.py +29 -0
- aider/editor.py +147 -0
- aider/exceptions.py +107 -0
- aider/format_settings.py +26 -0
- aider/gui.py +545 -0
- aider/help.py +163 -0
- aider/help_pats.py +19 -0
- aider/history.py +143 -0
- aider/io.py +1175 -0
- aider/linter.py +304 -0
- aider/llm.py +47 -0
- aider/main.py +1267 -0
- aider/mdstream.py +243 -0
- aider/models.py +1286 -0
- aider/onboarding.py +428 -0
- aider/openrouter.py +128 -0
- aider/prompts.py +64 -0
- aider/queries/tree-sitter-language-pack/README.md +7 -0
- aider/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/c-tags.scm +9 -0
- aider/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
- aider/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
- aider/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
- aider/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
- aider/queries/tree-sitter-language-pack/d-tags.scm +26 -0
- aider/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
- aider/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
- aider/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
- aider/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
- aider/queries/tree-sitter-language-pack/go-tags.scm +42 -0
- aider/queries/tree-sitter-language-pack/java-tags.scm +20 -0
- aider/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
- aider/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
- aider/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
- aider/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
- aider/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
- aider/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/python-tags.scm +14 -0
- aider/queries/tree-sitter-language-pack/r-tags.scm +21 -0
- aider/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
- aider/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
- aider/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
- aider/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
- aider/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
- aider/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
- aider/queries/tree-sitter-languages/README.md +23 -0
- aider/queries/tree-sitter-languages/c-tags.scm +9 -0
- aider/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
- aider/queries/tree-sitter-languages/cpp-tags.scm +15 -0
- aider/queries/tree-sitter-languages/dart-tags.scm +91 -0
- aider/queries/tree-sitter-languages/elisp-tags.scm +8 -0
- aider/queries/tree-sitter-languages/elixir-tags.scm +54 -0
- aider/queries/tree-sitter-languages/elm-tags.scm +19 -0
- aider/queries/tree-sitter-languages/go-tags.scm +30 -0
- aider/queries/tree-sitter-languages/hcl-tags.scm +77 -0
- aider/queries/tree-sitter-languages/java-tags.scm +20 -0
- aider/queries/tree-sitter-languages/javascript-tags.scm +88 -0
- aider/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
- aider/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
- aider/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
- aider/queries/tree-sitter-languages/php-tags.scm +26 -0
- aider/queries/tree-sitter-languages/python-tags.scm +12 -0
- aider/queries/tree-sitter-languages/ql-tags.scm +26 -0
- aider/queries/tree-sitter-languages/ruby-tags.scm +64 -0
- aider/queries/tree-sitter-languages/rust-tags.scm +60 -0
- aider/queries/tree-sitter-languages/scala-tags.scm +65 -0
- aider/queries/tree-sitter-languages/typescript-tags.scm +41 -0
- aider/reasoning_tags.py +82 -0
- aider/repo.py +623 -0
- aider/repomap.py +847 -0
- aider/report.py +200 -0
- aider/resources/__init__.py +3 -0
- aider/resources/model-metadata.json +468 -0
- aider/resources/model-settings.yml +1767 -0
- aider/run_cmd.py +132 -0
- aider/scrape.py +284 -0
- aider/sendchat.py +61 -0
- aider/special.py +203 -0
- aider/urls.py +17 -0
- aider/utils.py +338 -0
- aider/versioncheck.py +113 -0
- aider/voice.py +187 -0
- aider/waiting.py +221 -0
- aider/watch.py +318 -0
- aider/watch_prompts.py +12 -0
- aider/website/Gemfile +8 -0
- aider/website/_includes/blame.md +162 -0
- aider/website/_includes/get-started.md +22 -0
- aider/website/_includes/help-tip.md +5 -0
- aider/website/_includes/help.md +24 -0
- aider/website/_includes/install.md +5 -0
- aider/website/_includes/keys.md +4 -0
- aider/website/_includes/model-warnings.md +67 -0
- aider/website/_includes/multi-line.md +22 -0
- aider/website/_includes/python-m-aider.md +5 -0
- aider/website/_includes/recording.css +228 -0
- aider/website/_includes/recording.md +34 -0
- aider/website/_includes/replit-pipx.md +9 -0
- aider/website/_includes/works-best.md +1 -0
- aider/website/_sass/custom/custom.scss +103 -0
- aider/website/docs/config/adv-model-settings.md +1881 -0
- aider/website/docs/config/aider_conf.md +527 -0
- aider/website/docs/config/api-keys.md +90 -0
- aider/website/docs/config/dotenv.md +478 -0
- aider/website/docs/config/editor.md +127 -0
- aider/website/docs/config/model-aliases.md +103 -0
- aider/website/docs/config/options.md +843 -0
- aider/website/docs/config/reasoning.md +209 -0
- aider/website/docs/config.md +44 -0
- aider/website/docs/faq.md +378 -0
- aider/website/docs/git.md +76 -0
- aider/website/docs/index.md +47 -0
- aider/website/docs/install/codespaces.md +39 -0
- aider/website/docs/install/docker.md +57 -0
- aider/website/docs/install/optional.md +100 -0
- aider/website/docs/install/replit.md +8 -0
- aider/website/docs/install.md +115 -0
- aider/website/docs/languages.md +264 -0
- aider/website/docs/legal/contributor-agreement.md +111 -0
- aider/website/docs/legal/privacy.md +104 -0
- aider/website/docs/llms/anthropic.md +77 -0
- aider/website/docs/llms/azure.md +48 -0
- aider/website/docs/llms/bedrock.md +132 -0
- aider/website/docs/llms/cohere.md +34 -0
- aider/website/docs/llms/deepseek.md +32 -0
- aider/website/docs/llms/gemini.md +49 -0
- aider/website/docs/llms/github.md +105 -0
- aider/website/docs/llms/groq.md +36 -0
- aider/website/docs/llms/lm-studio.md +39 -0
- aider/website/docs/llms/ollama.md +75 -0
- aider/website/docs/llms/openai-compat.md +39 -0
- aider/website/docs/llms/openai.md +58 -0
- aider/website/docs/llms/openrouter.md +78 -0
- aider/website/docs/llms/other.md +103 -0
- aider/website/docs/llms/vertex.md +50 -0
- aider/website/docs/llms/warnings.md +10 -0
- aider/website/docs/llms/xai.md +53 -0
- aider/website/docs/llms.md +54 -0
- aider/website/docs/more/analytics.md +122 -0
- aider/website/docs/more/edit-formats.md +116 -0
- aider/website/docs/more/infinite-output.md +137 -0
- aider/website/docs/more-info.md +8 -0
- aider/website/docs/recordings/auto-accept-architect.md +31 -0
- aider/website/docs/recordings/dont-drop-original-read-files.md +35 -0
- aider/website/docs/recordings/index.md +21 -0
- aider/website/docs/recordings/model-accepts-settings.md +69 -0
- aider/website/docs/recordings/tree-sitter-language-pack.md +80 -0
- aider/website/docs/repomap.md +112 -0
- aider/website/docs/scripting.md +100 -0
- aider/website/docs/troubleshooting/aider-not-found.md +24 -0
- aider/website/docs/troubleshooting/edit-errors.md +76 -0
- aider/website/docs/troubleshooting/imports.md +62 -0
- aider/website/docs/troubleshooting/models-and-keys.md +54 -0
- aider/website/docs/troubleshooting/support.md +79 -0
- aider/website/docs/troubleshooting/token-limits.md +96 -0
- aider/website/docs/troubleshooting/warnings.md +12 -0
- aider/website/docs/troubleshooting.md +11 -0
- aider/website/docs/usage/browser.md +57 -0
- aider/website/docs/usage/caching.md +49 -0
- aider/website/docs/usage/commands.md +132 -0
- aider/website/docs/usage/conventions.md +119 -0
- aider/website/docs/usage/copypaste.md +121 -0
- aider/website/docs/usage/images-urls.md +48 -0
- aider/website/docs/usage/lint-test.md +118 -0
- aider/website/docs/usage/modes.md +211 -0
- aider/website/docs/usage/not-code.md +179 -0
- aider/website/docs/usage/notifications.md +87 -0
- aider/website/docs/usage/tips.md +79 -0
- aider/website/docs/usage/tutorials.md +30 -0
- aider/website/docs/usage/voice.md +121 -0
- aider/website/docs/usage/watch.md +294 -0
- aider/website/docs/usage.md +92 -0
- aider/website/share/index.md +101 -0
- chatmcp_cli-0.1.0.dist-info/METADATA +502 -0
- chatmcp_cli-0.1.0.dist-info/RECORD +228 -0
- chatmcp_cli-0.1.0.dist-info/WHEEL +5 -0
- chatmcp_cli-0.1.0.dist-info/entry_points.txt +3 -0
- chatmcp_cli-0.1.0.dist-info/licenses/LICENSE.txt +202 -0
- chatmcp_cli-0.1.0.dist-info/top_level.txt +1 -0
aider/utils.py
ADDED
@@ -0,0 +1,338 @@
|
|
1
|
+
import os
|
2
|
+
import platform
|
3
|
+
import subprocess
|
4
|
+
import sys
|
5
|
+
import tempfile
|
6
|
+
from pathlib import Path
|
7
|
+
|
8
|
+
import oslex
|
9
|
+
|
10
|
+
from aider.dump import dump # noqa: F401
|
11
|
+
from aider.waiting import Spinner
|
12
|
+
|
13
|
+
IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".webp", ".pdf"}
|
14
|
+
|
15
|
+
|
16
|
+
class IgnorantTemporaryDirectory:
|
17
|
+
def __init__(self):
|
18
|
+
if sys.version_info >= (3, 10):
|
19
|
+
self.temp_dir = tempfile.TemporaryDirectory(ignore_cleanup_errors=True)
|
20
|
+
else:
|
21
|
+
self.temp_dir = tempfile.TemporaryDirectory()
|
22
|
+
|
23
|
+
def __enter__(self):
|
24
|
+
return self.temp_dir.__enter__()
|
25
|
+
|
26
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
27
|
+
self.cleanup()
|
28
|
+
|
29
|
+
def cleanup(self):
|
30
|
+
try:
|
31
|
+
self.temp_dir.cleanup()
|
32
|
+
except (OSError, PermissionError, RecursionError):
|
33
|
+
pass # Ignore errors (Windows and potential recursion)
|
34
|
+
|
35
|
+
def __getattr__(self, item):
|
36
|
+
return getattr(self.temp_dir, item)
|
37
|
+
|
38
|
+
|
39
|
+
class ChdirTemporaryDirectory(IgnorantTemporaryDirectory):
|
40
|
+
def __init__(self):
|
41
|
+
try:
|
42
|
+
self.cwd = os.getcwd()
|
43
|
+
except FileNotFoundError:
|
44
|
+
self.cwd = None
|
45
|
+
|
46
|
+
super().__init__()
|
47
|
+
|
48
|
+
def __enter__(self):
|
49
|
+
res = super().__enter__()
|
50
|
+
os.chdir(Path(self.temp_dir.name).resolve())
|
51
|
+
return res
|
52
|
+
|
53
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
54
|
+
if self.cwd:
|
55
|
+
try:
|
56
|
+
os.chdir(self.cwd)
|
57
|
+
except FileNotFoundError:
|
58
|
+
pass
|
59
|
+
super().__exit__(exc_type, exc_val, exc_tb)
|
60
|
+
|
61
|
+
|
62
|
+
class GitTemporaryDirectory(ChdirTemporaryDirectory):
|
63
|
+
def __enter__(self):
|
64
|
+
dname = super().__enter__()
|
65
|
+
self.repo = make_repo(dname)
|
66
|
+
return dname
|
67
|
+
|
68
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
69
|
+
del self.repo
|
70
|
+
super().__exit__(exc_type, exc_val, exc_tb)
|
71
|
+
|
72
|
+
|
73
|
+
def make_repo(path=None):
|
74
|
+
import git
|
75
|
+
|
76
|
+
if not path:
|
77
|
+
path = "."
|
78
|
+
repo = git.Repo.init(path)
|
79
|
+
repo.config_writer().set_value("user", "name", "Test User").release()
|
80
|
+
repo.config_writer().set_value("user", "email", "testuser@example.com").release()
|
81
|
+
|
82
|
+
return repo
|
83
|
+
|
84
|
+
|
85
|
+
def is_image_file(file_name):
|
86
|
+
"""
|
87
|
+
Check if the given file name has an image file extension.
|
88
|
+
|
89
|
+
:param file_name: The name of the file to check.
|
90
|
+
:return: True if the file is an image, False otherwise.
|
91
|
+
"""
|
92
|
+
file_name = str(file_name) # Convert file_name to string
|
93
|
+
return any(file_name.endswith(ext) for ext in IMAGE_EXTENSIONS)
|
94
|
+
|
95
|
+
|
96
|
+
def safe_abs_path(res):
|
97
|
+
"Gives an abs path, which safely returns a full (not 8.3) windows path"
|
98
|
+
res = Path(res).resolve()
|
99
|
+
return str(res)
|
100
|
+
|
101
|
+
|
102
|
+
def format_content(role, content):
|
103
|
+
formatted_lines = []
|
104
|
+
for line in content.splitlines():
|
105
|
+
formatted_lines.append(f"{role} {line}")
|
106
|
+
return "\n".join(formatted_lines)
|
107
|
+
|
108
|
+
|
109
|
+
def format_messages(messages, title=None):
|
110
|
+
output = []
|
111
|
+
if title:
|
112
|
+
output.append(f"{title.upper()} {'*' * 50}")
|
113
|
+
|
114
|
+
for msg in messages:
|
115
|
+
output.append("-------")
|
116
|
+
role = msg["role"].upper()
|
117
|
+
content = msg.get("content")
|
118
|
+
if isinstance(content, list): # Handle list content (e.g., image messages)
|
119
|
+
for item in content:
|
120
|
+
if isinstance(item, dict):
|
121
|
+
for key, value in item.items():
|
122
|
+
if isinstance(value, dict) and "url" in value:
|
123
|
+
output.append(f"{role} {key.capitalize()} URL: {value['url']}")
|
124
|
+
else:
|
125
|
+
output.append(f"{role} {key}: {value}")
|
126
|
+
else:
|
127
|
+
output.append(f"{role} {item}")
|
128
|
+
elif isinstance(content, str): # Handle string content
|
129
|
+
output.append(format_content(role, content))
|
130
|
+
function_call = msg.get("function_call")
|
131
|
+
if function_call:
|
132
|
+
output.append(f"{role} Function Call: {function_call}")
|
133
|
+
|
134
|
+
return "\n".join(output)
|
135
|
+
|
136
|
+
|
137
|
+
def show_messages(messages, title=None, functions=None):
|
138
|
+
formatted_output = format_messages(messages, title)
|
139
|
+
print(formatted_output)
|
140
|
+
|
141
|
+
if functions:
|
142
|
+
dump(functions)
|
143
|
+
|
144
|
+
|
145
|
+
def split_chat_history_markdown(text, include_tool=False):
|
146
|
+
messages = []
|
147
|
+
user = []
|
148
|
+
assistant = []
|
149
|
+
tool = []
|
150
|
+
lines = text.splitlines(keepends=True)
|
151
|
+
|
152
|
+
def append_msg(role, lines):
|
153
|
+
lines = "".join(lines)
|
154
|
+
if lines.strip():
|
155
|
+
messages.append(dict(role=role, content=lines))
|
156
|
+
|
157
|
+
for line in lines:
|
158
|
+
if line.startswith("# "):
|
159
|
+
continue
|
160
|
+
if line.startswith("> "):
|
161
|
+
append_msg("assistant", assistant)
|
162
|
+
assistant = []
|
163
|
+
append_msg("user", user)
|
164
|
+
user = []
|
165
|
+
tool.append(line[2:])
|
166
|
+
continue
|
167
|
+
# if line.startswith("#### /"):
|
168
|
+
# continue
|
169
|
+
|
170
|
+
if line.startswith("#### "):
|
171
|
+
append_msg("assistant", assistant)
|
172
|
+
assistant = []
|
173
|
+
append_msg("tool", tool)
|
174
|
+
tool = []
|
175
|
+
|
176
|
+
content = line[5:]
|
177
|
+
user.append(content)
|
178
|
+
continue
|
179
|
+
|
180
|
+
append_msg("user", user)
|
181
|
+
user = []
|
182
|
+
append_msg("tool", tool)
|
183
|
+
tool = []
|
184
|
+
|
185
|
+
assistant.append(line)
|
186
|
+
|
187
|
+
append_msg("assistant", assistant)
|
188
|
+
append_msg("user", user)
|
189
|
+
|
190
|
+
if not include_tool:
|
191
|
+
messages = [m for m in messages if m["role"] != "tool"]
|
192
|
+
|
193
|
+
return messages
|
194
|
+
|
195
|
+
|
196
|
+
def get_pip_install(args):
|
197
|
+
cmd = [
|
198
|
+
sys.executable,
|
199
|
+
"-m",
|
200
|
+
"pip",
|
201
|
+
"install",
|
202
|
+
"--upgrade",
|
203
|
+
"--upgrade-strategy",
|
204
|
+
"only-if-needed",
|
205
|
+
]
|
206
|
+
cmd += args
|
207
|
+
return cmd
|
208
|
+
|
209
|
+
|
210
|
+
def run_install(cmd):
|
211
|
+
print()
|
212
|
+
print("Installing:", printable_shell_command(cmd))
|
213
|
+
|
214
|
+
try:
|
215
|
+
output = []
|
216
|
+
process = subprocess.Popen(
|
217
|
+
cmd,
|
218
|
+
stdout=subprocess.PIPE,
|
219
|
+
stderr=subprocess.STDOUT,
|
220
|
+
text=True,
|
221
|
+
bufsize=1,
|
222
|
+
universal_newlines=True,
|
223
|
+
encoding=sys.stdout.encoding,
|
224
|
+
errors="replace",
|
225
|
+
)
|
226
|
+
spinner = Spinner("Installing...")
|
227
|
+
|
228
|
+
while True:
|
229
|
+
char = process.stdout.read(1)
|
230
|
+
if not char:
|
231
|
+
break
|
232
|
+
|
233
|
+
output.append(char)
|
234
|
+
spinner.step()
|
235
|
+
|
236
|
+
spinner.end()
|
237
|
+
return_code = process.wait()
|
238
|
+
output = "".join(output)
|
239
|
+
|
240
|
+
if return_code == 0:
|
241
|
+
print("Installation complete.")
|
242
|
+
print()
|
243
|
+
return True, output
|
244
|
+
|
245
|
+
except subprocess.CalledProcessError as e:
|
246
|
+
print(f"\nError running pip install: {e}")
|
247
|
+
|
248
|
+
print("\nInstallation failed.\n")
|
249
|
+
|
250
|
+
return False, output
|
251
|
+
|
252
|
+
|
253
|
+
def find_common_root(abs_fnames):
|
254
|
+
try:
|
255
|
+
if len(abs_fnames) == 1:
|
256
|
+
return safe_abs_path(os.path.dirname(list(abs_fnames)[0]))
|
257
|
+
elif abs_fnames:
|
258
|
+
return safe_abs_path(os.path.commonpath(list(abs_fnames)))
|
259
|
+
except OSError:
|
260
|
+
pass
|
261
|
+
|
262
|
+
try:
|
263
|
+
return safe_abs_path(os.getcwd())
|
264
|
+
except FileNotFoundError:
|
265
|
+
# Fallback if cwd is deleted
|
266
|
+
return "."
|
267
|
+
|
268
|
+
|
269
|
+
def format_tokens(count):
|
270
|
+
if count < 1000:
|
271
|
+
return f"{count}"
|
272
|
+
elif count < 10000:
|
273
|
+
return f"{count / 1000:.1f}k"
|
274
|
+
else:
|
275
|
+
return f"{round(count / 1000)}k"
|
276
|
+
|
277
|
+
|
278
|
+
def touch_file(fname):
|
279
|
+
fname = Path(fname)
|
280
|
+
try:
|
281
|
+
fname.parent.mkdir(parents=True, exist_ok=True)
|
282
|
+
fname.touch()
|
283
|
+
return True
|
284
|
+
except OSError:
|
285
|
+
return False
|
286
|
+
|
287
|
+
|
288
|
+
def check_pip_install_extra(io, module, prompt, pip_install_cmd, self_update=False):
|
289
|
+
if module:
|
290
|
+
try:
|
291
|
+
__import__(module)
|
292
|
+
return True
|
293
|
+
except (ImportError, ModuleNotFoundError, RuntimeError):
|
294
|
+
pass
|
295
|
+
|
296
|
+
cmd = get_pip_install(pip_install_cmd)
|
297
|
+
|
298
|
+
if prompt:
|
299
|
+
io.tool_warning(prompt)
|
300
|
+
|
301
|
+
if self_update and platform.system() == "Windows":
|
302
|
+
io.tool_output("Run this command to update:")
|
303
|
+
print()
|
304
|
+
print(printable_shell_command(cmd)) # plain print so it doesn't line-wrap
|
305
|
+
return
|
306
|
+
|
307
|
+
if not io.confirm_ask("Run pip install?", default="y", subject=printable_shell_command(cmd)):
|
308
|
+
return
|
309
|
+
|
310
|
+
success, output = run_install(cmd)
|
311
|
+
if success:
|
312
|
+
if not module:
|
313
|
+
return True
|
314
|
+
try:
|
315
|
+
__import__(module)
|
316
|
+
return True
|
317
|
+
except (ImportError, ModuleNotFoundError, RuntimeError) as err:
|
318
|
+
io.tool_error(str(err))
|
319
|
+
pass
|
320
|
+
|
321
|
+
io.tool_error(output)
|
322
|
+
|
323
|
+
print()
|
324
|
+
print("Install failed, try running this command manually:")
|
325
|
+
print(printable_shell_command(cmd))
|
326
|
+
|
327
|
+
|
328
|
+
def printable_shell_command(cmd_list):
|
329
|
+
"""
|
330
|
+
Convert a list of command arguments to a properly shell-escaped string.
|
331
|
+
|
332
|
+
Args:
|
333
|
+
cmd_list (list): List of command arguments.
|
334
|
+
|
335
|
+
Returns:
|
336
|
+
str: Shell-escaped command string.
|
337
|
+
"""
|
338
|
+
return oslex.join(cmd_list)
|
aider/versioncheck.py
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
import os
|
2
|
+
import sys
|
3
|
+
import time
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
import packaging.version
|
7
|
+
|
8
|
+
import aider
|
9
|
+
from aider import utils
|
10
|
+
from aider.dump import dump # noqa: F401
|
11
|
+
|
12
|
+
VERSION_CHECK_FNAME = Path.home() / ".aider" / "caches" / "versioncheck"
|
13
|
+
|
14
|
+
|
15
|
+
def install_from_main_branch(io):
|
16
|
+
"""
|
17
|
+
Install the latest version of aider from the main branch of the GitHub repository.
|
18
|
+
"""
|
19
|
+
|
20
|
+
return utils.check_pip_install_extra(
|
21
|
+
io,
|
22
|
+
None,
|
23
|
+
"Install the development version of aider from the main branch?",
|
24
|
+
["git+https://github.com/Aider-AI/aider.git"],
|
25
|
+
self_update=True,
|
26
|
+
)
|
27
|
+
|
28
|
+
|
29
|
+
def install_upgrade(io, latest_version=None):
|
30
|
+
"""
|
31
|
+
Install the latest version of aider from PyPI.
|
32
|
+
"""
|
33
|
+
|
34
|
+
if latest_version:
|
35
|
+
new_ver_text = f"Newer aider version v{latest_version} is available."
|
36
|
+
else:
|
37
|
+
new_ver_text = "Install latest version of aider?"
|
38
|
+
|
39
|
+
docker_image = os.environ.get("AIDER_DOCKER_IMAGE")
|
40
|
+
if docker_image:
|
41
|
+
text = f"""
|
42
|
+
{new_ver_text} To upgrade, run:
|
43
|
+
|
44
|
+
docker pull {docker_image}
|
45
|
+
"""
|
46
|
+
io.tool_warning(text)
|
47
|
+
return True
|
48
|
+
|
49
|
+
success = utils.check_pip_install_extra(
|
50
|
+
io,
|
51
|
+
None,
|
52
|
+
new_ver_text,
|
53
|
+
["aider-chat"],
|
54
|
+
self_update=True,
|
55
|
+
)
|
56
|
+
|
57
|
+
if success:
|
58
|
+
io.tool_output("Re-run aider to use new version.")
|
59
|
+
sys.exit()
|
60
|
+
|
61
|
+
return
|
62
|
+
|
63
|
+
|
64
|
+
def check_version(io, just_check=False, verbose=False):
|
65
|
+
if not just_check and VERSION_CHECK_FNAME.exists():
|
66
|
+
day = 60 * 60 * 24
|
67
|
+
since = time.time() - os.path.getmtime(VERSION_CHECK_FNAME)
|
68
|
+
if 0 < since < day:
|
69
|
+
if verbose:
|
70
|
+
hours = since / 60 / 60
|
71
|
+
io.tool_output(f"Too soon to check version: {hours:.1f} hours")
|
72
|
+
return
|
73
|
+
|
74
|
+
# To keep startup fast, avoid importing this unless needed
|
75
|
+
import requests
|
76
|
+
|
77
|
+
try:
|
78
|
+
response = requests.get("https://pypi.org/pypi/aider-chat/json")
|
79
|
+
data = response.json()
|
80
|
+
latest_version = data["info"]["version"]
|
81
|
+
current_version = aider.__version__
|
82
|
+
|
83
|
+
if just_check or verbose:
|
84
|
+
io.tool_output(f"Current version: {current_version}")
|
85
|
+
io.tool_output(f"Latest version: {latest_version}")
|
86
|
+
|
87
|
+
is_update_available = packaging.version.parse(latest_version) > packaging.version.parse(
|
88
|
+
current_version
|
89
|
+
)
|
90
|
+
except Exception as err:
|
91
|
+
io.tool_error(f"Error checking pypi for new version: {err}")
|
92
|
+
return False
|
93
|
+
finally:
|
94
|
+
VERSION_CHECK_FNAME.parent.mkdir(parents=True, exist_ok=True)
|
95
|
+
VERSION_CHECK_FNAME.touch()
|
96
|
+
|
97
|
+
###
|
98
|
+
# is_update_available = True
|
99
|
+
|
100
|
+
if just_check or verbose:
|
101
|
+
if is_update_available:
|
102
|
+
io.tool_output("Update available")
|
103
|
+
else:
|
104
|
+
io.tool_output("No update available")
|
105
|
+
|
106
|
+
if just_check:
|
107
|
+
return is_update_available
|
108
|
+
|
109
|
+
if not is_update_available:
|
110
|
+
return False
|
111
|
+
|
112
|
+
install_upgrade(io, latest_version)
|
113
|
+
return True
|
aider/voice.py
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
import math
|
2
|
+
import os
|
3
|
+
import queue
|
4
|
+
import tempfile
|
5
|
+
import time
|
6
|
+
import warnings
|
7
|
+
|
8
|
+
from prompt_toolkit.shortcuts import prompt
|
9
|
+
|
10
|
+
from aider.llm import litellm
|
11
|
+
|
12
|
+
from .dump import dump # noqa: F401
|
13
|
+
|
14
|
+
warnings.filterwarnings(
|
15
|
+
"ignore", message="Couldn't find ffmpeg or avconv - defaulting to ffmpeg, but may not work"
|
16
|
+
)
|
17
|
+
warnings.filterwarnings("ignore", category=SyntaxWarning)
|
18
|
+
|
19
|
+
|
20
|
+
from pydub import AudioSegment # noqa
|
21
|
+
from pydub.exceptions import CouldntDecodeError, CouldntEncodeError # noqa
|
22
|
+
|
23
|
+
try:
|
24
|
+
import soundfile as sf
|
25
|
+
except (OSError, ModuleNotFoundError):
|
26
|
+
sf = None
|
27
|
+
|
28
|
+
|
29
|
+
class SoundDeviceError(Exception):
|
30
|
+
pass
|
31
|
+
|
32
|
+
|
33
|
+
class Voice:
|
34
|
+
max_rms = 0
|
35
|
+
min_rms = 1e5
|
36
|
+
pct = 0
|
37
|
+
|
38
|
+
threshold = 0.15
|
39
|
+
|
40
|
+
def __init__(self, audio_format="wav", device_name=None):
|
41
|
+
if sf is None:
|
42
|
+
raise SoundDeviceError
|
43
|
+
try:
|
44
|
+
print("Initializing sound device...")
|
45
|
+
import sounddevice as sd
|
46
|
+
|
47
|
+
self.sd = sd
|
48
|
+
|
49
|
+
devices = sd.query_devices()
|
50
|
+
|
51
|
+
if device_name:
|
52
|
+
# Find the device with matching name
|
53
|
+
device_id = None
|
54
|
+
for i, device in enumerate(devices):
|
55
|
+
if device_name in device["name"]:
|
56
|
+
device_id = i
|
57
|
+
break
|
58
|
+
if device_id is None:
|
59
|
+
available_inputs = [d["name"] for d in devices if d["max_input_channels"] > 0]
|
60
|
+
raise ValueError(
|
61
|
+
f"Device '{device_name}' not found. Available input devices:"
|
62
|
+
f" {available_inputs}"
|
63
|
+
)
|
64
|
+
|
65
|
+
print(f"Using input device: {device_name} (ID: {device_id})")
|
66
|
+
|
67
|
+
self.device_id = device_id
|
68
|
+
else:
|
69
|
+
self.device_id = None
|
70
|
+
|
71
|
+
except (OSError, ModuleNotFoundError):
|
72
|
+
raise SoundDeviceError
|
73
|
+
if audio_format not in ["wav", "mp3", "webm"]:
|
74
|
+
raise ValueError(f"Unsupported audio format: {audio_format}")
|
75
|
+
self.audio_format = audio_format
|
76
|
+
|
77
|
+
def callback(self, indata, frames, time, status):
|
78
|
+
"""This is called (from a separate thread) for each audio block."""
|
79
|
+
import numpy as np
|
80
|
+
|
81
|
+
rms = np.sqrt(np.mean(indata**2))
|
82
|
+
self.max_rms = max(self.max_rms, rms)
|
83
|
+
self.min_rms = min(self.min_rms, rms)
|
84
|
+
|
85
|
+
rng = self.max_rms - self.min_rms
|
86
|
+
if rng > 0.001:
|
87
|
+
self.pct = (rms - self.min_rms) / rng
|
88
|
+
else:
|
89
|
+
self.pct = 0.5
|
90
|
+
|
91
|
+
self.q.put(indata.copy())
|
92
|
+
|
93
|
+
def get_prompt(self):
|
94
|
+
num = 10
|
95
|
+
if math.isnan(self.pct) or self.pct < self.threshold:
|
96
|
+
cnt = 0
|
97
|
+
else:
|
98
|
+
cnt = int(self.pct * 10)
|
99
|
+
|
100
|
+
bar = "░" * cnt + "█" * (num - cnt)
|
101
|
+
bar = bar[:num]
|
102
|
+
|
103
|
+
dur = time.time() - self.start_time
|
104
|
+
return f"Recording, press ENTER when done... {dur:.1f}sec {bar}"
|
105
|
+
|
106
|
+
def record_and_transcribe(self, history=None, language=None):
|
107
|
+
try:
|
108
|
+
return self.raw_record_and_transcribe(history, language)
|
109
|
+
except KeyboardInterrupt:
|
110
|
+
return
|
111
|
+
except SoundDeviceError as e:
|
112
|
+
print(f"Error: {e}")
|
113
|
+
print("Please ensure you have a working audio input device connected and try again.")
|
114
|
+
return
|
115
|
+
|
116
|
+
def raw_record_and_transcribe(self, history, language):
|
117
|
+
self.q = queue.Queue()
|
118
|
+
|
119
|
+
temp_wav = tempfile.mktemp(suffix=".wav")
|
120
|
+
|
121
|
+
try:
|
122
|
+
sample_rate = int(self.sd.query_devices(self.device_id, "input")["default_samplerate"])
|
123
|
+
except (TypeError, ValueError):
|
124
|
+
sample_rate = 16000 # fallback to 16kHz if unable to query device
|
125
|
+
except self.sd.PortAudioError:
|
126
|
+
raise SoundDeviceError(
|
127
|
+
"No audio input device detected. Please check your audio settings and try again."
|
128
|
+
)
|
129
|
+
|
130
|
+
self.start_time = time.time()
|
131
|
+
|
132
|
+
try:
|
133
|
+
with self.sd.InputStream(
|
134
|
+
samplerate=sample_rate, channels=1, callback=self.callback, device=self.device_id
|
135
|
+
):
|
136
|
+
prompt(self.get_prompt, refresh_interval=0.1)
|
137
|
+
except self.sd.PortAudioError as err:
|
138
|
+
raise SoundDeviceError(f"Error accessing audio input device: {err}")
|
139
|
+
|
140
|
+
with sf.SoundFile(temp_wav, mode="x", samplerate=sample_rate, channels=1) as file:
|
141
|
+
while not self.q.empty():
|
142
|
+
file.write(self.q.get())
|
143
|
+
|
144
|
+
use_audio_format = self.audio_format
|
145
|
+
|
146
|
+
# Check file size and offer to convert to mp3 if too large
|
147
|
+
file_size = os.path.getsize(temp_wav)
|
148
|
+
if file_size > 24.9 * 1024 * 1024 and self.audio_format == "wav":
|
149
|
+
print("\nWarning: {temp_wav} is too large, switching to mp3 format.")
|
150
|
+
use_audio_format = "mp3"
|
151
|
+
|
152
|
+
filename = temp_wav
|
153
|
+
if use_audio_format != "wav":
|
154
|
+
try:
|
155
|
+
new_filename = tempfile.mktemp(suffix=f".{use_audio_format}")
|
156
|
+
audio = AudioSegment.from_wav(temp_wav)
|
157
|
+
audio.export(new_filename, format=use_audio_format)
|
158
|
+
os.remove(temp_wav)
|
159
|
+
filename = new_filename
|
160
|
+
except (CouldntDecodeError, CouldntEncodeError) as e:
|
161
|
+
print(f"Error converting audio: {e}")
|
162
|
+
except (OSError, FileNotFoundError) as e:
|
163
|
+
print(f"File system error during conversion: {e}")
|
164
|
+
except Exception as e:
|
165
|
+
print(f"Unexpected error during audio conversion: {e}")
|
166
|
+
|
167
|
+
with open(filename, "rb") as fh:
|
168
|
+
try:
|
169
|
+
transcript = litellm.transcription(
|
170
|
+
model="whisper-1", file=fh, prompt=history, language=language
|
171
|
+
)
|
172
|
+
except Exception as err:
|
173
|
+
print(f"Unable to transcribe {filename}: {err}")
|
174
|
+
return
|
175
|
+
|
176
|
+
if filename != temp_wav:
|
177
|
+
os.remove(filename)
|
178
|
+
|
179
|
+
text = transcript.text
|
180
|
+
return text
|
181
|
+
|
182
|
+
|
183
|
+
if __name__ == "__main__":
|
184
|
+
api_key = os.getenv("OPENAI_API_KEY")
|
185
|
+
if not api_key:
|
186
|
+
raise ValueError("Please set the OPENAI_API_KEY environment variable.")
|
187
|
+
print(Voice().record_and_transcribe())
|