aizen-ai-cli 2.2.4__tar.gz → 2.2.5__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.2.5}/PKG-INFO +6 -4
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/README.md +4 -3
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/aizen/commands.py +128 -105
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/aizen/config.py +43 -16
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/aizen/context.py +12 -10
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/aizen/main.py +82 -64
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/aizen/tools.py +293 -93
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/aizen/utils.py +9 -4
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5/aizen_ai_cli.egg-info}/PKG-INFO +6 -4
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/aizen_ai_cli.egg-info/requires.txt +1 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/pyproject.toml +2 -1
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/setup.py +3 -1
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/tests/test_commands.py +7 -6
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/tests/test_config.py +2 -5
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/tests/test_context.py +4 -2
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/tests/test_main.py +9 -8
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/tests/test_mcp.py +17 -15
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/tests/test_session.py +2 -5
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/tests/test_tools.py +61 -42
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/tests/test_utils.py +6 -11
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/MANIFEST.in +0 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/aizen/__init__.py +0 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/aizen/exceptions.py +0 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/aizen/logging_config.py +0 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/aizen/mcp.py +0 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/aizen/plugins.py +0 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/aizen/retry.py +0 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/aizen/session.py +0 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/aizen_ai_cli.egg-info/SOURCES.txt +0 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/aizen_ai_cli.egg-info/dependency_links.txt +0 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/aizen_ai_cli.egg-info/entry_points.txt +0 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/aizen_ai_cli.egg-info/top_level.txt +0 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/requirements.txt +0 -0
- {aizen_ai_cli-2.2.4 → aizen_ai_cli-2.2.5}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aizen-ai-cli
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.5
|
|
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
|
|
|
@@ -60,8 +61,9 @@ Aizen has 9 built-in tools the AI can use:
|
|
|
60
61
|
| Tool | Description |
|
|
61
62
|
|------|-------------|
|
|
62
63
|
| `read_file` | Read file contents before making changes |
|
|
63
|
-
| `write_file` | Create new files (with preview) |
|
|
64
|
-
| `
|
|
64
|
+
| `write_file` | Create new files or overwrite entirely (with preview) |
|
|
65
|
+
| `replace_file_content` | Surgical search-and-replace on existing files (with line-bounds and diff preview) |
|
|
66
|
+
| `multi_replace_file_content` | Perform multiple, non-adjacent surgical edits sequentially in a single pass |
|
|
65
67
|
| `run_command` | Execute shell commands (supports background execution; safe commands auto-run, dangerous ones require approval) |
|
|
66
68
|
| `check_background_task` | Check the status and read recent output of a command running in the background |
|
|
67
69
|
| `kill_background_task` | Kill a running background task |
|
|
@@ -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
|
|
|
@@ -21,8 +21,9 @@ Aizen has 9 built-in tools the AI can use:
|
|
|
21
21
|
| Tool | Description |
|
|
22
22
|
|------|-------------|
|
|
23
23
|
| `read_file` | Read file contents before making changes |
|
|
24
|
-
| `write_file` | Create new files (with preview) |
|
|
25
|
-
| `
|
|
24
|
+
| `write_file` | Create new files or overwrite entirely (with preview) |
|
|
25
|
+
| `replace_file_content` | Surgical search-and-replace on existing files (with line-bounds and diff preview) |
|
|
26
|
+
| `multi_replace_file_content` | Perform multiple, non-adjacent surgical edits sequentially in a single pass |
|
|
26
27
|
| `run_command` | Execute shell commands (supports background execution; safe commands auto-run, dangerous ones require approval) |
|
|
27
28
|
| `check_background_task` | Check the status and read recent output of a command running in the background |
|
|
28
29
|
| `kill_background_task` | Kill a running background task |
|
|
@@ -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,
|
|
@@ -149,7 +150,7 @@ async def handle_slash_command(
|
|
|
149
150
|
if cmd == "/clear":
|
|
150
151
|
if len(messages) > 1:
|
|
151
152
|
messages[:] = [messages[0]]
|
|
152
|
-
console.print("[
|
|
153
|
+
console.print(f" [{Theme.SUCCESS}]✓ Conversation cleared.[/{Theme.SUCCESS}]\n")
|
|
153
154
|
|
|
154
155
|
elif cmd == "/drop":
|
|
155
156
|
dropped_count = 0
|
|
@@ -184,31 +185,36 @@ async def handle_slash_command(
|
|
|
184
185
|
msg["content"] = new_content
|
|
185
186
|
dropped_count += 1
|
|
186
187
|
if dropped_count > 0:
|
|
187
|
-
console.print(f"[
|
|
188
|
+
console.print(f" [{Theme.SUCCESS}]✓ Dropped attached contexts from {dropped_count} past messages.[/{Theme.SUCCESS}]\n")
|
|
188
189
|
else:
|
|
189
|
-
console.print("[
|
|
190
|
+
console.print(f" [{Theme.WARNING}]No attached contexts found to drop.[/{Theme.WARNING}]\n")
|
|
190
191
|
|
|
191
192
|
elif cmd == "/model":
|
|
192
193
|
if arg:
|
|
193
194
|
if arg.startswith("search ") or arg == "list" or arg == "search":
|
|
194
195
|
models = get_cached_models()
|
|
195
196
|
if not models:
|
|
196
|
-
console.print("[
|
|
197
|
+
console.print(f" [{Theme.WARNING}]Model list is still fetching or unavailable. Try again in a moment.[/{Theme.WARNING}]\n")
|
|
197
198
|
return False
|
|
198
199
|
|
|
199
200
|
search_query = arg[7:].lower().strip() if arg.startswith("search ") else ""
|
|
200
201
|
|
|
201
|
-
table = Table(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
202
|
+
table = Table(
|
|
203
|
+
title=f"🧠 OpenRouter Models{' (Search: ' + search_query + ')' if search_query else ''}",
|
|
204
|
+
border_style=Theme.BORDER,
|
|
205
|
+
header_style=f"bold {Theme.PRIMARY}",
|
|
206
|
+
)
|
|
207
|
+
table.add_column("Model ID", style=Theme.ACCENT)
|
|
208
|
+
table.add_column("Name", style=Theme.TEXT)
|
|
209
|
+
table.add_column("Context", style=Theme.MUTED)
|
|
210
|
+
table.add_column("Pricing", style=Theme.SUCCESS)
|
|
206
211
|
|
|
207
212
|
count = 0
|
|
208
213
|
for m in models:
|
|
209
214
|
if not search_query or search_query in m["id"].lower() or search_query in m["name"].lower():
|
|
210
|
-
|
|
211
|
-
|
|
215
|
+
pricing = m.get("pricing") or {}
|
|
216
|
+
price_prompt = pricing.get("prompt", "?")
|
|
217
|
+
price_comp = pricing.get("completion", "?")
|
|
212
218
|
pricing_str = f"P: {price_prompt} C: {price_comp}"
|
|
213
219
|
table.add_row(m["id"], m["name"], str(m.get("context_length")), pricing_str)
|
|
214
220
|
count += 1
|
|
@@ -217,7 +223,7 @@ async def handle_slash_command(
|
|
|
217
223
|
|
|
218
224
|
console.print(table)
|
|
219
225
|
if count >= 30:
|
|
220
|
-
console.print("[
|
|
226
|
+
console.print(f" [{Theme.MUTED}]... and more (showing top 30). Use `/model search <query>` to narrow down.[/{Theme.MUTED}]\n")
|
|
221
227
|
else:
|
|
222
228
|
console.print()
|
|
223
229
|
else:
|
|
@@ -225,47 +231,64 @@ async def handle_slash_command(
|
|
|
225
231
|
found = any(m["id"] == arg for m in models)
|
|
226
232
|
|
|
227
233
|
if models and not found:
|
|
228
|
-
console.print(f"[
|
|
234
|
+
console.print(f" [{Theme.WARNING}]⚠️ Warning: Model '{arg}' not found in OpenRouter API list.[/{Theme.WARNING}]")
|
|
229
235
|
|
|
230
236
|
set_active_model(arg, save=True)
|
|
231
|
-
console.print(f"[
|
|
237
|
+
console.print(f" [{Theme.SUCCESS}]✓ Model switched to:[/{Theme.SUCCESS}] [bold {Theme.ACCENT}]{arg}[/bold {Theme.ACCENT}]\n")
|
|
232
238
|
else:
|
|
233
|
-
console.print(f"[bold]Current model:[/bold] [
|
|
234
|
-
console.print("[
|
|
235
|
-
console.print("[
|
|
239
|
+
console.print(f" [bold {Theme.TEXT}]Current model:[/bold {Theme.TEXT}] [{Theme.ACCENT}]{current_model}[/{Theme.ACCENT}]")
|
|
240
|
+
console.print(f" [{Theme.MUTED}]Usage: /model <model_name>[/{Theme.MUTED}]")
|
|
241
|
+
console.print(f" [{Theme.MUTED}] /model search <query> (or `/model list`)[/{Theme.MUTED}]\n")
|
|
236
242
|
|
|
237
243
|
elif cmd == "/help":
|
|
238
244
|
help_table = Table(
|
|
239
245
|
title="⚡ Aizen Commands",
|
|
240
|
-
border_style=
|
|
246
|
+
border_style=Theme.BORDER,
|
|
241
247
|
show_header=True,
|
|
242
|
-
header_style="bold
|
|
248
|
+
header_style=f"bold {Theme.PRIMARY}",
|
|
249
|
+
title_style=f"bold {Theme.ACCENT}",
|
|
243
250
|
)
|
|
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("
|
|
251
|
+
help_table.add_column("Command", style=f"{Theme.ACCENT} bold", min_width=24)
|
|
252
|
+
help_table.add_column("Description", style=Theme.TEXT)
|
|
253
|
+
|
|
254
|
+
# ── Navigation & Info ──
|
|
255
|
+
help_table.add_row(f"[bold {Theme.MUTED}]── Navigation ──[/bold {Theme.MUTED}]", "")
|
|
256
|
+
help_table.add_row(" 📖 /help", "Show this help message")
|
|
257
|
+
help_table.add_row(" ⚙️ /config", "View current configuration")
|
|
258
|
+
help_table.add_row(" 📊 /usage", "Show token usage statistics")
|
|
259
|
+
help_table.add_row(" 🔌 /mcp", "View MCP servers and status")
|
|
260
|
+
|
|
261
|
+
# ── Model ──
|
|
262
|
+
help_table.add_row(f"[bold {Theme.MUTED}]── Model ──[/bold {Theme.MUTED}]", "")
|
|
263
|
+
help_table.add_row(" 🧠 /model [name]", "View or switch the active model")
|
|
264
|
+
|
|
265
|
+
# ── Session ──
|
|
266
|
+
help_table.add_row(f"[bold {Theme.MUTED}]── Session ──[/bold {Theme.MUTED}]", "")
|
|
267
|
+
help_table.add_row(" 💾 /save [name]", "Save current conversation")
|
|
268
|
+
help_table.add_row(" 📂 /load [name]", "Load a saved conversation")
|
|
269
|
+
help_table.add_row(" 📌 /checkpoint [n]", "Save a conversation snapshot")
|
|
270
|
+
help_table.add_row(" 🔄 /restore [name]", "Restore a saved checkpoint")
|
|
271
|
+
help_table.add_row(" 📋 /export [file]", "Export conversation to Markdown")
|
|
272
|
+
|
|
273
|
+
# ── Editing ──
|
|
274
|
+
help_table.add_row(f"[bold {Theme.MUTED}]── Editing ──[/bold {Theme.MUTED}]", "")
|
|
275
|
+
help_table.add_row(" 🗑️ /clear", "Clear conversation history")
|
|
276
|
+
help_table.add_row(" 📎 /drop", "Drop attached files/URLs from history")
|
|
277
|
+
help_table.add_row(" 🧹 /compact", "Summarize conversation to save tokens")
|
|
278
|
+
help_table.add_row(" ↩️ /undo", "Undo the last file modification")
|
|
279
|
+
help_table.add_row(" 🔁 /retry", "Retry the last user message")
|
|
280
|
+
help_table.add_row(" 📝 /copy", "Copy last AI response to clipboard")
|
|
281
|
+
|
|
282
|
+
# ── Git ──
|
|
283
|
+
help_table.add_row(f"[bold {Theme.MUTED}]── Git ──[/bold {Theme.MUTED}]", "")
|
|
284
|
+
help_table.add_row(" 🔀 /commit", "Auto-generate and commit changes")
|
|
285
|
+
help_table.add_row(" 📊 /diff", "Show all uncommitted changes")
|
|
286
|
+
|
|
287
|
+
# ── Shortcuts ──
|
|
288
|
+
help_table.add_row(f"[bold {Theme.MUTED}]── Shortcuts ──[/bold {Theme.MUTED}]", "")
|
|
289
|
+
help_table.add_row(f" [{Theme.PINK}]@file / @url[/{Theme.PINK}]", "Attach file context or web URL")
|
|
290
|
+
help_table.add_row(f" [{Theme.PINK}]exit / quit[/{Theme.PINK}]", "Exit Aizen")
|
|
291
|
+
help_table.add_row(f" [{Theme.MUTED}]Tip[/{Theme.MUTED}]", f"[{Theme.MUTED}]End a line with \\\\ for multi-line input[/{Theme.MUTED}]")
|
|
269
292
|
console.print(help_table)
|
|
270
293
|
console.print()
|
|
271
294
|
|
|
@@ -276,51 +299,51 @@ async def handle_slash_command(
|
|
|
276
299
|
elif cmd == "/save":
|
|
277
300
|
try:
|
|
278
301
|
path = save_session(messages, arg if arg else None, token_tracker)
|
|
279
|
-
console.print(f"[
|
|
302
|
+
console.print(f" [{Theme.SUCCESS}]✓ Session saved to {path}[/{Theme.SUCCESS}]\n")
|
|
280
303
|
except Exception as e:
|
|
281
|
-
console.print(f"[
|
|
304
|
+
console.print(f" [{Theme.ERROR}]Error saving session: {e}[/{Theme.ERROR}]\n")
|
|
282
305
|
|
|
283
306
|
elif cmd == "/load":
|
|
284
307
|
if not arg:
|
|
285
308
|
sessions = list_sessions()
|
|
286
309
|
if not sessions:
|
|
287
|
-
console.print("[
|
|
310
|
+
console.print(f" [{Theme.WARNING}]No saved sessions found.[/{Theme.WARNING}]\n")
|
|
288
311
|
else:
|
|
289
312
|
table = Table(
|
|
290
313
|
title="📂 Saved Sessions",
|
|
291
|
-
border_style=
|
|
292
|
-
header_style="bold
|
|
314
|
+
border_style=Theme.BORDER,
|
|
315
|
+
header_style=f"bold {Theme.PRIMARY}",
|
|
293
316
|
)
|
|
294
|
-
table.add_column("Name", style=
|
|
295
|
-
table.add_column("Saved At", style=
|
|
317
|
+
table.add_column("Name", style=Theme.ACCENT)
|
|
318
|
+
table.add_column("Saved At", style=Theme.MUTED)
|
|
296
319
|
table.add_column("Messages", style="white", justify="right")
|
|
297
320
|
for s in sessions[:10]:
|
|
298
321
|
table.add_row(s["name"], s["saved_at"][:19], str(s["messages"]))
|
|
299
322
|
console.print(table)
|
|
300
|
-
console.print("[
|
|
323
|
+
console.print(f" [{Theme.MUTED}]Usage: /load <session_name>[/{Theme.MUTED}]\n")
|
|
301
324
|
else:
|
|
302
325
|
loaded = load_session(arg)
|
|
303
326
|
if loaded:
|
|
304
327
|
messages[:] = loaded
|
|
305
328
|
console.print(
|
|
306
|
-
f"[
|
|
329
|
+
f" [{Theme.SUCCESS}]✓ Loaded session '{arg}' ({len(loaded)} messages)[/{Theme.SUCCESS}]\n"
|
|
307
330
|
)
|
|
308
331
|
else:
|
|
309
|
-
console.print(f"[
|
|
332
|
+
console.print(f" [{Theme.ERROR}]Session '{arg}' not found.[/{Theme.ERROR}]\n")
|
|
310
333
|
|
|
311
334
|
elif cmd == "/undo":
|
|
312
335
|
result = backup_manager.undo()
|
|
313
|
-
console.print(f"[
|
|
336
|
+
console.print(f" [{Theme.SUCCESS}]{result}[/{Theme.SUCCESS}]\n")
|
|
314
337
|
|
|
315
338
|
elif cmd == "/retry":
|
|
316
339
|
# Remove last assistant + tool messages, then re-process the last user message
|
|
317
340
|
while messages and messages[-1]["role"] in ("assistant", "tool"):
|
|
318
341
|
messages.pop()
|
|
319
342
|
if messages and messages[-1]["role"] == "user":
|
|
320
|
-
console.print("[
|
|
343
|
+
console.print(f" [{Theme.SUCCESS}]✓ Retrying last message...[/{Theme.SUCCESS}]\n")
|
|
321
344
|
return True # Signal to re-process
|
|
322
345
|
else:
|
|
323
|
-
console.print("[
|
|
346
|
+
console.print(f" [{Theme.WARNING}]Nothing to retry.[/{Theme.WARNING}]\n")
|
|
324
347
|
|
|
325
348
|
elif cmd == "/copy":
|
|
326
349
|
last_response = None
|
|
@@ -349,13 +372,13 @@ async def handle_slash_command(
|
|
|
349
372
|
subprocess.run(
|
|
350
373
|
["clip"], input=last_response, text=True, check=True
|
|
351
374
|
)
|
|
352
|
-
console.print("[
|
|
375
|
+
console.print(f" [{Theme.SUCCESS}]✓ Copied to clipboard.[/{Theme.SUCCESS}]\n")
|
|
353
376
|
except Exception:
|
|
354
377
|
console.print(
|
|
355
|
-
"[
|
|
378
|
+
f" [{Theme.WARNING}]⚠️ Could not copy to clipboard.[/{Theme.WARNING}]\n"
|
|
356
379
|
)
|
|
357
380
|
else:
|
|
358
|
-
console.print("[
|
|
381
|
+
console.print(f" [{Theme.WARNING}]No response to copy.[/{Theme.WARNING}]\n")
|
|
359
382
|
|
|
360
383
|
elif cmd == "/export":
|
|
361
384
|
filename = (
|
|
@@ -377,13 +400,13 @@ async def handle_slash_command(
|
|
|
377
400
|
f.write(f"## 👤 You\n\n{msg['content']}\n\n")
|
|
378
401
|
elif msg["role"] == "assistant" and msg.get("content"):
|
|
379
402
|
f.write(f"## ✦ Aizen\n\n{msg['content']}\n\n")
|
|
380
|
-
console.print(f"[
|
|
403
|
+
console.print(f" [{Theme.SUCCESS}]✓ Exported to {filename}[/{Theme.SUCCESS}]\n")
|
|
381
404
|
except Exception as e:
|
|
382
|
-
console.print(f"[
|
|
405
|
+
console.print(f" [{Theme.ERROR}]Error exporting: {e}[/{Theme.ERROR}]\n")
|
|
383
406
|
|
|
384
407
|
elif cmd == "/compact":
|
|
385
408
|
if len(messages) <= 4:
|
|
386
|
-
console.print("[
|
|
409
|
+
console.print(f" [{Theme.WARNING}]Conversation is already compact.[/{Theme.WARNING}]\n")
|
|
387
410
|
else:
|
|
388
411
|
system_msg = messages[0]
|
|
389
412
|
recent = messages[-4:]
|
|
@@ -391,7 +414,7 @@ async def handle_slash_command(
|
|
|
391
414
|
|
|
392
415
|
if middle:
|
|
393
416
|
# Attempt LLM-based summarization for much better context retention
|
|
394
|
-
console.print("[
|
|
417
|
+
console.print(f" [{Theme.MUTED}]Summarizing conversation with AI...[/{Theme.MUTED}]")
|
|
395
418
|
try:
|
|
396
419
|
from openai import AsyncOpenAI as _AsyncOpenAI
|
|
397
420
|
|
|
@@ -449,20 +472,20 @@ async def handle_slash_command(
|
|
|
449
472
|
},
|
|
450
473
|
] + recent
|
|
451
474
|
console.print(
|
|
452
|
-
f"[
|
|
475
|
+
f" [{Theme.SUCCESS}]✓ Compacted {len(middle)} messages into an AI-generated summary.[/{Theme.SUCCESS}]\n"
|
|
453
476
|
)
|
|
454
477
|
else:
|
|
455
|
-
console.print("[
|
|
478
|
+
console.print(f" [{Theme.WARNING}]Not enough messages to compact.[/{Theme.WARNING}]\n")
|
|
456
479
|
|
|
457
480
|
elif cmd == "/config":
|
|
458
481
|
config = load_config()
|
|
459
482
|
table = Table(
|
|
460
483
|
title="⚙️ Configuration",
|
|
461
|
-
border_style=
|
|
462
|
-
header_style="bold
|
|
484
|
+
border_style=Theme.BORDER,
|
|
485
|
+
header_style=f"bold {Theme.PRIMARY}",
|
|
463
486
|
)
|
|
464
|
-
table.add_column("Key", style=
|
|
465
|
-
table.add_column("Value", style=
|
|
487
|
+
table.add_column("Key", style=Theme.ACCENT)
|
|
488
|
+
table.add_column("Value", style=Theme.TEXT)
|
|
466
489
|
table.add_row("Model", current_model)
|
|
467
490
|
table.add_row(
|
|
468
491
|
"API Base URL",
|
|
@@ -478,22 +501,22 @@ async def handle_slash_command(
|
|
|
478
501
|
|
|
479
502
|
elif cmd == "/mcp":
|
|
480
503
|
if not mcp_manager:
|
|
481
|
-
console.print("[
|
|
504
|
+
console.print(f" [{Theme.WARNING}]MCP Manager is not available.[/{Theme.WARNING}]\n")
|
|
482
505
|
return False
|
|
483
506
|
|
|
484
507
|
if not mcp_manager.config:
|
|
485
|
-
console.print("[
|
|
486
|
-
console.print("[
|
|
508
|
+
console.print(f" [{Theme.WARNING}]No MCP servers configured in ~/.aizen_config.json[/{Theme.WARNING}]\n")
|
|
509
|
+
console.print(f" [{Theme.MUTED}]Add an 'mcp_servers' block to your config to enable MCP plugins.[/{Theme.MUTED}]\n")
|
|
487
510
|
return False
|
|
488
511
|
|
|
489
512
|
table = Table(
|
|
490
513
|
title="🔌 Configured MCP Servers",
|
|
491
|
-
border_style=
|
|
492
|
-
header_style="bold
|
|
514
|
+
border_style=Theme.BORDER,
|
|
515
|
+
header_style=f"bold {Theme.PRIMARY}",
|
|
493
516
|
)
|
|
494
|
-
table.add_column("Server Name", style="
|
|
495
|
-
table.add_column("Status", style=
|
|
496
|
-
table.add_column("Tools Available", style=
|
|
517
|
+
table.add_column("Server Name", style=f"{Theme.ACCENT} bold")
|
|
518
|
+
table.add_column("Status", style=Theme.TEXT)
|
|
519
|
+
table.add_column("Tools Available", style=Theme.MUTED)
|
|
497
520
|
|
|
498
521
|
tools = mcp_manager.get_tools()
|
|
499
522
|
server_tools: dict[str, list[str]] = {srv: [] for srv in mcp_manager.config.keys()}
|
|
@@ -508,9 +531,9 @@ async def handle_slash_command(
|
|
|
508
531
|
|
|
509
532
|
for server_name in mcp_manager.config.keys():
|
|
510
533
|
if server_name in mcp_manager.sessions:
|
|
511
|
-
status = "[
|
|
534
|
+
status = f"[{Theme.SUCCESS}]● Connected[/{Theme.SUCCESS}]"
|
|
512
535
|
else:
|
|
513
|
-
status = "[
|
|
536
|
+
status = f"[{Theme.ERROR}]● Disconnected[/{Theme.ERROR}]"
|
|
514
537
|
|
|
515
538
|
tool_count = len(server_tools[server_name])
|
|
516
539
|
if tool_count > 0:
|
|
@@ -531,37 +554,37 @@ async def handle_slash_command(
|
|
|
531
554
|
name = arg or f"cp_{datetime.now().strftime('%H%M%S')}"
|
|
532
555
|
_checkpoints[name] = copy.deepcopy(messages)
|
|
533
556
|
console.print(
|
|
534
|
-
f"[
|
|
557
|
+
f" [{Theme.SUCCESS}]✓ Checkpoint '{name}' saved ({len(messages)} messages)[/{Theme.SUCCESS}]\n"
|
|
535
558
|
)
|
|
536
559
|
|
|
537
560
|
elif cmd == "/restore":
|
|
538
561
|
if not arg:
|
|
539
562
|
if not _checkpoints:
|
|
540
|
-
console.print("[
|
|
563
|
+
console.print(f" [{Theme.WARNING}]No checkpoints saved. Use /checkpoint [name] first.[/{Theme.WARNING}]\n")
|
|
541
564
|
else:
|
|
542
565
|
table = Table(
|
|
543
566
|
title="📌 Checkpoints",
|
|
544
|
-
border_style=
|
|
545
|
-
header_style="bold
|
|
567
|
+
border_style=Theme.BORDER,
|
|
568
|
+
header_style=f"bold {Theme.PRIMARY}",
|
|
546
569
|
)
|
|
547
|
-
table.add_column("Name", style=
|
|
570
|
+
table.add_column("Name", style=Theme.ACCENT)
|
|
548
571
|
table.add_column("Messages", style="white", justify="right")
|
|
549
572
|
for cp_name, cp_msgs in _checkpoints.items():
|
|
550
573
|
table.add_row(cp_name, str(len(cp_msgs)))
|
|
551
574
|
console.print(table)
|
|
552
|
-
console.print("[
|
|
575
|
+
console.print(f" [{Theme.MUTED}]Usage: /restore <name>[/{Theme.MUTED}]\n")
|
|
553
576
|
else:
|
|
554
577
|
if arg in _checkpoints:
|
|
555
578
|
messages[:] = copy.deepcopy(_checkpoints[arg])
|
|
556
579
|
console.print(
|
|
557
|
-
f"[
|
|
580
|
+
f" [{Theme.SUCCESS}]✓ Restored checkpoint '{arg}' ({len(messages)} messages)[/{Theme.SUCCESS}]\n"
|
|
558
581
|
)
|
|
559
582
|
else:
|
|
560
|
-
console.print(f"[
|
|
583
|
+
console.print(f" [{Theme.ERROR}]Checkpoint '{arg}' not found.[/{Theme.ERROR}]\n")
|
|
561
584
|
|
|
562
585
|
elif cmd == "/commit":
|
|
563
586
|
if not client:
|
|
564
|
-
console.print("[
|
|
587
|
+
console.print(f" [{Theme.ERROR}]API client is not available for /commit.[/{Theme.ERROR}]\n")
|
|
565
588
|
return False
|
|
566
589
|
|
|
567
590
|
try:
|
|
@@ -575,12 +598,12 @@ async def handle_slash_command(
|
|
|
575
598
|
unstaged_diff = result_unstaged.stdout.strip()
|
|
576
599
|
|
|
577
600
|
if not unstaged_diff:
|
|
578
|
-
console.print("[
|
|
601
|
+
console.print(f" [{Theme.WARNING}]No changes found to commit.[/{Theme.WARNING}]\n")
|
|
579
602
|
return False
|
|
580
603
|
|
|
581
604
|
answer = prompt("No staged changes. Stage all current changes? [Y/n] ")
|
|
582
605
|
if answer.lower() not in ("y", "yes", ""):
|
|
583
|
-
console.print("[
|
|
606
|
+
console.print(f" [{Theme.WARNING}]Commit aborted.[/{Theme.WARNING}]\n")
|
|
584
607
|
return False
|
|
585
608
|
|
|
586
609
|
subprocess.run(["git", "add", "-u"], check=True)
|
|
@@ -588,10 +611,10 @@ async def handle_slash_command(
|
|
|
588
611
|
diff = result.stdout.strip()
|
|
589
612
|
|
|
590
613
|
if not diff:
|
|
591
|
-
console.print("[
|
|
614
|
+
console.print(f" [{Theme.WARNING}]No changes staged to commit.[/{Theme.WARNING}]\n")
|
|
592
615
|
return False
|
|
593
616
|
|
|
594
|
-
console.print("[
|
|
617
|
+
console.print(f" [{Theme.MUTED}]Generating commit message...[/{Theme.MUTED}]")
|
|
595
618
|
|
|
596
619
|
commit_messages = [
|
|
597
620
|
{"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 +630,8 @@ async def handle_slash_command(
|
|
|
607
630
|
# Remove any markdown codeblocks if model didn't listen
|
|
608
631
|
commit_msg = commit_msg.replace("```text", "").replace("```", "").strip()
|
|
609
632
|
|
|
610
|
-
console.print("\n[bold]Generated Commit Message:[/bold]")
|
|
611
|
-
console.print(f"[
|
|
633
|
+
console.print(f"\n [bold {Theme.TEXT}]Generated Commit Message:[/bold {Theme.TEXT}]")
|
|
634
|
+
console.print(f" [{Theme.ACCENT}]{commit_msg}[/{Theme.ACCENT}]\n")
|
|
612
635
|
|
|
613
636
|
action = prompt("Commit with this message? [Y/n/e(dit)] ")
|
|
614
637
|
action = action.lower().strip()
|
|
@@ -622,12 +645,12 @@ async def handle_slash_command(
|
|
|
622
645
|
return False
|
|
623
646
|
|
|
624
647
|
subprocess.run(["git", "commit", "-m", final_msg], check=True)
|
|
625
|
-
console.print("[
|
|
648
|
+
console.print(f" [{Theme.SUCCESS}]✓ Committed successfully.[/{Theme.SUCCESS}]\n")
|
|
626
649
|
|
|
627
650
|
except subprocess.CalledProcessError:
|
|
628
|
-
console.print("[
|
|
651
|
+
console.print(f" [{Theme.ERROR}]Error: Not a git repository or git command failed.[/{Theme.ERROR}]\n")
|
|
629
652
|
except Exception as e:
|
|
630
|
-
console.print(f"[
|
|
653
|
+
console.print(f" [{Theme.ERROR}]Error during auto-commit: {e}[/{Theme.ERROR}]\n")
|
|
631
654
|
|
|
632
655
|
elif cmd == "/diff":
|
|
633
656
|
try:
|
|
@@ -648,18 +671,18 @@ async def handle_slash_command(
|
|
|
648
671
|
has_output = False
|
|
649
672
|
|
|
650
673
|
if result_staged.stdout.strip():
|
|
651
|
-
console.print("[bold
|
|
674
|
+
console.print(f" [bold {Theme.SUCCESS}]Staged changes:[/bold {Theme.SUCCESS}]")
|
|
652
675
|
console.print(f"[dim]{result_staged.stdout.strip()}[/dim]")
|
|
653
676
|
has_output = True
|
|
654
677
|
|
|
655
678
|
if result_unstaged.stdout.strip():
|
|
656
|
-
console.print("[bold
|
|
679
|
+
console.print(f" [bold {Theme.WARNING}]Unstaged changes:[/bold {Theme.WARNING}]")
|
|
657
680
|
console.print(f"[dim]{result_unstaged.stdout.strip()}[/dim]")
|
|
658
681
|
has_output = True
|
|
659
682
|
|
|
660
683
|
if result_untracked.stdout.strip():
|
|
661
684
|
untracked = result_untracked.stdout.strip().split("\n")
|
|
662
|
-
console.print(f"[bold
|
|
685
|
+
console.print(f" [bold {Theme.ACCENT}]Untracked files ({len(untracked)}):[/bold {Theme.ACCENT}]")
|
|
663
686
|
for f in untracked[:20]:
|
|
664
687
|
console.print(f" [dim]+ {f}[/dim]")
|
|
665
688
|
if len(untracked) > 20:
|
|
@@ -667,7 +690,7 @@ async def handle_slash_command(
|
|
|
667
690
|
has_output = True
|
|
668
691
|
|
|
669
692
|
if not has_output:
|
|
670
|
-
console.print("[
|
|
693
|
+
console.print(f" [{Theme.SUCCESS}]✓ Working tree is clean.[/{Theme.SUCCESS}]")
|
|
671
694
|
|
|
672
695
|
# Show full diff if requested
|
|
673
696
|
if arg == "--full" or arg == "-f":
|
|
@@ -682,13 +705,13 @@ async def handle_slash_command(
|
|
|
682
705
|
|
|
683
706
|
console.print()
|
|
684
707
|
except subprocess.CalledProcessError:
|
|
685
|
-
console.print("[
|
|
708
|
+
console.print(f" [{Theme.ERROR}]Error: Not a git repository or git command failed.[/{Theme.ERROR}]\n")
|
|
686
709
|
except Exception as e:
|
|
687
|
-
console.print(f"[
|
|
710
|
+
console.print(f" [{Theme.ERROR}]Error showing diff: {e}[/{Theme.ERROR}]\n")
|
|
688
711
|
|
|
689
712
|
else:
|
|
690
713
|
console.print(
|
|
691
|
-
f"[
|
|
714
|
+
f" [{Theme.ERROR}]Unknown command: {cmd}[/{Theme.ERROR}] — type [bold {Theme.ACCENT}]/help[/bold {Theme.ACCENT}] for commands.\n"
|
|
692
715
|
)
|
|
693
716
|
|
|
694
717
|
return False
|