aizen-ai-cli 2.2.4__tar.gz → 2.4.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.
- {aizen_ai_cli-2.2.4/aizen_ai_cli.egg-info → aizen_ai_cli-2.4.0}/PKG-INFO +14 -5
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/README.md +12 -4
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/aizen/commands.py +133 -105
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/aizen/config.py +67 -22
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/aizen/context.py +12 -10
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/aizen/main.py +257 -120
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/aizen/session.py +3 -2
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/aizen/tools.py +493 -101
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/aizen/utils.py +20 -4
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0/aizen_ai_cli.egg-info}/PKG-INFO +14 -5
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/aizen_ai_cli.egg-info/requires.txt +1 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/pyproject.toml +2 -1
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/setup.py +4 -2
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/tests/test_commands.py +7 -6
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/tests/test_config.py +2 -5
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/tests/test_context.py +4 -2
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/tests/test_main.py +9 -8
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/tests/test_mcp.py +17 -15
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/tests/test_session.py +2 -5
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/tests/test_tools.py +64 -45
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/tests/test_utils.py +6 -11
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/MANIFEST.in +0 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/aizen/__init__.py +0 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/aizen/exceptions.py +0 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/aizen/logging_config.py +0 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/aizen/mcp.py +0 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/aizen/plugins.py +0 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/aizen/retry.py +0 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/aizen_ai_cli.egg-info/SOURCES.txt +0 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/aizen_ai_cli.egg-info/dependency_links.txt +0 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/aizen_ai_cli.egg-info/entry_points.txt +0 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/aizen_ai_cli.egg-info/top_level.txt +0 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/requirements.txt +0 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.4.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aizen-ai-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: Aizen AI Agent — A professional-grade AI coding assistant for your terminal.
|
|
5
5
|
Author: Irtaza Malik
|
|
6
6
|
License: MIT
|
|
@@ -26,6 +26,7 @@ Requires-Dist: openai>=1.0
|
|
|
26
26
|
Requires-Dist: python-dotenv>=1.0
|
|
27
27
|
Requires-Dist: rich>=13.0
|
|
28
28
|
Requires-Dist: prompt_toolkit>=3.0
|
|
29
|
+
Requires-Dist: questionary>=2.0.0
|
|
29
30
|
Requires-Dist: mcp>=1.0.0
|
|
30
31
|
Provides-Extra: tiktoken
|
|
31
32
|
Requires-Dist: tiktoken>=0.5; extra == "tiktoken"
|
|
@@ -41,7 +42,7 @@ Requires-Dist: mypy>=1.0; extra == "dev"
|
|
|
41
42
|
|
|
42
43
|
[](https://github.com/irtaza302/aizen-agent/actions/workflows/ci.yml)
|
|
43
44
|
|
|
44
|
-
A
|
|
45
|
+
A helpful AI coding assistant you can use right in your terminal. Aizen reads your code, edits files safely, runs commands, and helps you get things done faster — all with a friendly command‑line interface.
|
|
45
46
|
|
|
46
47
|
## ✨ Features
|
|
47
48
|
|
|
@@ -53,21 +54,27 @@ A professional-grade AI coding assistant that runs directly in your terminal. Ai
|
|
|
53
54
|
- **SQLite Session Persistence** — Session storage is powered by a SQLite database (`~/.aizen_sessions/aizen.db`), auto-migrating older JSON sessions.
|
|
54
55
|
- **Project-Specific Rules** — Customizes agent behavior per repository by auto-loading `.aizen_rules` or `.cursorrules` from the current working directory.
|
|
55
56
|
- **Smart Autocomplete** — `@`-mention files with Tab completion that respects `.gitignore` and supports directory traversal.
|
|
57
|
+
- **Vision Support** — Attach images natively (e.g., `@mockup.png`) and Aizen will automatically encode them for Vision APIs (GPT-4o, Claude 3.5 Sonnet).
|
|
58
|
+
- **Real-time Command Streaming** — Long-running shell commands stream their output live to the terminal instead of freezing with a spinner.
|
|
59
|
+
- **Smart Context Pruning** — Automatically drops old, large file attachments first when hitting the context limit before resorting to LLM summarization.
|
|
56
60
|
|
|
57
61
|
### Tools
|
|
58
|
-
Aizen has
|
|
62
|
+
Aizen has 10 built-in tools the AI can use:
|
|
59
63
|
|
|
60
64
|
| Tool | Description |
|
|
61
65
|
|------|-------------|
|
|
62
66
|
| `read_file` | Read file contents before making changes |
|
|
63
|
-
| `write_file` | Create new files (with preview) |
|
|
64
|
-
| `
|
|
67
|
+
| `write_file` | Create new files or overwrite entirely (with preview) |
|
|
68
|
+
| `replace_file_content` | Surgical search-and-replace on existing files (with line-bounds and diff preview) |
|
|
69
|
+
| `multi_replace_file_content` | Perform multiple, non-adjacent surgical edits sequentially in a single pass |
|
|
65
70
|
| `run_command` | Execute shell commands (supports background execution; safe commands auto-run, dangerous ones require approval) |
|
|
66
71
|
| `check_background_task` | Check the status and read recent output of a command running in the background |
|
|
67
72
|
| `kill_background_task` | Kill a running background task |
|
|
68
73
|
| `list_directory` | List files/folders with sizes, respecting `.gitignore` |
|
|
69
74
|
| `grep_search` | Search for text or regex patterns across the codebase |
|
|
70
75
|
| `find_files` | Find files by glob pattern (e.g., `*.py`, `Dockerfile`) |
|
|
76
|
+
| `get_file_outline` | Extract AST outline of a Python file (classes, methods, docstrings) without blowing up the context window |
|
|
77
|
+
| `web_search` | Search the web for current information, docs, or API references |
|
|
71
78
|
|
|
72
79
|
### Commands
|
|
73
80
|
|
|
@@ -91,9 +98,11 @@ Aizen has 9 built-in tools the AI can use:
|
|
|
91
98
|
| `/export [file]` | Export conversation to a Markdown file |
|
|
92
99
|
| `/config` | View current configuration |
|
|
93
100
|
| `/mcp` | View configured MCP servers and their connection status |
|
|
101
|
+
| `/auto [task]` | Enter a fully autonomous agentic loop to execute a complex task step-by-step |
|
|
94
102
|
|
|
95
103
|
### Safety & UX
|
|
96
104
|
- **Command Safety** — Read-only commands (`ls`, `cat`, `git status`, etc.) auto-execute. Destructive commands (`rm`, `sudo`, etc.) always require confirmation.
|
|
105
|
+
- **Autonomous Limits** — The `/auto` mode enforces a strict 25-step execution limit to prevent infinite loops and runaway costs.
|
|
97
106
|
- **`--yolo` Mode** — Auto-approve all operations for power users.
|
|
98
107
|
- **Background Tasks** — Run builds, tests, or other long-running tasks asynchronously while continuing to interact with Aizen.
|
|
99
108
|
- **File Backups** — Every file modification creates a backup. Use `/undo` to restore.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/irtaza302/aizen-agent/actions/workflows/ci.yml)
|
|
4
4
|
|
|
5
|
-
A
|
|
5
|
+
A helpful AI coding assistant you can use right in your terminal. Aizen reads your code, edits files safely, runs commands, and helps you get things done faster — all with a friendly command‑line interface.
|
|
6
6
|
|
|
7
7
|
## ✨ Features
|
|
8
8
|
|
|
@@ -14,21 +14,27 @@ A professional-grade AI coding assistant that runs directly in your terminal. Ai
|
|
|
14
14
|
- **SQLite Session Persistence** — Session storage is powered by a SQLite database (`~/.aizen_sessions/aizen.db`), auto-migrating older JSON sessions.
|
|
15
15
|
- **Project-Specific Rules** — Customizes agent behavior per repository by auto-loading `.aizen_rules` or `.cursorrules` from the current working directory.
|
|
16
16
|
- **Smart Autocomplete** — `@`-mention files with Tab completion that respects `.gitignore` and supports directory traversal.
|
|
17
|
+
- **Vision Support** — Attach images natively (e.g., `@mockup.png`) and Aizen will automatically encode them for Vision APIs (GPT-4o, Claude 3.5 Sonnet).
|
|
18
|
+
- **Real-time Command Streaming** — Long-running shell commands stream their output live to the terminal instead of freezing with a spinner.
|
|
19
|
+
- **Smart Context Pruning** — Automatically drops old, large file attachments first when hitting the context limit before resorting to LLM summarization.
|
|
17
20
|
|
|
18
21
|
### Tools
|
|
19
|
-
Aizen has
|
|
22
|
+
Aizen has 10 built-in tools the AI can use:
|
|
20
23
|
|
|
21
24
|
| Tool | Description |
|
|
22
25
|
|------|-------------|
|
|
23
26
|
| `read_file` | Read file contents before making changes |
|
|
24
|
-
| `write_file` | Create new files (with preview) |
|
|
25
|
-
| `
|
|
27
|
+
| `write_file` | Create new files or overwrite entirely (with preview) |
|
|
28
|
+
| `replace_file_content` | Surgical search-and-replace on existing files (with line-bounds and diff preview) |
|
|
29
|
+
| `multi_replace_file_content` | Perform multiple, non-adjacent surgical edits sequentially in a single pass |
|
|
26
30
|
| `run_command` | Execute shell commands (supports background execution; safe commands auto-run, dangerous ones require approval) |
|
|
27
31
|
| `check_background_task` | Check the status and read recent output of a command running in the background |
|
|
28
32
|
| `kill_background_task` | Kill a running background task |
|
|
29
33
|
| `list_directory` | List files/folders with sizes, respecting `.gitignore` |
|
|
30
34
|
| `grep_search` | Search for text or regex patterns across the codebase |
|
|
31
35
|
| `find_files` | Find files by glob pattern (e.g., `*.py`, `Dockerfile`) |
|
|
36
|
+
| `get_file_outline` | Extract AST outline of a Python file (classes, methods, docstrings) without blowing up the context window |
|
|
37
|
+
| `web_search` | Search the web for current information, docs, or API references |
|
|
32
38
|
|
|
33
39
|
### Commands
|
|
34
40
|
|
|
@@ -52,9 +58,11 @@ Aizen has 9 built-in tools the AI can use:
|
|
|
52
58
|
| `/export [file]` | Export conversation to a Markdown file |
|
|
53
59
|
| `/config` | View current configuration |
|
|
54
60
|
| `/mcp` | View configured MCP servers and their connection status |
|
|
61
|
+
| `/auto [task]` | Enter a fully autonomous agentic loop to execute a complex task step-by-step |
|
|
55
62
|
|
|
56
63
|
### Safety & UX
|
|
57
64
|
- **Command Safety** — Read-only commands (`ls`, `cat`, `git status`, etc.) auto-execute. Destructive commands (`rm`, `sudo`, etc.) always require confirmation.
|
|
65
|
+
- **Autonomous Limits** — The `/auto` mode enforces a strict 25-step execution limit to prevent infinite loops and runaway costs.
|
|
58
66
|
- **`--yolo` Mode** — Auto-approve all operations for power users.
|
|
59
67
|
- **Background Tasks** — Run builds, tests, or other long-running tasks asynchronously while continuing to interact with Aizen.
|
|
60
68
|
- **File Backups** — Every file modification creates a backup. Use `/undo` to restore.
|
|
@@ -13,6 +13,7 @@ from .config import (
|
|
|
13
13
|
BACKUPS_DIR,
|
|
14
14
|
CONFIG_PATH,
|
|
15
15
|
SESSIONS_DIR,
|
|
16
|
+
Theme,
|
|
16
17
|
console,
|
|
17
18
|
get_active_model,
|
|
18
19
|
get_cached_models,
|
|
@@ -43,6 +44,7 @@ SLASH_COMMANDS = [
|
|
|
43
44
|
("/mcp", "View configured MCP servers and their status"),
|
|
44
45
|
("/commit", "Auto-generate and commit changes"),
|
|
45
46
|
("/diff", "Show all uncommitted changes"),
|
|
47
|
+
("/auto", "Enter autonomous agentic mode for a complex task"),
|
|
46
48
|
]
|
|
47
49
|
|
|
48
50
|
# In-memory checkpoint storage for conversation branching
|
|
@@ -149,7 +151,7 @@ async def handle_slash_command(
|
|
|
149
151
|
if cmd == "/clear":
|
|
150
152
|
if len(messages) > 1:
|
|
151
153
|
messages[:] = [messages[0]]
|
|
152
|
-
console.print("[
|
|
154
|
+
console.print(f" [{Theme.SUCCESS}]✓ Conversation cleared.[/{Theme.SUCCESS}]\n")
|
|
153
155
|
|
|
154
156
|
elif cmd == "/drop":
|
|
155
157
|
dropped_count = 0
|
|
@@ -184,31 +186,36 @@ async def handle_slash_command(
|
|
|
184
186
|
msg["content"] = new_content
|
|
185
187
|
dropped_count += 1
|
|
186
188
|
if dropped_count > 0:
|
|
187
|
-
console.print(f"[
|
|
189
|
+
console.print(f" [{Theme.SUCCESS}]✓ Dropped attached contexts from {dropped_count} past messages.[/{Theme.SUCCESS}]\n")
|
|
188
190
|
else:
|
|
189
|
-
console.print("[
|
|
191
|
+
console.print(f" [{Theme.WARNING}]No attached contexts found to drop.[/{Theme.WARNING}]\n")
|
|
190
192
|
|
|
191
193
|
elif cmd == "/model":
|
|
192
194
|
if arg:
|
|
193
195
|
if arg.startswith("search ") or arg == "list" or arg == "search":
|
|
194
196
|
models = get_cached_models()
|
|
195
197
|
if not models:
|
|
196
|
-
console.print("[
|
|
198
|
+
console.print(f" [{Theme.WARNING}]Model list is still fetching or unavailable. Try again in a moment.[/{Theme.WARNING}]\n")
|
|
197
199
|
return False
|
|
198
200
|
|
|
199
201
|
search_query = arg[7:].lower().strip() if arg.startswith("search ") else ""
|
|
200
202
|
|
|
201
|
-
table = Table(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
203
|
+
table = Table(
|
|
204
|
+
title=f"🧠 OpenRouter Models{' (Search: ' + search_query + ')' if search_query else ''}",
|
|
205
|
+
border_style=Theme.BORDER,
|
|
206
|
+
header_style=f"bold {Theme.PRIMARY}",
|
|
207
|
+
)
|
|
208
|
+
table.add_column("Model ID", style=Theme.ACCENT)
|
|
209
|
+
table.add_column("Name", style=Theme.TEXT)
|
|
210
|
+
table.add_column("Context", style=Theme.MUTED)
|
|
211
|
+
table.add_column("Pricing", style=Theme.SUCCESS)
|
|
206
212
|
|
|
207
213
|
count = 0
|
|
208
214
|
for m in models:
|
|
209
215
|
if not search_query or search_query in m["id"].lower() or search_query in m["name"].lower():
|
|
210
|
-
|
|
211
|
-
|
|
216
|
+
pricing = m.get("pricing") or {}
|
|
217
|
+
price_prompt = pricing.get("prompt", "?")
|
|
218
|
+
price_comp = pricing.get("completion", "?")
|
|
212
219
|
pricing_str = f"P: {price_prompt} C: {price_comp}"
|
|
213
220
|
table.add_row(m["id"], m["name"], str(m.get("context_length")), pricing_str)
|
|
214
221
|
count += 1
|
|
@@ -217,7 +224,7 @@ async def handle_slash_command(
|
|
|
217
224
|
|
|
218
225
|
console.print(table)
|
|
219
226
|
if count >= 30:
|
|
220
|
-
console.print("[
|
|
227
|
+
console.print(f" [{Theme.MUTED}]... and more (showing top 30). Use `/model search <query>` to narrow down.[/{Theme.MUTED}]\n")
|
|
221
228
|
else:
|
|
222
229
|
console.print()
|
|
223
230
|
else:
|
|
@@ -225,47 +232,68 @@ async def handle_slash_command(
|
|
|
225
232
|
found = any(m["id"] == arg for m in models)
|
|
226
233
|
|
|
227
234
|
if models and not found:
|
|
228
|
-
console.print(f"[
|
|
235
|
+
console.print(f" [{Theme.WARNING}]⚠️ Warning: Model '{arg}' not found in OpenRouter API list.[/{Theme.WARNING}]")
|
|
229
236
|
|
|
230
237
|
set_active_model(arg, save=True)
|
|
231
|
-
console.print(f"[
|
|
238
|
+
console.print(f" [{Theme.SUCCESS}]✓ Model switched to:[/{Theme.SUCCESS}] [bold {Theme.ACCENT}]{arg}[/bold {Theme.ACCENT}]\n")
|
|
232
239
|
else:
|
|
233
|
-
console.print(f"[bold]Current model:[/bold] [
|
|
234
|
-
console.print("[
|
|
235
|
-
console.print("[
|
|
240
|
+
console.print(f" [bold {Theme.TEXT}]Current model:[/bold {Theme.TEXT}] [{Theme.ACCENT}]{current_model}[/{Theme.ACCENT}]")
|
|
241
|
+
console.print(f" [{Theme.MUTED}]Usage: /model <model_name>[/{Theme.MUTED}]")
|
|
242
|
+
console.print(f" [{Theme.MUTED}] /model search <query> (or `/model list`)[/{Theme.MUTED}]\n")
|
|
236
243
|
|
|
237
244
|
elif cmd == "/help":
|
|
238
245
|
help_table = Table(
|
|
239
246
|
title="⚡ Aizen Commands",
|
|
240
|
-
border_style=
|
|
247
|
+
border_style=Theme.BORDER,
|
|
241
248
|
show_header=True,
|
|
242
|
-
header_style="bold
|
|
249
|
+
header_style=f"bold {Theme.PRIMARY}",
|
|
250
|
+
title_style=f"bold {Theme.ACCENT}",
|
|
243
251
|
)
|
|
244
|
-
help_table.add_column("Command", style="
|
|
245
|
-
help_table.add_column("Description", style=
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
help_table.add_row("/
|
|
249
|
-
help_table.add_row("/
|
|
250
|
-
help_table.add_row("/
|
|
251
|
-
help_table.add_row("/
|
|
252
|
-
help_table.add_row("/
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
help_table.add_row("
|
|
256
|
-
help_table.add_row("/
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
help_table.add_row("/
|
|
260
|
-
help_table.add_row("/
|
|
261
|
-
help_table.add_row("/
|
|
262
|
-
help_table.add_row("/
|
|
263
|
-
help_table.add_row("/
|
|
264
|
-
help_table.add_row("", "")
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
help_table.add_row("", "")
|
|
268
|
-
help_table.add_row("
|
|
252
|
+
help_table.add_column("Command", style=f"{Theme.ACCENT} bold", min_width=24)
|
|
253
|
+
help_table.add_column("Description", style=Theme.TEXT)
|
|
254
|
+
|
|
255
|
+
# ── Navigation & Info ──
|
|
256
|
+
help_table.add_row(f"[bold {Theme.MUTED}]── Navigation ──[/bold {Theme.MUTED}]", "")
|
|
257
|
+
help_table.add_row(" 📖 /help", "Show this help message")
|
|
258
|
+
help_table.add_row(" ⚙️ /config", "View current configuration")
|
|
259
|
+
help_table.add_row(" 📊 /usage", "Show token usage statistics")
|
|
260
|
+
help_table.add_row(" 🔌 /mcp", "View MCP servers and status")
|
|
261
|
+
|
|
262
|
+
# ── Model ──
|
|
263
|
+
help_table.add_row(f"[bold {Theme.MUTED}]── Model ──[/bold {Theme.MUTED}]", "")
|
|
264
|
+
help_table.add_row(" 🧠 /model [name]", "View or switch the active model")
|
|
265
|
+
|
|
266
|
+
# ── Session ──
|
|
267
|
+
help_table.add_row(f"[bold {Theme.MUTED}]── Session ──[/bold {Theme.MUTED}]", "")
|
|
268
|
+
help_table.add_row(" 💾 /save [name]", "Save current conversation")
|
|
269
|
+
help_table.add_row(" 📂 /load [name]", "Load a saved conversation")
|
|
270
|
+
help_table.add_row(" 📌 /checkpoint [n]", "Save a conversation snapshot")
|
|
271
|
+
help_table.add_row(" 🔄 /restore [name]", "Restore a saved checkpoint")
|
|
272
|
+
help_table.add_row(" 📋 /export [file]", "Export conversation to Markdown")
|
|
273
|
+
|
|
274
|
+
# ── Editing ──
|
|
275
|
+
help_table.add_row(f"[bold {Theme.MUTED}]── Editing ──[/bold {Theme.MUTED}]", "")
|
|
276
|
+
help_table.add_row(" 🗑️ /clear", "Clear conversation history")
|
|
277
|
+
help_table.add_row(" 📎 /drop", "Drop attached files/URLs from history")
|
|
278
|
+
help_table.add_row(" 🧹 /compact", "Summarize conversation to save tokens")
|
|
279
|
+
help_table.add_row(" ↩️ /undo", "Undo the last file modification")
|
|
280
|
+
help_table.add_row(" 🔁 /retry", "Retry the last user message")
|
|
281
|
+
help_table.add_row(" 📝 /copy", "Copy last AI response to clipboard")
|
|
282
|
+
|
|
283
|
+
# ── Git ──
|
|
284
|
+
help_table.add_row(f"[bold {Theme.MUTED}]── Git ──[/bold {Theme.MUTED}]", "")
|
|
285
|
+
help_table.add_row(" 🔀 /commit", "Auto-generate and commit changes")
|
|
286
|
+
help_table.add_row(" 📊 /diff", "Show all uncommitted changes")
|
|
287
|
+
|
|
288
|
+
# ── Agent ──
|
|
289
|
+
help_table.add_row(f"[bold {Theme.MUTED}]── Agent ──[/bold {Theme.MUTED}]", "")
|
|
290
|
+
help_table.add_row(" 🤖 /auto [task]", "Enter autonomous mode for a complex task (max iterations apply)")
|
|
291
|
+
|
|
292
|
+
# ── Shortcuts ──
|
|
293
|
+
help_table.add_row(f"[bold {Theme.MUTED}]── Shortcuts ──[/bold {Theme.MUTED}]", "")
|
|
294
|
+
help_table.add_row(f" [{Theme.PINK}]@file / @url[/{Theme.PINK}]", "Attach file context or web URL")
|
|
295
|
+
help_table.add_row(f" [{Theme.PINK}]exit / quit[/{Theme.PINK}]", "Exit Aizen")
|
|
296
|
+
help_table.add_row(f" [{Theme.MUTED}]Tip[/{Theme.MUTED}]", f"[{Theme.MUTED}]End a line with \\\\ for multi-line input[/{Theme.MUTED}]")
|
|
269
297
|
console.print(help_table)
|
|
270
298
|
console.print()
|
|
271
299
|
|
|
@@ -276,51 +304,51 @@ async def handle_slash_command(
|
|
|
276
304
|
elif cmd == "/save":
|
|
277
305
|
try:
|
|
278
306
|
path = save_session(messages, arg if arg else None, token_tracker)
|
|
279
|
-
console.print(f"[
|
|
307
|
+
console.print(f" [{Theme.SUCCESS}]✓ Session saved to {path}[/{Theme.SUCCESS}]\n")
|
|
280
308
|
except Exception as e:
|
|
281
|
-
console.print(f"[
|
|
309
|
+
console.print(f" [{Theme.ERROR}]Error saving session: {e}[/{Theme.ERROR}]\n")
|
|
282
310
|
|
|
283
311
|
elif cmd == "/load":
|
|
284
312
|
if not arg:
|
|
285
313
|
sessions = list_sessions()
|
|
286
314
|
if not sessions:
|
|
287
|
-
console.print("[
|
|
315
|
+
console.print(f" [{Theme.WARNING}]No saved sessions found.[/{Theme.WARNING}]\n")
|
|
288
316
|
else:
|
|
289
317
|
table = Table(
|
|
290
318
|
title="📂 Saved Sessions",
|
|
291
|
-
border_style=
|
|
292
|
-
header_style="bold
|
|
319
|
+
border_style=Theme.BORDER,
|
|
320
|
+
header_style=f"bold {Theme.PRIMARY}",
|
|
293
321
|
)
|
|
294
|
-
table.add_column("Name", style=
|
|
295
|
-
table.add_column("Saved At", style=
|
|
322
|
+
table.add_column("Name", style=Theme.ACCENT)
|
|
323
|
+
table.add_column("Saved At", style=Theme.MUTED)
|
|
296
324
|
table.add_column("Messages", style="white", justify="right")
|
|
297
325
|
for s in sessions[:10]:
|
|
298
326
|
table.add_row(s["name"], s["saved_at"][:19], str(s["messages"]))
|
|
299
327
|
console.print(table)
|
|
300
|
-
console.print("[
|
|
328
|
+
console.print(f" [{Theme.MUTED}]Usage: /load <session_name>[/{Theme.MUTED}]\n")
|
|
301
329
|
else:
|
|
302
330
|
loaded = load_session(arg)
|
|
303
331
|
if loaded:
|
|
304
332
|
messages[:] = loaded
|
|
305
333
|
console.print(
|
|
306
|
-
f"[
|
|
334
|
+
f" [{Theme.SUCCESS}]✓ Loaded session '{arg}' ({len(loaded)} messages)[/{Theme.SUCCESS}]\n"
|
|
307
335
|
)
|
|
308
336
|
else:
|
|
309
|
-
console.print(f"[
|
|
337
|
+
console.print(f" [{Theme.ERROR}]Session '{arg}' not found.[/{Theme.ERROR}]\n")
|
|
310
338
|
|
|
311
339
|
elif cmd == "/undo":
|
|
312
340
|
result = backup_manager.undo()
|
|
313
|
-
console.print(f"[
|
|
341
|
+
console.print(f" [{Theme.SUCCESS}]{result}[/{Theme.SUCCESS}]\n")
|
|
314
342
|
|
|
315
343
|
elif cmd == "/retry":
|
|
316
344
|
# Remove last assistant + tool messages, then re-process the last user message
|
|
317
345
|
while messages and messages[-1]["role"] in ("assistant", "tool"):
|
|
318
346
|
messages.pop()
|
|
319
347
|
if messages and messages[-1]["role"] == "user":
|
|
320
|
-
console.print("[
|
|
348
|
+
console.print(f" [{Theme.SUCCESS}]✓ Retrying last message...[/{Theme.SUCCESS}]\n")
|
|
321
349
|
return True # Signal to re-process
|
|
322
350
|
else:
|
|
323
|
-
console.print("[
|
|
351
|
+
console.print(f" [{Theme.WARNING}]Nothing to retry.[/{Theme.WARNING}]\n")
|
|
324
352
|
|
|
325
353
|
elif cmd == "/copy":
|
|
326
354
|
last_response = None
|
|
@@ -349,13 +377,13 @@ async def handle_slash_command(
|
|
|
349
377
|
subprocess.run(
|
|
350
378
|
["clip"], input=last_response, text=True, check=True
|
|
351
379
|
)
|
|
352
|
-
console.print("[
|
|
380
|
+
console.print(f" [{Theme.SUCCESS}]✓ Copied to clipboard.[/{Theme.SUCCESS}]\n")
|
|
353
381
|
except Exception:
|
|
354
382
|
console.print(
|
|
355
|
-
"[
|
|
383
|
+
f" [{Theme.WARNING}]⚠️ Could not copy to clipboard.[/{Theme.WARNING}]\n"
|
|
356
384
|
)
|
|
357
385
|
else:
|
|
358
|
-
console.print("[
|
|
386
|
+
console.print(f" [{Theme.WARNING}]No response to copy.[/{Theme.WARNING}]\n")
|
|
359
387
|
|
|
360
388
|
elif cmd == "/export":
|
|
361
389
|
filename = (
|
|
@@ -377,13 +405,13 @@ async def handle_slash_command(
|
|
|
377
405
|
f.write(f"## 👤 You\n\n{msg['content']}\n\n")
|
|
378
406
|
elif msg["role"] == "assistant" and msg.get("content"):
|
|
379
407
|
f.write(f"## ✦ Aizen\n\n{msg['content']}\n\n")
|
|
380
|
-
console.print(f"[
|
|
408
|
+
console.print(f" [{Theme.SUCCESS}]✓ Exported to {filename}[/{Theme.SUCCESS}]\n")
|
|
381
409
|
except Exception as e:
|
|
382
|
-
console.print(f"[
|
|
410
|
+
console.print(f" [{Theme.ERROR}]Error exporting: {e}[/{Theme.ERROR}]\n")
|
|
383
411
|
|
|
384
412
|
elif cmd == "/compact":
|
|
385
413
|
if len(messages) <= 4:
|
|
386
|
-
console.print("[
|
|
414
|
+
console.print(f" [{Theme.WARNING}]Conversation is already compact.[/{Theme.WARNING}]\n")
|
|
387
415
|
else:
|
|
388
416
|
system_msg = messages[0]
|
|
389
417
|
recent = messages[-4:]
|
|
@@ -391,7 +419,7 @@ async def handle_slash_command(
|
|
|
391
419
|
|
|
392
420
|
if middle:
|
|
393
421
|
# Attempt LLM-based summarization for much better context retention
|
|
394
|
-
console.print("[
|
|
422
|
+
console.print(f" [{Theme.MUTED}]Summarizing conversation with AI...[/{Theme.MUTED}]")
|
|
395
423
|
try:
|
|
396
424
|
from openai import AsyncOpenAI as _AsyncOpenAI
|
|
397
425
|
|
|
@@ -449,20 +477,20 @@ async def handle_slash_command(
|
|
|
449
477
|
},
|
|
450
478
|
] + recent
|
|
451
479
|
console.print(
|
|
452
|
-
f"[
|
|
480
|
+
f" [{Theme.SUCCESS}]✓ Compacted {len(middle)} messages into an AI-generated summary.[/{Theme.SUCCESS}]\n"
|
|
453
481
|
)
|
|
454
482
|
else:
|
|
455
|
-
console.print("[
|
|
483
|
+
console.print(f" [{Theme.WARNING}]Not enough messages to compact.[/{Theme.WARNING}]\n")
|
|
456
484
|
|
|
457
485
|
elif cmd == "/config":
|
|
458
486
|
config = load_config()
|
|
459
487
|
table = Table(
|
|
460
488
|
title="⚙️ Configuration",
|
|
461
|
-
border_style=
|
|
462
|
-
header_style="bold
|
|
489
|
+
border_style=Theme.BORDER,
|
|
490
|
+
header_style=f"bold {Theme.PRIMARY}",
|
|
463
491
|
)
|
|
464
|
-
table.add_column("Key", style=
|
|
465
|
-
table.add_column("Value", style=
|
|
492
|
+
table.add_column("Key", style=Theme.ACCENT)
|
|
493
|
+
table.add_column("Value", style=Theme.TEXT)
|
|
466
494
|
table.add_row("Model", current_model)
|
|
467
495
|
table.add_row(
|
|
468
496
|
"API Base URL",
|
|
@@ -478,22 +506,22 @@ async def handle_slash_command(
|
|
|
478
506
|
|
|
479
507
|
elif cmd == "/mcp":
|
|
480
508
|
if not mcp_manager:
|
|
481
|
-
console.print("[
|
|
509
|
+
console.print(f" [{Theme.WARNING}]MCP Manager is not available.[/{Theme.WARNING}]\n")
|
|
482
510
|
return False
|
|
483
511
|
|
|
484
512
|
if not mcp_manager.config:
|
|
485
|
-
console.print("[
|
|
486
|
-
console.print("[
|
|
513
|
+
console.print(f" [{Theme.WARNING}]No MCP servers configured in ~/.aizen_config.json[/{Theme.WARNING}]\n")
|
|
514
|
+
console.print(f" [{Theme.MUTED}]Add an 'mcp_servers' block to your config to enable MCP plugins.[/{Theme.MUTED}]\n")
|
|
487
515
|
return False
|
|
488
516
|
|
|
489
517
|
table = Table(
|
|
490
518
|
title="🔌 Configured MCP Servers",
|
|
491
|
-
border_style=
|
|
492
|
-
header_style="bold
|
|
519
|
+
border_style=Theme.BORDER,
|
|
520
|
+
header_style=f"bold {Theme.PRIMARY}",
|
|
493
521
|
)
|
|
494
|
-
table.add_column("Server Name", style="
|
|
495
|
-
table.add_column("Status", style=
|
|
496
|
-
table.add_column("Tools Available", style=
|
|
522
|
+
table.add_column("Server Name", style=f"{Theme.ACCENT} bold")
|
|
523
|
+
table.add_column("Status", style=Theme.TEXT)
|
|
524
|
+
table.add_column("Tools Available", style=Theme.MUTED)
|
|
497
525
|
|
|
498
526
|
tools = mcp_manager.get_tools()
|
|
499
527
|
server_tools: dict[str, list[str]] = {srv: [] for srv in mcp_manager.config.keys()}
|
|
@@ -508,9 +536,9 @@ async def handle_slash_command(
|
|
|
508
536
|
|
|
509
537
|
for server_name in mcp_manager.config.keys():
|
|
510
538
|
if server_name in mcp_manager.sessions:
|
|
511
|
-
status = "[
|
|
539
|
+
status = f"[{Theme.SUCCESS}]● Connected[/{Theme.SUCCESS}]"
|
|
512
540
|
else:
|
|
513
|
-
status = "[
|
|
541
|
+
status = f"[{Theme.ERROR}]● Disconnected[/{Theme.ERROR}]"
|
|
514
542
|
|
|
515
543
|
tool_count = len(server_tools[server_name])
|
|
516
544
|
if tool_count > 0:
|
|
@@ -531,37 +559,37 @@ async def handle_slash_command(
|
|
|
531
559
|
name = arg or f"cp_{datetime.now().strftime('%H%M%S')}"
|
|
532
560
|
_checkpoints[name] = copy.deepcopy(messages)
|
|
533
561
|
console.print(
|
|
534
|
-
f"[
|
|
562
|
+
f" [{Theme.SUCCESS}]✓ Checkpoint '{name}' saved ({len(messages)} messages)[/{Theme.SUCCESS}]\n"
|
|
535
563
|
)
|
|
536
564
|
|
|
537
565
|
elif cmd == "/restore":
|
|
538
566
|
if not arg:
|
|
539
567
|
if not _checkpoints:
|
|
540
|
-
console.print("[
|
|
568
|
+
console.print(f" [{Theme.WARNING}]No checkpoints saved. Use /checkpoint [name] first.[/{Theme.WARNING}]\n")
|
|
541
569
|
else:
|
|
542
570
|
table = Table(
|
|
543
571
|
title="📌 Checkpoints",
|
|
544
|
-
border_style=
|
|
545
|
-
header_style="bold
|
|
572
|
+
border_style=Theme.BORDER,
|
|
573
|
+
header_style=f"bold {Theme.PRIMARY}",
|
|
546
574
|
)
|
|
547
|
-
table.add_column("Name", style=
|
|
575
|
+
table.add_column("Name", style=Theme.ACCENT)
|
|
548
576
|
table.add_column("Messages", style="white", justify="right")
|
|
549
577
|
for cp_name, cp_msgs in _checkpoints.items():
|
|
550
578
|
table.add_row(cp_name, str(len(cp_msgs)))
|
|
551
579
|
console.print(table)
|
|
552
|
-
console.print("[
|
|
580
|
+
console.print(f" [{Theme.MUTED}]Usage: /restore <name>[/{Theme.MUTED}]\n")
|
|
553
581
|
else:
|
|
554
582
|
if arg in _checkpoints:
|
|
555
583
|
messages[:] = copy.deepcopy(_checkpoints[arg])
|
|
556
584
|
console.print(
|
|
557
|
-
f"[
|
|
585
|
+
f" [{Theme.SUCCESS}]✓ Restored checkpoint '{arg}' ({len(messages)} messages)[/{Theme.SUCCESS}]\n"
|
|
558
586
|
)
|
|
559
587
|
else:
|
|
560
|
-
console.print(f"[
|
|
588
|
+
console.print(f" [{Theme.ERROR}]Checkpoint '{arg}' not found.[/{Theme.ERROR}]\n")
|
|
561
589
|
|
|
562
590
|
elif cmd == "/commit":
|
|
563
591
|
if not client:
|
|
564
|
-
console.print("[
|
|
592
|
+
console.print(f" [{Theme.ERROR}]API client is not available for /commit.[/{Theme.ERROR}]\n")
|
|
565
593
|
return False
|
|
566
594
|
|
|
567
595
|
try:
|
|
@@ -575,12 +603,12 @@ async def handle_slash_command(
|
|
|
575
603
|
unstaged_diff = result_unstaged.stdout.strip()
|
|
576
604
|
|
|
577
605
|
if not unstaged_diff:
|
|
578
|
-
console.print("[
|
|
606
|
+
console.print(f" [{Theme.WARNING}]No changes found to commit.[/{Theme.WARNING}]\n")
|
|
579
607
|
return False
|
|
580
608
|
|
|
581
609
|
answer = prompt("No staged changes. Stage all current changes? [Y/n] ")
|
|
582
610
|
if answer.lower() not in ("y", "yes", ""):
|
|
583
|
-
console.print("[
|
|
611
|
+
console.print(f" [{Theme.WARNING}]Commit aborted.[/{Theme.WARNING}]\n")
|
|
584
612
|
return False
|
|
585
613
|
|
|
586
614
|
subprocess.run(["git", "add", "-u"], check=True)
|
|
@@ -588,10 +616,10 @@ async def handle_slash_command(
|
|
|
588
616
|
diff = result.stdout.strip()
|
|
589
617
|
|
|
590
618
|
if not diff:
|
|
591
|
-
console.print("[
|
|
619
|
+
console.print(f" [{Theme.WARNING}]No changes staged to commit.[/{Theme.WARNING}]\n")
|
|
592
620
|
return False
|
|
593
621
|
|
|
594
|
-
console.print("[
|
|
622
|
+
console.print(f" [{Theme.MUTED}]Generating commit message...[/{Theme.MUTED}]")
|
|
595
623
|
|
|
596
624
|
commit_messages = [
|
|
597
625
|
{"role": "system", "content": "You are a senior developer. Write a concise, conventional commit message for the following diff. Output ONLY the commit message, no explanation, no markdown blocks."},
|
|
@@ -607,8 +635,8 @@ async def handle_slash_command(
|
|
|
607
635
|
# Remove any markdown codeblocks if model didn't listen
|
|
608
636
|
commit_msg = commit_msg.replace("```text", "").replace("```", "").strip()
|
|
609
637
|
|
|
610
|
-
console.print("\n[bold]Generated Commit Message:[/bold]")
|
|
611
|
-
console.print(f"[
|
|
638
|
+
console.print(f"\n [bold {Theme.TEXT}]Generated Commit Message:[/bold {Theme.TEXT}]")
|
|
639
|
+
console.print(f" [{Theme.ACCENT}]{commit_msg}[/{Theme.ACCENT}]\n")
|
|
612
640
|
|
|
613
641
|
action = prompt("Commit with this message? [Y/n/e(dit)] ")
|
|
614
642
|
action = action.lower().strip()
|
|
@@ -622,12 +650,12 @@ async def handle_slash_command(
|
|
|
622
650
|
return False
|
|
623
651
|
|
|
624
652
|
subprocess.run(["git", "commit", "-m", final_msg], check=True)
|
|
625
|
-
console.print("[
|
|
653
|
+
console.print(f" [{Theme.SUCCESS}]✓ Committed successfully.[/{Theme.SUCCESS}]\n")
|
|
626
654
|
|
|
627
655
|
except subprocess.CalledProcessError:
|
|
628
|
-
console.print("[
|
|
656
|
+
console.print(f" [{Theme.ERROR}]Error: Not a git repository or git command failed.[/{Theme.ERROR}]\n")
|
|
629
657
|
except Exception as e:
|
|
630
|
-
console.print(f"[
|
|
658
|
+
console.print(f" [{Theme.ERROR}]Error during auto-commit: {e}[/{Theme.ERROR}]\n")
|
|
631
659
|
|
|
632
660
|
elif cmd == "/diff":
|
|
633
661
|
try:
|
|
@@ -648,18 +676,18 @@ async def handle_slash_command(
|
|
|
648
676
|
has_output = False
|
|
649
677
|
|
|
650
678
|
if result_staged.stdout.strip():
|
|
651
|
-
console.print("[bold
|
|
679
|
+
console.print(f" [bold {Theme.SUCCESS}]Staged changes:[/bold {Theme.SUCCESS}]")
|
|
652
680
|
console.print(f"[dim]{result_staged.stdout.strip()}[/dim]")
|
|
653
681
|
has_output = True
|
|
654
682
|
|
|
655
683
|
if result_unstaged.stdout.strip():
|
|
656
|
-
console.print("[bold
|
|
684
|
+
console.print(f" [bold {Theme.WARNING}]Unstaged changes:[/bold {Theme.WARNING}]")
|
|
657
685
|
console.print(f"[dim]{result_unstaged.stdout.strip()}[/dim]")
|
|
658
686
|
has_output = True
|
|
659
687
|
|
|
660
688
|
if result_untracked.stdout.strip():
|
|
661
689
|
untracked = result_untracked.stdout.strip().split("\n")
|
|
662
|
-
console.print(f"[bold
|
|
690
|
+
console.print(f" [bold {Theme.ACCENT}]Untracked files ({len(untracked)}):[/bold {Theme.ACCENT}]")
|
|
663
691
|
for f in untracked[:20]:
|
|
664
692
|
console.print(f" [dim]+ {f}[/dim]")
|
|
665
693
|
if len(untracked) > 20:
|
|
@@ -667,7 +695,7 @@ async def handle_slash_command(
|
|
|
667
695
|
has_output = True
|
|
668
696
|
|
|
669
697
|
if not has_output:
|
|
670
|
-
console.print("[
|
|
698
|
+
console.print(f" [{Theme.SUCCESS}]✓ Working tree is clean.[/{Theme.SUCCESS}]")
|
|
671
699
|
|
|
672
700
|
# Show full diff if requested
|
|
673
701
|
if arg == "--full" or arg == "-f":
|
|
@@ -682,13 +710,13 @@ async def handle_slash_command(
|
|
|
682
710
|
|
|
683
711
|
console.print()
|
|
684
712
|
except subprocess.CalledProcessError:
|
|
685
|
-
console.print("[
|
|
713
|
+
console.print(f" [{Theme.ERROR}]Error: Not a git repository or git command failed.[/{Theme.ERROR}]\n")
|
|
686
714
|
except Exception as e:
|
|
687
|
-
console.print(f"[
|
|
715
|
+
console.print(f" [{Theme.ERROR}]Error showing diff: {e}[/{Theme.ERROR}]\n")
|
|
688
716
|
|
|
689
717
|
else:
|
|
690
718
|
console.print(
|
|
691
|
-
f"[
|
|
719
|
+
f" [{Theme.ERROR}]Unknown command: {cmd}[/{Theme.ERROR}] — type [bold {Theme.ACCENT}]/help[/bold {Theme.ACCENT}] for commands.\n"
|
|
692
720
|
)
|
|
693
721
|
|
|
694
722
|
return False
|