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 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 = """# Welcome to Kader CLI! 🚀
36
+ WELCOME_MESSAGE = """
37
+ <div align="center">
36
38
 
37
- Your **modern AI-powered coding assistant**.
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.2
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=eEWrfq_XobsjAeSc2lRMNBJ1cs2nlZ_qeoamuqSnubg,19981
5
- cli/app.tcss,sha256=eJX5akeqHmoZVDjibvBPIBot5UK7eiUxgojNu9IjMBs,10098
6
- cli/utils.py,sha256=jt5wvSMIjPP-ZbvXEK2fd00-oRj32sj95HinJd4c73Q,1815
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.2.dist-info/METADATA,sha256=FvswsMNeTn3O6mOQR5jiK_MXgKhgPeKB18eC4Hkq3hA,9490
43
- kader-0.1.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
44
- kader-0.1.2.dist-info/entry_points.txt,sha256=TK0VOtrfDFqZ8JQfxpuAHHvDLHyoiafUjS-VOixl02c,39
45
- kader-0.1.2.dist-info/RECORD,,
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