kader 0.1.2__py3-none-any.whl → 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cli/app.py +124 -2
- cli/app.tcss +17 -0
- cli/utils.py +1 -0
- {kader-0.1.2.dist-info → kader-0.1.3.dist-info}/METADATA +2 -1
- {kader-0.1.2.dist-info → kader-0.1.3.dist-info}/RECORD +7 -7
- {kader-0.1.2.dist-info → kader-0.1.3.dist-info}/WHEEL +0 -0
- {kader-0.1.2.dist-info → kader-0.1.3.dist-info}/entry_points.txt +0 -0
cli/app.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import threading
|
|
5
|
+
from importlib.metadata import version as get_version
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from typing import Optional
|
|
7
8
|
|
|
@@ -32,9 +33,19 @@ from .utils import (
|
|
|
32
33
|
)
|
|
33
34
|
from .widgets import ConversationView, InlineSelector, LoadingSpinner, ModelSelector
|
|
34
35
|
|
|
35
|
-
WELCOME_MESSAGE = """
|
|
36
|
+
WELCOME_MESSAGE = """
|
|
37
|
+
<div align="center">
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
```
|
|
40
|
+
██╗ ██╗ ██╗ █████╗ ██████╗ ███████╗██████╗
|
|
41
|
+
██╔╝ ██║ ██╔╝██╔══██╗██╔══██╗██╔════╝██╔══██╗
|
|
42
|
+
██╔╝ █████╔╝ ███████║██║ ██║█████╗ ██████╔╝
|
|
43
|
+
██╔╝ ██╔═██╗ ██╔══██║██║ ██║██╔══╝ ██╔══██╗
|
|
44
|
+
██╔╝ ██║ ██╗██║ ██║██████╔╝███████╗██║ ██║
|
|
45
|
+
╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚══════╝╚═╝ ╚═╝
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
</div>
|
|
38
49
|
|
|
39
50
|
Type a message below to start chatting, or use one of the commands:
|
|
40
51
|
|
|
@@ -45,10 +56,16 @@ Type a message below to start chatting, or use one of the commands:
|
|
|
45
56
|
- `/save` - Save current session
|
|
46
57
|
- `/load` - Load a saved session
|
|
47
58
|
- `/sessions` - List saved sessions
|
|
59
|
+
- `/cost` - Show the cost of the conversation
|
|
48
60
|
- `/exit` - Exit the application
|
|
49
61
|
"""
|
|
50
62
|
|
|
51
63
|
|
|
64
|
+
# Minimum terminal size to prevent UI breakage
|
|
65
|
+
MIN_WIDTH = 89
|
|
66
|
+
MIN_HEIGHT = 29
|
|
67
|
+
|
|
68
|
+
|
|
52
69
|
class KaderApp(App):
|
|
53
70
|
"""Main Kader CLI application."""
|
|
54
71
|
|
|
@@ -81,6 +98,7 @@ class KaderApp(App):
|
|
|
81
98
|
self._confirmation_result: tuple[bool, Optional[str]] = (True, None)
|
|
82
99
|
self._inline_selector: Optional[InlineSelector] = None
|
|
83
100
|
self._model_selector: Optional[ModelSelector] = None
|
|
101
|
+
self._update_info: Optional[str] = None # Latest version if update available
|
|
84
102
|
|
|
85
103
|
self._agent = self._create_agent(self._current_model)
|
|
86
104
|
|
|
@@ -291,6 +309,71 @@ class KaderApp(App):
|
|
|
291
309
|
# Focus the input
|
|
292
310
|
self.query_one("#prompt-input", Input).focus()
|
|
293
311
|
|
|
312
|
+
# Check initial size
|
|
313
|
+
self._check_terminal_size()
|
|
314
|
+
|
|
315
|
+
# Start background update check
|
|
316
|
+
threading.Thread(target=self._check_for_updates, daemon=True).start()
|
|
317
|
+
|
|
318
|
+
def _check_for_updates(self) -> None:
|
|
319
|
+
"""Check for package updates in background thread."""
|
|
320
|
+
try:
|
|
321
|
+
from outdated import check_outdated
|
|
322
|
+
|
|
323
|
+
current_version = get_version("kader")
|
|
324
|
+
is_outdated, latest_version = check_outdated("kader", current_version)
|
|
325
|
+
|
|
326
|
+
if is_outdated:
|
|
327
|
+
self._update_info = latest_version
|
|
328
|
+
# Schedule UI update on main thread
|
|
329
|
+
self.call_from_thread(self._show_update_notification)
|
|
330
|
+
except Exception:
|
|
331
|
+
# Silently ignore update check failures
|
|
332
|
+
pass
|
|
333
|
+
|
|
334
|
+
def _show_update_notification(self) -> None:
|
|
335
|
+
"""Show update notification as a toast."""
|
|
336
|
+
if not self._update_info:
|
|
337
|
+
return
|
|
338
|
+
|
|
339
|
+
try:
|
|
340
|
+
current = get_version("kader")
|
|
341
|
+
message = (
|
|
342
|
+
f">> Update available! v{current} → v{self._update_info} "
|
|
343
|
+
f"Run: uv tool upgrade kader"
|
|
344
|
+
)
|
|
345
|
+
self.notify(message, severity="information", timeout=10)
|
|
346
|
+
except Exception:
|
|
347
|
+
pass
|
|
348
|
+
|
|
349
|
+
def on_resize(self) -> None:
|
|
350
|
+
"""Handle terminal resize events."""
|
|
351
|
+
self._check_terminal_size()
|
|
352
|
+
|
|
353
|
+
def _check_terminal_size(self) -> None:
|
|
354
|
+
"""Check if terminal is large enough and show warning if not."""
|
|
355
|
+
width = self.console.size.width
|
|
356
|
+
height = self.console.size.height
|
|
357
|
+
|
|
358
|
+
# Check if we need to show/hide the size warning
|
|
359
|
+
too_small = width < MIN_WIDTH or height < MIN_HEIGHT
|
|
360
|
+
|
|
361
|
+
try:
|
|
362
|
+
warning = self.query_one("#size-warning", Static)
|
|
363
|
+
if not too_small:
|
|
364
|
+
warning.remove()
|
|
365
|
+
except Exception:
|
|
366
|
+
if too_small:
|
|
367
|
+
# Show warning overlay
|
|
368
|
+
warning_text = f"""⚠️ Terminal Too Small
|
|
369
|
+
|
|
370
|
+
Current: {width}x{height}
|
|
371
|
+
Minimum: {MIN_WIDTH}x{MIN_HEIGHT}
|
|
372
|
+
|
|
373
|
+
Please resize your terminal."""
|
|
374
|
+
warning = Static(warning_text, id="size-warning")
|
|
375
|
+
self.mount(warning)
|
|
376
|
+
|
|
294
377
|
async def on_input_submitted(self, event: Input.Submitted) -> None:
|
|
295
378
|
"""Handle user input submission."""
|
|
296
379
|
user_input = event.value.strip()
|
|
@@ -324,6 +407,7 @@ class KaderApp(App):
|
|
|
324
407
|
elif cmd == "/clear":
|
|
325
408
|
conversation.clear_messages()
|
|
326
409
|
self._agent.memory.clear()
|
|
410
|
+
self._agent.provider.reset_tracking() # Reset usage/cost tracking
|
|
327
411
|
self._current_session_id = None
|
|
328
412
|
self.notify("Conversation cleared!", severity="information")
|
|
329
413
|
elif cmd == "/save":
|
|
@@ -342,6 +426,8 @@ class KaderApp(App):
|
|
|
342
426
|
elif cmd == "/refresh":
|
|
343
427
|
self._refresh_directory_tree()
|
|
344
428
|
self.notify("Directory tree refreshed!", severity="information")
|
|
429
|
+
elif cmd == "/cost":
|
|
430
|
+
self._handle_cost(conversation)
|
|
345
431
|
elif cmd == "/exit":
|
|
346
432
|
self.exit()
|
|
347
433
|
else:
|
|
@@ -536,6 +622,42 @@ class KaderApp(App):
|
|
|
536
622
|
conversation.add_message(f"❌ Error listing sessions: {e}", "assistant")
|
|
537
623
|
self.notify(f"Error: {e}", severity="error")
|
|
538
624
|
|
|
625
|
+
def _handle_cost(self, conversation: ConversationView) -> None:
|
|
626
|
+
"""Display LLM usage costs."""
|
|
627
|
+
try:
|
|
628
|
+
# Get cost and usage from the provider
|
|
629
|
+
cost = self._agent.provider.total_cost
|
|
630
|
+
usage = self._agent.provider.total_usage
|
|
631
|
+
model = self._agent.provider.model
|
|
632
|
+
|
|
633
|
+
lines = [
|
|
634
|
+
"## Usage Costs 💰\n",
|
|
635
|
+
f"**Model:** `{model}`\n",
|
|
636
|
+
"### Cost Breakdown",
|
|
637
|
+
"| Type | Amount |",
|
|
638
|
+
"|------|--------|",
|
|
639
|
+
f"| Input Cost | ${cost.input_cost:.6f} |",
|
|
640
|
+
f"| Output Cost | ${cost.output_cost:.6f} |",
|
|
641
|
+
f"| **Total Cost** | **${cost.total_cost:.6f}** |",
|
|
642
|
+
"",
|
|
643
|
+
"### Token Usage",
|
|
644
|
+
"| Type | Tokens |",
|
|
645
|
+
"|------|--------|",
|
|
646
|
+
f"| Prompt Tokens | {usage.prompt_tokens:,} |",
|
|
647
|
+
f"| Completion Tokens | {usage.completion_tokens:,} |",
|
|
648
|
+
f"| **Total Tokens** | **{usage.total_tokens:,}** |",
|
|
649
|
+
]
|
|
650
|
+
|
|
651
|
+
if cost.total_cost == 0.0:
|
|
652
|
+
lines.append(
|
|
653
|
+
"\n> 💡 *Note: Ollama runs locally, so there are no API costs.*"
|
|
654
|
+
)
|
|
655
|
+
|
|
656
|
+
conversation.add_message("\n".join(lines), "assistant")
|
|
657
|
+
except Exception as e:
|
|
658
|
+
conversation.add_message(f"❌ Error getting costs: {e}", "assistant")
|
|
659
|
+
self.notify(f"Error: {e}", severity="error")
|
|
660
|
+
|
|
539
661
|
|
|
540
662
|
def main() -> None:
|
|
541
663
|
"""Run the Kader CLI application."""
|
cli/app.tcss
CHANGED
|
@@ -17,6 +17,23 @@ $text-muted: #6c7086;
|
|
|
17
17
|
|
|
18
18
|
Screen {
|
|
19
19
|
background: $background;
|
|
20
|
+
min-width: 89;
|
|
21
|
+
min-height: 29;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* ===== Size Warning Overlay ===== */
|
|
25
|
+
|
|
26
|
+
#size-warning {
|
|
27
|
+
dock: top;
|
|
28
|
+
width: 100%;
|
|
29
|
+
height: 100%;
|
|
30
|
+
background: $background 95%;
|
|
31
|
+
color: $warning;
|
|
32
|
+
text-align: center;
|
|
33
|
+
content-align: center middle;
|
|
34
|
+
text-style: bold;
|
|
35
|
+
padding: 2;
|
|
36
|
+
layer: overlay;
|
|
20
37
|
}
|
|
21
38
|
|
|
22
39
|
/* ===== Header ===== */
|
cli/utils.py
CHANGED
|
@@ -19,6 +19,7 @@ HELP_TEXT = """## Kader CLI Commands 📖
|
|
|
19
19
|
| `/save` | Save current session |
|
|
20
20
|
| `/load <id>` | Load a saved session |
|
|
21
21
|
| `/sessions` | List saved sessions |
|
|
22
|
+
| `/cost` | Show usage costs |
|
|
22
23
|
| `/refresh` | Refresh file tree |
|
|
23
24
|
| `/exit` | Exit the CLI |
|
|
24
25
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kader
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: kader coding agent
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Requires-Dist: faiss-cpu>=1.9.0
|
|
7
7
|
Requires-Dist: jinja2>=3.1.6
|
|
8
8
|
Requires-Dist: loguru>=0.7.3
|
|
9
9
|
Requires-Dist: ollama>=0.6.1
|
|
10
|
+
Requires-Dist: outdated>=0.2.2
|
|
10
11
|
Requires-Dist: pyyaml>=6.0.3
|
|
11
12
|
Requires-Dist: tenacity>=9.1.2
|
|
12
13
|
Requires-Dist: textual[syntax]>=6.8.0
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
cli/README.md,sha256=DY3X7w6LPka1GzhtTrGwhpkFmx0YyRpcTCHjFmti3Yg,4654
|
|
2
2
|
cli/__init__.py,sha256=OAi_KSwcuYXR0sRxKuw1DYQrz1jbu8p7vn41_99f36I,107
|
|
3
3
|
cli/__main__.py,sha256=xO2JVjCsh691b-cjSBAEKocJeUeI3P0gfUqM-f1Mp1A,95
|
|
4
|
-
cli/app.py,sha256=
|
|
5
|
-
cli/app.tcss,sha256=
|
|
6
|
-
cli/utils.py,sha256=
|
|
4
|
+
cli/app.py,sha256=PwYDnhdCJjSHpDqPjwlhHQoHtirZUNv1WHkG9Hr0Ius,24805
|
|
5
|
+
cli/app.tcss,sha256=fImM5JxlTCWvD4i3OmpJpBOEE8HGo87Q9E8rKbOKEGU,10416
|
|
6
|
+
cli/utils.py,sha256=GkyVTkIjNsnEEYCVD2l5snQybReXknfg5RihKXWuK4w,1846
|
|
7
7
|
cli/widgets/__init__.py,sha256=1vj31CrJyxZROLthkKr79i_GbNyj8g3q60ZQPbJHK5k,300
|
|
8
8
|
cli/widgets/confirmation.py,sha256=s6h3ZY98xHeeF3G-OtHYYw-xPlAP6K7i73jo_ftOMQc,9400
|
|
9
9
|
cli/widgets/conversation.py,sha256=vqI3eq7qQrn4UD8fB8rBr33kIesrLpNVhNIp3MdGeOM,1519
|
|
@@ -39,7 +39,7 @@ kader/tools/rag.py,sha256=37Nd49D5R_DlWmMyhSdSvst_XFOnoxpaFNtlbLEt6qM,15653
|
|
|
39
39
|
kader/tools/todo.py,sha256=omirxoG7_KVHAmMoD12DGmn7scqRaewbI1cqWm0ShUo,7735
|
|
40
40
|
kader/tools/utils.py,sha256=bfq7b1vpA1qBaL_QZYBFTqOM35-omu_UeDjo6v0rxEg,14176
|
|
41
41
|
kader/tools/web.py,sha256=2Aqy0nO7ZwNLAqE9IWjCg5y8qnhbt-AIZOHy02XgbxA,8464
|
|
42
|
-
kader-0.1.
|
|
43
|
-
kader-0.1.
|
|
44
|
-
kader-0.1.
|
|
45
|
-
kader-0.1.
|
|
42
|
+
kader-0.1.3.dist-info/METADATA,sha256=UbSQYvgdw-4AWNgfGv22VafMPPkY54nFQp5PgKq-QMA,9521
|
|
43
|
+
kader-0.1.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
44
|
+
kader-0.1.3.dist-info/entry_points.txt,sha256=TK0VOtrfDFqZ8JQfxpuAHHvDLHyoiafUjS-VOixl02c,39
|
|
45
|
+
kader-0.1.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|