aider-webui 0.1.0__tar.gz
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_webui-0.1.0/LICENSE +21 -0
- aider_webui-0.1.0/PKG-INFO +86 -0
- aider_webui-0.1.0/README.md +57 -0
- aider_webui-0.1.0/pyproject.toml +50 -0
- aider_webui-0.1.0/setup.cfg +4 -0
- aider_webui-0.1.0/src/aider_webui/__init__.py +3 -0
- aider_webui-0.1.0/src/aider_webui/__main__.py +5 -0
- aider_webui-0.1.0/src/aider_webui/chat_parser.py +104 -0
- aider_webui-0.1.0/src/aider_webui/chat_poller.py +131 -0
- aider_webui-0.1.0/src/aider_webui/config_manager.py +61 -0
- aider_webui-0.1.0/src/aider_webui/file_picker.py +204 -0
- aider_webui-0.1.0/src/aider_webui/main.py +553 -0
- aider_webui-0.1.0/src/aider_webui/pty_manager.py +173 -0
- aider_webui-0.1.0/src/aider_webui/session_manager.py +149 -0
- aider_webui-0.1.0/src/aider_webui/static/styles.css +520 -0
- aider_webui-0.1.0/src/aider_webui.egg-info/PKG-INFO +86 -0
- aider_webui-0.1.0/src/aider_webui.egg-info/SOURCES.txt +19 -0
- aider_webui-0.1.0/src/aider_webui.egg-info/dependency_links.txt +1 -0
- aider_webui-0.1.0/src/aider_webui.egg-info/entry_points.txt +2 -0
- aider_webui-0.1.0/src/aider_webui.egg-info/requires.txt +5 -0
- aider_webui-0.1.0/src/aider_webui.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LX_Aider Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aider-webui
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A modern web interface for Aider CLI — split-view Chat UI + Terminal.
|
|
5
|
+
Author: LX_Aider Contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/LX-Aider/aider-webui
|
|
8
|
+
Project-URL: Repository, https://github.com/LX-Aider/aider-webui
|
|
9
|
+
Keywords: aider,webui,ai,coding,assistant,terminal
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Web Environment
|
|
12
|
+
Classifier: Framework :: FastAPI
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: nicegui>=3.8.0
|
|
26
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
27
|
+
Requires-Dist: pywinpty>=2.0.0; sys_platform == "win32"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# Aider WebUI
|
|
31
|
+
|
|
32
|
+
A modern web interface for [Aider CLI](https://aider.chat) — split-view **Chat UI** + **Terminal**.
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
- 💬 **Chat History** — Clean Markdown rendering of AI conversations
|
|
37
|
+
- ⬛ **Full Terminal** — Real xterm.js terminal with ANSI color support
|
|
38
|
+
- 🔀 **Split View** — Resizable panels for chat and terminal side-by-side
|
|
39
|
+
- 🔒 **State Sync** — Input locks while Aider is processing
|
|
40
|
+
- 📁 **File Picker** — Tree-based file browser for `/add`, `/read-only`, `/run` commands
|
|
41
|
+
- 🧩 **Session Isolation** — Each browser tab gets its own PTY process
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install aider-webui
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
1. Make sure [Aider](https://aider.chat) is installed and accessible in your PATH.
|
|
52
|
+
|
|
53
|
+
2. Create a `.env` file in your working directory (optional):
|
|
54
|
+
|
|
55
|
+
```env
|
|
56
|
+
AIDER_COMMAND=aider
|
|
57
|
+
WORKING_DIR=.
|
|
58
|
+
ANTHROPIC_API_KEY=sk-...
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
3. Run:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
aider-webui
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
4. Open `http://localhost:8080` in your browser.
|
|
68
|
+
|
|
69
|
+
## Usage
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Run with default settings
|
|
73
|
+
aider-webui
|
|
74
|
+
|
|
75
|
+
# Or run as a Python module
|
|
76
|
+
python -m aider_webui
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Requirements
|
|
80
|
+
|
|
81
|
+
- Python 3.10+
|
|
82
|
+
- [Aider CLI](https://aider.chat) installed and accessible
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
MIT
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Aider WebUI
|
|
2
|
+
|
|
3
|
+
A modern web interface for [Aider CLI](https://aider.chat) — split-view **Chat UI** + **Terminal**.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 💬 **Chat History** — Clean Markdown rendering of AI conversations
|
|
8
|
+
- ⬛ **Full Terminal** — Real xterm.js terminal with ANSI color support
|
|
9
|
+
- 🔀 **Split View** — Resizable panels for chat and terminal side-by-side
|
|
10
|
+
- 🔒 **State Sync** — Input locks while Aider is processing
|
|
11
|
+
- 📁 **File Picker** — Tree-based file browser for `/add`, `/read-only`, `/run` commands
|
|
12
|
+
- 🧩 **Session Isolation** — Each browser tab gets its own PTY process
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install aider-webui
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
1. Make sure [Aider](https://aider.chat) is installed and accessible in your PATH.
|
|
23
|
+
|
|
24
|
+
2. Create a `.env` file in your working directory (optional):
|
|
25
|
+
|
|
26
|
+
```env
|
|
27
|
+
AIDER_COMMAND=aider
|
|
28
|
+
WORKING_DIR=.
|
|
29
|
+
ANTHROPIC_API_KEY=sk-...
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
3. Run:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
aider-webui
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
4. Open `http://localhost:8080` in your browser.
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Run with default settings
|
|
44
|
+
aider-webui
|
|
45
|
+
|
|
46
|
+
# Or run as a Python module
|
|
47
|
+
python -m aider_webui
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Requirements
|
|
51
|
+
|
|
52
|
+
- Python 3.10+
|
|
53
|
+
- [Aider CLI](https://aider.chat) installed and accessible
|
|
54
|
+
|
|
55
|
+
## License
|
|
56
|
+
|
|
57
|
+
MIT
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "setuptools-scm"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "aider-webui"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "A modern web interface for Aider CLI — split-view Chat UI + Terminal."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "LX_Aider Contributors"},
|
|
14
|
+
]
|
|
15
|
+
keywords = ["aider", "webui", "ai", "coding", "assistant", "terminal"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Environment :: Web Environment",
|
|
19
|
+
"Framework :: FastAPI",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"License :: OSI Approved :: MIT License",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3.10",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Programming Language :: Python :: 3.13",
|
|
28
|
+
"Topic :: Software Development :: User Interfaces",
|
|
29
|
+
]
|
|
30
|
+
dependencies = [
|
|
31
|
+
"nicegui>=3.8.0",
|
|
32
|
+
"python-dotenv>=1.0.0",
|
|
33
|
+
"pywinpty>=2.0.0; sys_platform == 'win32'",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/LX-Aider/aider-webui"
|
|
38
|
+
Repository = "https://github.com/LX-Aider/aider-webui"
|
|
39
|
+
|
|
40
|
+
[project.scripts]
|
|
41
|
+
aider-webui = "aider_webui.main:run"
|
|
42
|
+
|
|
43
|
+
[tool.setuptools.dynamic]
|
|
44
|
+
version = {attr = "aider_webui.__version__"}
|
|
45
|
+
|
|
46
|
+
[tool.setuptools.packages.find]
|
|
47
|
+
where = ["src"]
|
|
48
|
+
|
|
49
|
+
[tool.setuptools.package-data]
|
|
50
|
+
aider_webui = ["static/**"]
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Chat history parser.
|
|
3
|
+
Parses Aider's .aider.chat.history.md file into structured messages.
|
|
4
|
+
|
|
5
|
+
Aider's actual format:
|
|
6
|
+
#### <user message text>
|
|
7
|
+
|
|
8
|
+
<assistant response text>
|
|
9
|
+
|
|
10
|
+
> Tokens: ... Cost: ...
|
|
11
|
+
|
|
12
|
+
Lines starting with '>' are aider system/meta lines (costs, prompts, etc.)
|
|
13
|
+
Lines starting with '# aider chat started' are session markers.
|
|
14
|
+
Lines starting with '####' are user messages.
|
|
15
|
+
Everything else (non-blank, non-meta) after a user message is the assistant response.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import re
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class ChatMessage:
|
|
24
|
+
"""Represents a single chat message."""
|
|
25
|
+
role: str # "user" or "assistant"
|
|
26
|
+
content: str # Markdown content
|
|
27
|
+
|
|
28
|
+
def __eq__(self, other):
|
|
29
|
+
if not isinstance(other, ChatMessage):
|
|
30
|
+
return NotImplemented
|
|
31
|
+
return self.role == other.role and self.content == other.content
|
|
32
|
+
|
|
33
|
+
def __hash__(self):
|
|
34
|
+
return hash((self.role, self.content))
|
|
35
|
+
|
|
36
|
+
def __repr__(self):
|
|
37
|
+
preview = self.content[:50].replace("\n", "\\n")
|
|
38
|
+
return f"ChatMessage(role={self.role!r}, content={preview!r}...)"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# Matches user prompt lines: #### <message>
|
|
42
|
+
_USER_PROMPT_RE = re.compile(r"^####\s+(.+)$", re.MULTILINE)
|
|
43
|
+
|
|
44
|
+
# Matches aider meta/system lines (starting with >)
|
|
45
|
+
_META_LINE_RE = re.compile(r"^>\s+", re.MULTILINE)
|
|
46
|
+
|
|
47
|
+
# Matches session start markers
|
|
48
|
+
_SESSION_RE = re.compile(r"^#\s+aider chat started", re.MULTILINE)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def parse_history(text: str) -> list[ChatMessage]:
|
|
52
|
+
"""
|
|
53
|
+
Parse the full chat history text into a list of ChatMessages.
|
|
54
|
+
|
|
55
|
+
Aider writes chat history in this format:
|
|
56
|
+
#### <user message>
|
|
57
|
+
|
|
58
|
+
<assistant response lines>
|
|
59
|
+
|
|
60
|
+
> Tokens: ... Cost: ...
|
|
61
|
+
"""
|
|
62
|
+
messages: list[ChatMessage] = []
|
|
63
|
+
user_matches = list(_USER_PROMPT_RE.finditer(text))
|
|
64
|
+
|
|
65
|
+
for i, match in enumerate(user_matches):
|
|
66
|
+
# User message is the text after ####
|
|
67
|
+
user_content = match.group(1).strip()
|
|
68
|
+
# Skip raw slash-command entries (e.g. "/ask how are you").
|
|
69
|
+
# Aider writes both the raw command and the processed text;
|
|
70
|
+
# we only want the processed version.
|
|
71
|
+
if user_content and not user_content.startswith("/"):
|
|
72
|
+
messages.append(ChatMessage(role="user", content=user_content))
|
|
73
|
+
|
|
74
|
+
# Assistant response is everything between this user prompt
|
|
75
|
+
# and the next user prompt (or end of text),
|
|
76
|
+
# excluding meta lines (>) and session markers (#)
|
|
77
|
+
resp_start = match.end()
|
|
78
|
+
resp_end = user_matches[i + 1].start() if i + 1 < len(user_matches) else len(text)
|
|
79
|
+
resp_block = text[resp_start:resp_end]
|
|
80
|
+
|
|
81
|
+
# Extract assistant lines: skip blank, meta (>), and session (#) lines
|
|
82
|
+
assistant_lines = []
|
|
83
|
+
for line in resp_block.split("\n"):
|
|
84
|
+
stripped = line.strip()
|
|
85
|
+
if not stripped:
|
|
86
|
+
# Keep blank lines within a response block (for formatting)
|
|
87
|
+
if assistant_lines:
|
|
88
|
+
assistant_lines.append("")
|
|
89
|
+
continue
|
|
90
|
+
if stripped.startswith(">"):
|
|
91
|
+
continue
|
|
92
|
+
if stripped.startswith("# aider chat"):
|
|
93
|
+
continue
|
|
94
|
+
assistant_lines.append(line.rstrip())
|
|
95
|
+
|
|
96
|
+
# Strip trailing blank lines
|
|
97
|
+
while assistant_lines and not assistant_lines[-1].strip():
|
|
98
|
+
assistant_lines.pop()
|
|
99
|
+
|
|
100
|
+
assistant_content = "\n".join(assistant_lines).strip()
|
|
101
|
+
if assistant_content:
|
|
102
|
+
messages.append(ChatMessage(role="assistant", content=assistant_content))
|
|
103
|
+
|
|
104
|
+
return messages
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Chat history file poller.
|
|
3
|
+
Polls .aider.chat.history.md for changes and dispatches new messages.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Callable, Optional
|
|
11
|
+
|
|
12
|
+
from .chat_parser import ChatMessage, parse_history
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ChatPoller:
|
|
18
|
+
"""
|
|
19
|
+
Async task that polls .aider.chat.history.md every 500ms.
|
|
20
|
+
When new content is detected, parses delta and calls the callback.
|
|
21
|
+
|
|
22
|
+
Handles the edge case where Aider truncates and rewrites the file
|
|
23
|
+
by tracking _last_msg_count instead of relying on content prefix matching.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
POLL_INTERVAL = 0.5 # seconds
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
history_file: str,
|
|
31
|
+
on_new_messages: Callable[[list[ChatMessage]], None],
|
|
32
|
+
):
|
|
33
|
+
self._file_path = Path(history_file)
|
|
34
|
+
self._on_new_messages = on_new_messages
|
|
35
|
+
self._task: Optional[asyncio.Task] = None
|
|
36
|
+
self._last_mtime: float = 0
|
|
37
|
+
self._last_size: int = 0
|
|
38
|
+
self._last_msg_count: int = 0
|
|
39
|
+
self._last_messages: list[ChatMessage] = []
|
|
40
|
+
|
|
41
|
+
def start(self):
|
|
42
|
+
"""Start the polling asyncio task."""
|
|
43
|
+
if self._task and not self._task.done():
|
|
44
|
+
logger.warning("Poller already running")
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
self._task = asyncio.create_task(self._poll_loop())
|
|
48
|
+
logger.info(f"Chat poller started: {self._file_path}")
|
|
49
|
+
|
|
50
|
+
def stop(self):
|
|
51
|
+
"""Stop the polling task."""
|
|
52
|
+
if self._task and not self._task.done():
|
|
53
|
+
self._task.cancel()
|
|
54
|
+
logger.info("Chat poller stopped")
|
|
55
|
+
|
|
56
|
+
async def _poll_loop(self):
|
|
57
|
+
"""Main polling loop."""
|
|
58
|
+
try:
|
|
59
|
+
while True:
|
|
60
|
+
await self._check_file()
|
|
61
|
+
await asyncio.sleep(self.POLL_INTERVAL)
|
|
62
|
+
except asyncio.CancelledError:
|
|
63
|
+
pass
|
|
64
|
+
except Exception as e:
|
|
65
|
+
logger.error(f"Chat poller error: {e}", exc_info=True)
|
|
66
|
+
|
|
67
|
+
async def _check_file(self):
|
|
68
|
+
"""Check if the history file has changed.
|
|
69
|
+
|
|
70
|
+
Always re-parses the full file and uses index-based tracking
|
|
71
|
+
to determine truly new messages. This avoids ordering bugs
|
|
72
|
+
caused by delta-only parsing of partial/growing responses.
|
|
73
|
+
"""
|
|
74
|
+
if not self._file_path.exists():
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
stat = self._file_path.stat()
|
|
79
|
+
mtime = stat.st_mtime
|
|
80
|
+
size = stat.st_size
|
|
81
|
+
|
|
82
|
+
# Skip if no change
|
|
83
|
+
if mtime == self._last_mtime and size == self._last_size:
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
self._last_mtime = mtime
|
|
87
|
+
self._last_size = size
|
|
88
|
+
|
|
89
|
+
# Read and parse the FULL file every time
|
|
90
|
+
content = await asyncio.to_thread(
|
|
91
|
+
self._file_path.read_text, "utf-8"
|
|
92
|
+
)
|
|
93
|
+
all_messages = parse_history(content)
|
|
94
|
+
|
|
95
|
+
new_messages: list[ChatMessage] = []
|
|
96
|
+
|
|
97
|
+
if len(all_messages) > self._last_msg_count:
|
|
98
|
+
# New messages were added
|
|
99
|
+
new_messages = all_messages[self._last_msg_count:]
|
|
100
|
+
elif len(all_messages) < self._last_msg_count:
|
|
101
|
+
# File was truncated — reset tracking
|
|
102
|
+
logger.warning(
|
|
103
|
+
f"History file shrank: {self._last_msg_count} → "
|
|
104
|
+
f"{len(all_messages)} messages, resetting count"
|
|
105
|
+
)
|
|
106
|
+
self._last_msg_count = len(all_messages)
|
|
107
|
+
self._last_messages = list(all_messages)
|
|
108
|
+
return
|
|
109
|
+
elif (
|
|
110
|
+
all_messages
|
|
111
|
+
and self._last_messages
|
|
112
|
+
and all_messages[-1] != self._last_messages[-1]
|
|
113
|
+
):
|
|
114
|
+
# Same count but last message content changed (still growing)
|
|
115
|
+
# Send as an update so the UI can refresh it in-place
|
|
116
|
+
new_messages = [all_messages[-1]]
|
|
117
|
+
# Don't increment _last_msg_count — count hasn't changed
|
|
118
|
+
self._last_messages = list(all_messages)
|
|
119
|
+
if new_messages:
|
|
120
|
+
self._on_new_messages(new_messages)
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
self._last_msg_count = len(all_messages)
|
|
124
|
+
self._last_messages = list(all_messages)
|
|
125
|
+
|
|
126
|
+
if new_messages:
|
|
127
|
+
self._on_new_messages(new_messages)
|
|
128
|
+
|
|
129
|
+
except Exception as e:
|
|
130
|
+
logger.error(f"Error reading history file: {e}")
|
|
131
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Environment configuration manager.
|
|
3
|
+
Loads settings from .env file and provides them to PTY processes.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from dotenv import load_dotenv
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ConfigManager:
|
|
12
|
+
"""Manages environment configuration for Aider CLI sessions."""
|
|
13
|
+
|
|
14
|
+
# Keys that will be passed to PTY environment
|
|
15
|
+
PASSTHROUGH_KEYS = [
|
|
16
|
+
"ANTHROPIC_API_KEY",
|
|
17
|
+
"ANTHROPIC_BASE_URL",
|
|
18
|
+
"OPENAI_API_KEY",
|
|
19
|
+
"OPENAI_BASE_URL",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
def __init__(self, env_file: str = ".env"):
|
|
23
|
+
self._env_file = Path(env_file)
|
|
24
|
+
self._config: dict[str, str] = {}
|
|
25
|
+
self._load()
|
|
26
|
+
|
|
27
|
+
def _load(self):
|
|
28
|
+
"""Load configuration from .env file."""
|
|
29
|
+
if self._env_file.exists():
|
|
30
|
+
load_dotenv(self._env_file, override=True)
|
|
31
|
+
|
|
32
|
+
self._config = {
|
|
33
|
+
"AIDER_COMMAND": os.getenv("AIDER_COMMAND", "aider"),
|
|
34
|
+
"WORKING_DIR": os.getenv("WORKING_DIR", "."),
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# Load API keys / base URLs if set
|
|
38
|
+
for key in self.PASSTHROUGH_KEYS:
|
|
39
|
+
value = os.getenv(key)
|
|
40
|
+
if value:
|
|
41
|
+
self._config[key] = value
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def aider_command(self) -> str:
|
|
45
|
+
return self._config["AIDER_COMMAND"]
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def working_dir(self) -> str:
|
|
49
|
+
return str(Path(self._config["WORKING_DIR"]).resolve())
|
|
50
|
+
|
|
51
|
+
def get_env_dict(self) -> dict[str, str]:
|
|
52
|
+
"""Return environment variables dict to pass to PTY process."""
|
|
53
|
+
env = os.environ.copy()
|
|
54
|
+
for key in self.PASSTHROUGH_KEYS:
|
|
55
|
+
if key in self._config:
|
|
56
|
+
env[key] = self._config[key]
|
|
57
|
+
return env
|
|
58
|
+
|
|
59
|
+
def reload(self):
|
|
60
|
+
"""Reload configuration from .env file."""
|
|
61
|
+
self._load()
|