aizen-ai-cli 2.2.2__tar.gz → 2.2.4__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.
- {aizen_ai_cli-2.2.2/aizen_ai_cli.egg-info → aizen_ai_cli-2.2.4}/PKG-INFO +1 -1
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/aizen/config.py +40 -23
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/aizen/main.py +30 -37
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/aizen/tools.py +22 -13
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4/aizen_ai_cli.egg-info}/PKG-INFO +1 -1
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/pyproject.toml +1 -1
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/setup.py +1 -1
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/MANIFEST.in +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/README.md +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/aizen/__init__.py +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/aizen/commands.py +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/aizen/context.py +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/aizen/exceptions.py +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/aizen/logging_config.py +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/aizen/mcp.py +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/aizen/plugins.py +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/aizen/retry.py +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/aizen/session.py +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/aizen/utils.py +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/aizen_ai_cli.egg-info/SOURCES.txt +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/aizen_ai_cli.egg-info/dependency_links.txt +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/aizen_ai_cli.egg-info/entry_points.txt +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/aizen_ai_cli.egg-info/requires.txt +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/aizen_ai_cli.egg-info/top_level.txt +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/requirements.txt +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/setup.cfg +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/tests/test_commands.py +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/tests/test_config.py +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/tests/test_context.py +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/tests/test_main.py +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/tests/test_mcp.py +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/tests/test_session.py +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/tests/test_tools.py +0 -0
- {aizen_ai_cli-2.2.2 → aizen_ai_cli-2.2.4}/tests/test_utils.py +0 -0
|
@@ -3,12 +3,12 @@ import json
|
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
5
|
import shutil
|
|
6
|
+
import ssl
|
|
6
7
|
import sys
|
|
7
8
|
import threading
|
|
8
9
|
import time
|
|
9
|
-
import urllib.request
|
|
10
10
|
import urllib.error
|
|
11
|
-
import
|
|
11
|
+
import urllib.request
|
|
12
12
|
from importlib.metadata import PackageNotFoundError
|
|
13
13
|
from importlib.metadata import version as _pkg_version
|
|
14
14
|
|
|
@@ -21,7 +21,7 @@ logger = logging.getLogger("aizen")
|
|
|
21
21
|
|
|
22
22
|
# Read version from installed package metadata (stays in sync with pyproject.toml).
|
|
23
23
|
# Falls back to a hardcoded value only when running from source without installing.
|
|
24
|
-
_FALLBACK_VERSION = "2.2.
|
|
24
|
+
_FALLBACK_VERSION = "2.2.4"
|
|
25
25
|
try:
|
|
26
26
|
VERSION = _pkg_version("aizen-ai-cli")
|
|
27
27
|
except PackageNotFoundError:
|
|
@@ -29,16 +29,16 @@ except PackageNotFoundError:
|
|
|
29
29
|
CONFIG_PATH = os.path.expanduser("~/.aizen_config.json")
|
|
30
30
|
SESSIONS_DIR = os.path.expanduser("~/.aizen_sessions")
|
|
31
31
|
BACKUPS_DIR = os.path.expanduser("~/.aizen_backups")
|
|
32
|
-
DEFAULT_MODEL = "
|
|
33
|
-
|
|
34
|
-
AIZEN_ASCII = r"""[bold
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
32
|
+
DEFAULT_MODEL = "openrouter/free"
|
|
33
|
+
|
|
34
|
+
AIZEN_ASCII = r"""[bold #ffabf3]
|
|
35
|
+
█████╗ ██╗███████╗███████╗███╗ ██╗
|
|
36
|
+
██╔══██╗██║╚══███╔╝██╔════╝████╗ ██║
|
|
37
|
+
███████║██║ ███╔╝ █████╗ ██╔██╗ ██║
|
|
38
|
+
██╔══██║██║ ███╔╝ ██╔══╝ ██║╚██╗██║
|
|
39
|
+
██║ ██║██║███████╗███████╗██║ ╚████║
|
|
40
|
+
╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚═╝ ╚═══╝
|
|
41
|
+
[/bold #ffabf3]
|
|
42
42
|
"""
|
|
43
43
|
|
|
44
44
|
# Safe commands that auto-execute without confirmation
|
|
@@ -134,7 +134,7 @@ def build_system_prompt(config: dict | None = None) -> str:
|
|
|
134
134
|
f"The following rules are defined by the project maintainers "
|
|
135
135
|
f"(from {rules_file}):\n\n{project_rules}"
|
|
136
136
|
)
|
|
137
|
-
console.print(f"
|
|
137
|
+
console.print(f"[bold #ffabf3][SYSTEM][/bold #ffabf3] Project rules loaded from [#d3fbff]{rules_file}[/#d3fbff]")
|
|
138
138
|
break # Only use the first rules file found
|
|
139
139
|
except Exception as e:
|
|
140
140
|
logger.debug("Failed to load project rules from %s: %s", rules_file, e)
|
|
@@ -258,7 +258,7 @@ def _do_update_check(config: dict):
|
|
|
258
258
|
ctx.load_verify_locations(cafile=certifi.where())
|
|
259
259
|
except ImportError:
|
|
260
260
|
ctx = ssl._create_unverified_context()
|
|
261
|
-
|
|
261
|
+
|
|
262
262
|
url = "https://pypi.org/pypi/aizen-ai-cli/json"
|
|
263
263
|
req = urllib.request.Request(url, headers={"User-Agent": "aizen-ai-cli"})
|
|
264
264
|
with urllib.request.urlopen(req, timeout=3, context=ctx) as response:
|
|
@@ -273,12 +273,20 @@ def _do_update_check(config: dict):
|
|
|
273
273
|
except Exception as e:
|
|
274
274
|
logger.debug("Failed to save config after update check: %s", e)
|
|
275
275
|
|
|
276
|
-
|
|
276
|
+
try:
|
|
277
|
+
latest_parts = tuple(map(int, latest.split('.')))
|
|
278
|
+
current_parts = tuple(map(int, VERSION.split('.')))
|
|
279
|
+
is_newer = latest_parts > current_parts
|
|
280
|
+
except Exception:
|
|
281
|
+
is_newer = latest != VERSION
|
|
282
|
+
|
|
283
|
+
if is_newer:
|
|
277
284
|
console.print(
|
|
278
285
|
f"\n[bold magenta]🔔 Update available:[/bold magenta] v{VERSION} → v{latest}"
|
|
279
286
|
)
|
|
280
287
|
console.print("[dim]Run: pip install -U aizen-ai-cli (or brew upgrade aizen)[/dim]")
|
|
281
|
-
console.print("[dim]Then restart Aizen to use the new version![/dim]\n"
|
|
288
|
+
console.print("[dim]Then restart Aizen to use the new version![/dim]\n"
|
|
289
|
+
)
|
|
282
290
|
except Exception as e:
|
|
283
291
|
logger.debug("Update check failed (network/parsing): %s", e)
|
|
284
292
|
|
|
@@ -295,11 +303,20 @@ def check_for_updates(config: dict | None = None):
|
|
|
295
303
|
# Check if we have a cached latest version that's newer
|
|
296
304
|
cached = config.get("_latest_version")
|
|
297
305
|
if cached and cached != VERSION:
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
306
|
+
try:
|
|
307
|
+
cached_parts = tuple(map(int, cached.split('.')))
|
|
308
|
+
current_parts = tuple(map(int, VERSION.split('.')))
|
|
309
|
+
is_newer = cached_parts > current_parts
|
|
310
|
+
except Exception:
|
|
311
|
+
is_newer = cached != VERSION
|
|
312
|
+
|
|
313
|
+
if is_newer:
|
|
314
|
+
console.print(
|
|
315
|
+
f"\n[bold magenta]🔔 Update available:[/bold magenta] v{VERSION} → v{cached}"
|
|
316
|
+
)
|
|
317
|
+
console.print("[dim]Run: pip install -U aizen-ai-cli (or brew upgrade aizen)[/dim]")
|
|
318
|
+
console.print("[dim]Then restart Aizen to use the new version![/dim]\n"
|
|
319
|
+
)
|
|
303
320
|
return
|
|
304
321
|
|
|
305
322
|
thread = threading.Thread(target=_do_update_check, args=(config,), daemon=True)
|
|
@@ -329,7 +346,7 @@ def _do_fetch_models():
|
|
|
329
346
|
ctx.load_verify_locations(cafile=certifi.where())
|
|
330
347
|
except ImportError:
|
|
331
348
|
ctx = ssl._create_unverified_context()
|
|
332
|
-
|
|
349
|
+
|
|
333
350
|
req = urllib.request.Request("https://openrouter.ai/api/v1/models")
|
|
334
351
|
with urllib.request.urlopen(req, timeout=5, context=ctx) as response:
|
|
335
352
|
data = json.loads(response.read().decode())
|
|
@@ -14,15 +14,15 @@ import sys
|
|
|
14
14
|
from typing import Any
|
|
15
15
|
|
|
16
16
|
from openai import APIConnectionError as OpenAIConnectionError
|
|
17
|
-
from openai import APITimeoutError, AsyncOpenAI, AuthenticationError
|
|
17
|
+
from openai import APITimeoutError, AsyncOpenAI, AuthenticationError, BadRequestError
|
|
18
18
|
from openai import RateLimitError as OpenAIRateLimitError
|
|
19
19
|
from prompt_toolkit import PromptSession
|
|
20
20
|
from prompt_toolkit.filters import completion_is_selected, has_completions
|
|
21
|
-
from prompt_toolkit.formatted_text import
|
|
21
|
+
from prompt_toolkit.formatted_text import FormattedText
|
|
22
22
|
from prompt_toolkit.key_binding import KeyBindings
|
|
23
23
|
from rich.live import Live
|
|
24
24
|
from rich.markdown import Markdown
|
|
25
|
-
from rich.
|
|
25
|
+
from rich.spinner import Spinner
|
|
26
26
|
from rich.text import Text
|
|
27
27
|
|
|
28
28
|
from .commands import AizenCompleter, handle_slash_command
|
|
@@ -212,16 +212,12 @@ async def main_loop():
|
|
|
212
212
|
|
|
213
213
|
# ── Header ──
|
|
214
214
|
console.print(AIZEN_ASCII)
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
header.append(" │ ", style="dim")
|
|
218
|
-
header.append(get_active_model(), style="cyan")
|
|
215
|
+
console.print(f"[bold #ffabf3][SYSTEM][/bold #ffabf3] Initializing Aizen AI v{VERSION}...")
|
|
216
|
+
console.print(f"[bold #ffabf3][SYSTEM][/bold #ffabf3] Model: {get_active_model()}")
|
|
219
217
|
if auto_approve:
|
|
220
|
-
|
|
221
|
-
header.append("YOLO MODE", style="bold red")
|
|
222
|
-
console.print(header)
|
|
218
|
+
console.print("[bold #ffabf3][SYSTEM][/bold #ffabf3] Mode: YOLO")
|
|
223
219
|
console.print(
|
|
224
|
-
"[dim]Type /help for commands • @file to attach • exit to quit[/dim]\n"
|
|
220
|
+
"\n[dim]Type /help for commands • @file to attach • exit to quit[/dim]\n"
|
|
225
221
|
)
|
|
226
222
|
|
|
227
223
|
# ── Keybindings ──
|
|
@@ -239,10 +235,12 @@ async def main_loop():
|
|
|
239
235
|
try:
|
|
240
236
|
# ── Multi-line Input ──
|
|
241
237
|
lines = []
|
|
242
|
-
prompt_html =
|
|
243
|
-
"
|
|
244
|
-
"
|
|
245
|
-
|
|
238
|
+
prompt_html = FormattedText([
|
|
239
|
+
("fg:#ffabf3", "➜"),
|
|
240
|
+
("", " "),
|
|
241
|
+
("fg:#d3fbff", "~"),
|
|
242
|
+
("", " ")
|
|
243
|
+
])
|
|
246
244
|
first_line = await session.prompt_async(prompt_html)
|
|
247
245
|
lines.append(first_line)
|
|
248
246
|
|
|
@@ -250,7 +248,7 @@ async def main_loop():
|
|
|
250
248
|
while lines[-1].rstrip().endswith("\\"):
|
|
251
249
|
lines[-1] = lines[-1].rstrip()[:-1] # Remove trailing backslash
|
|
252
250
|
continuation = await session.prompt_async(
|
|
253
|
-
|
|
251
|
+
FormattedText([("", " ")])
|
|
254
252
|
)
|
|
255
253
|
lines.append(continuation)
|
|
256
254
|
|
|
@@ -346,9 +344,7 @@ async def main_loop():
|
|
|
346
344
|
"Exploring...",
|
|
347
345
|
]
|
|
348
346
|
)
|
|
349
|
-
spinner_display = Text()
|
|
350
|
-
spinner_display.append(" ✦ ", style="bold magenta")
|
|
351
|
-
spinner_display.append(spinner_label, style="dim italic")
|
|
347
|
+
spinner_display = Spinner("dots", text=Text(spinner_label, style="#8e8e93 italic"), style="#ffabf3 bold")
|
|
352
348
|
|
|
353
349
|
try:
|
|
354
350
|
with Live(
|
|
@@ -378,23 +374,14 @@ async def main_loop():
|
|
|
378
374
|
full_content += delta.content
|
|
379
375
|
# Live-render Markdown in a panel
|
|
380
376
|
try:
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
border_style="magenta",
|
|
385
|
-
padding=(1, 2),
|
|
386
|
-
)
|
|
377
|
+
# Prepend AIZEN: styling before the markdown
|
|
378
|
+
display_content = f"**AIZEN:** {full_content}"
|
|
379
|
+
rendered = Markdown(display_content)
|
|
387
380
|
live.update(rendered)
|
|
388
381
|
except Exception:
|
|
389
382
|
# Fallback for incomplete markdown
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
Text(full_content),
|
|
393
|
-
title="[bold magenta]✦ Aizen[/bold magenta]",
|
|
394
|
-
border_style="magenta",
|
|
395
|
-
padding=(1, 2),
|
|
396
|
-
)
|
|
397
|
-
)
|
|
383
|
+
display_text = Text.from_markup(f"[bold #ffabf3]AIZEN:[/bold #ffabf3] {full_content}")
|
|
384
|
+
live.update(display_text)
|
|
398
385
|
|
|
399
386
|
# ── Tool call tokens ──
|
|
400
387
|
if delta.tool_calls:
|
|
@@ -427,10 +414,9 @@ async def main_loop():
|
|
|
427
414
|
]
|
|
428
415
|
if names and not full_content:
|
|
429
416
|
tool_text = Text()
|
|
430
|
-
tool_text.append("
|
|
417
|
+
tool_text.append("AIZEN: ", style="bold #ffabf3")
|
|
431
418
|
tool_text.append(
|
|
432
|
-
f"
|
|
433
|
-
style="dim italic",
|
|
419
|
+
f"Invoking [#d3fbff]{', '.join(names)}[/#d3fbff]...",
|
|
434
420
|
)
|
|
435
421
|
live.update(tool_text)
|
|
436
422
|
|
|
@@ -470,8 +456,15 @@ async def main_loop():
|
|
|
470
456
|
"[dim]Hint: Check your internet connection or API base URL.[/dim]"
|
|
471
457
|
)
|
|
472
458
|
break
|
|
459
|
+
except BadRequestError as e:
|
|
460
|
+
logger.error("Bad request to API: %s", e)
|
|
461
|
+
console.print(f"\n[bold red]Bad Request Error:[/bold red] {e}")
|
|
462
|
+
console.print(
|
|
463
|
+
"[dim]Hint: This usually means the model ID is invalid or the context length was exceeded.[/dim]"
|
|
464
|
+
)
|
|
465
|
+
break
|
|
473
466
|
except Exception as e:
|
|
474
|
-
logger.
|
|
467
|
+
logger.error("Unexpected API error: %s", e)
|
|
475
468
|
console.print(f"\n[bold red]API Error:[/bold red] {e}")
|
|
476
469
|
error_str = str(e).lower()
|
|
477
470
|
if "401" in error_str or "unauthorized" in error_str:
|
|
@@ -697,19 +697,28 @@ def run_command_impl(command: str, auto_approve: bool = False, timeout: int = 12
|
|
|
697
697
|
logger.warning("Command timed out after %ds: %s", timeout, command)
|
|
698
698
|
return f"Error: Command timed out after {timeout} seconds."
|
|
699
699
|
|
|
700
|
-
# Read available stdout non-blockingly
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
700
|
+
# Read available stdout and stderr non-blockingly
|
|
701
|
+
reads = []
|
|
702
|
+
if proc.stdout: reads.append(proc.stdout)
|
|
703
|
+
if proc.stderr: reads.append(proc.stderr)
|
|
704
|
+
|
|
705
|
+
if reads:
|
|
706
|
+
rlist, _, _ = select.select(reads, [], [], 0.1)
|
|
707
|
+
for fd in rlist:
|
|
708
|
+
if fd == proc.stdout:
|
|
709
|
+
line = proc.stdout.readline()
|
|
710
|
+
if line:
|
|
711
|
+
stdout_lines.append(line)
|
|
712
|
+
# Show live output tail (last 15 lines)
|
|
713
|
+
tail = "".join(stdout_lines[-15:])
|
|
714
|
+
display = Text()
|
|
715
|
+
display.append(f" ▶ Running ({elapsed:.0f}s)\n", style="dim italic")
|
|
716
|
+
display.append(tail.rstrip(), style="dim")
|
|
717
|
+
live.update(display)
|
|
718
|
+
elif fd == proc.stderr:
|
|
719
|
+
line = proc.stderr.readline()
|
|
720
|
+
if line:
|
|
721
|
+
stderr_lines.append(line)
|
|
713
722
|
|
|
714
723
|
# Read remaining output after process exits
|
|
715
724
|
if proc.stdout:
|
|
@@ -8,7 +8,7 @@ def parse_requirements(filename):
|
|
|
8
8
|
|
|
9
9
|
setup(
|
|
10
10
|
name="aizen-ai-cli",
|
|
11
|
-
version="2.2.
|
|
11
|
+
version="2.2.4",
|
|
12
12
|
description="Aizen AI Agent — A professional-grade AI coding assistant for your terminal.",
|
|
13
13
|
packages=["aizen"],
|
|
14
14
|
install_requires=parse_requirements("requirements.txt"),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|