kader 0.1.3__tar.gz → 0.1.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.
Files changed (78) hide show
  1. {kader-0.1.3 → kader-0.1.5}/PKG-INFO +1 -1
  2. {kader-0.1.3 → kader-0.1.5}/cli/app.py +63 -25
  3. {kader-0.1.3 → kader-0.1.5}/cli/app.tcss +9 -10
  4. {kader-0.1.3 → kader-0.1.5}/cli/utils.py +13 -8
  5. {kader-0.1.3 → kader-0.1.5}/cli/widgets/confirmation.py +5 -5
  6. {kader-0.1.3 → kader-0.1.5}/cli/widgets/conversation.py +1 -1
  7. {kader-0.1.3 → kader-0.1.5}/cli/widgets/loading.py +1 -1
  8. {kader-0.1.3 → kader-0.1.5}/kader/agent/base.py +10 -5
  9. {kader-0.1.3 → kader-0.1.5}/pyproject.toml +1 -1
  10. {kader-0.1.3 → kader-0.1.5}/uv.lock +1 -1
  11. {kader-0.1.3 → kader-0.1.5}/.github/workflows/ci.yml +0 -0
  12. {kader-0.1.3 → kader-0.1.5}/.github/workflows/release.yml +0 -0
  13. {kader-0.1.3 → kader-0.1.5}/.gitignore +0 -0
  14. {kader-0.1.3 → kader-0.1.5}/.python-version +0 -0
  15. {kader-0.1.3 → kader-0.1.5}/.qwen/QWEN.md +0 -0
  16. {kader-0.1.3 → kader-0.1.5}/.qwen/agents/technical-writer.md +0 -0
  17. {kader-0.1.3 → kader-0.1.5}/.qwen/agents/test-automation-specialist.md +0 -0
  18. {kader-0.1.3 → kader-0.1.5}/README.md +0 -0
  19. {kader-0.1.3 → kader-0.1.5}/cli/README.md +0 -0
  20. {kader-0.1.3 → kader-0.1.5}/cli/__init__.py +0 -0
  21. {kader-0.1.3 → kader-0.1.5}/cli/__main__.py +0 -0
  22. {kader-0.1.3 → kader-0.1.5}/cli/widgets/__init__.py +0 -0
  23. {kader-0.1.3 → kader-0.1.5}/examples/.gitignore +0 -0
  24. {kader-0.1.3 → kader-0.1.5}/examples/README.md +0 -0
  25. {kader-0.1.3 → kader-0.1.5}/examples/memory_example.py +0 -0
  26. {kader-0.1.3 → kader-0.1.5}/examples/ollama_example.py +0 -0
  27. {kader-0.1.3 → kader-0.1.5}/examples/planning_agent_example.py +0 -0
  28. {kader-0.1.3 → kader-0.1.5}/examples/python_developer/main.py +0 -0
  29. {kader-0.1.3 → kader-0.1.5}/examples/python_developer/template.yaml +0 -0
  30. {kader-0.1.3 → kader-0.1.5}/examples/react_agent_example.py +0 -0
  31. {kader-0.1.3 → kader-0.1.5}/examples/simple_agent.py +0 -0
  32. {kader-0.1.3 → kader-0.1.5}/examples/todo_agent/main.py +0 -0
  33. {kader-0.1.3 → kader-0.1.5}/examples/tools_example.py +0 -0
  34. {kader-0.1.3 → kader-0.1.5}/kader/__init__.py +0 -0
  35. {kader-0.1.3 → kader-0.1.5}/kader/agent/__init__.py +0 -0
  36. {kader-0.1.3 → kader-0.1.5}/kader/agent/agents.py +0 -0
  37. {kader-0.1.3 → kader-0.1.5}/kader/agent/logger.py +0 -0
  38. {kader-0.1.3 → kader-0.1.5}/kader/config.py +0 -0
  39. {kader-0.1.3 → kader-0.1.5}/kader/memory/__init__.py +0 -0
  40. {kader-0.1.3 → kader-0.1.5}/kader/memory/conversation.py +0 -0
  41. {kader-0.1.3 → kader-0.1.5}/kader/memory/session.py +0 -0
  42. {kader-0.1.3 → kader-0.1.5}/kader/memory/state.py +0 -0
  43. {kader-0.1.3 → kader-0.1.5}/kader/memory/types.py +0 -0
  44. {kader-0.1.3 → kader-0.1.5}/kader/prompts/__init__.py +0 -0
  45. {kader-0.1.3 → kader-0.1.5}/kader/prompts/agent_prompts.py +0 -0
  46. {kader-0.1.3 → kader-0.1.5}/kader/prompts/base.py +0 -0
  47. {kader-0.1.3 → kader-0.1.5}/kader/prompts/templates/planning_agent.j2 +0 -0
  48. {kader-0.1.3 → kader-0.1.5}/kader/prompts/templates/react_agent.j2 +0 -0
  49. {kader-0.1.3 → kader-0.1.5}/kader/providers/__init__.py +0 -0
  50. {kader-0.1.3 → kader-0.1.5}/kader/providers/base.py +0 -0
  51. {kader-0.1.3 → kader-0.1.5}/kader/providers/mock.py +0 -0
  52. {kader-0.1.3 → kader-0.1.5}/kader/providers/ollama.py +0 -0
  53. {kader-0.1.3 → kader-0.1.5}/kader/tools/README.md +0 -0
  54. {kader-0.1.3 → kader-0.1.5}/kader/tools/__init__.py +0 -0
  55. {kader-0.1.3 → kader-0.1.5}/kader/tools/base.py +0 -0
  56. {kader-0.1.3 → kader-0.1.5}/kader/tools/exec_commands.py +0 -0
  57. {kader-0.1.3 → kader-0.1.5}/kader/tools/filesys.py +0 -0
  58. {kader-0.1.3 → kader-0.1.5}/kader/tools/filesystem.py +0 -0
  59. {kader-0.1.3 → kader-0.1.5}/kader/tools/protocol.py +0 -0
  60. {kader-0.1.3 → kader-0.1.5}/kader/tools/rag.py +0 -0
  61. {kader-0.1.3 → kader-0.1.5}/kader/tools/todo.py +0 -0
  62. {kader-0.1.3 → kader-0.1.5}/kader/tools/utils.py +0 -0
  63. {kader-0.1.3 → kader-0.1.5}/kader/tools/web.py +0 -0
  64. {kader-0.1.3 → kader-0.1.5}/tests/conftest.py +0 -0
  65. {kader-0.1.3 → kader-0.1.5}/tests/providers/test_mock.py +0 -0
  66. {kader-0.1.3 → kader-0.1.5}/tests/providers/test_ollama.py +0 -0
  67. {kader-0.1.3 → kader-0.1.5}/tests/providers/test_providers_base.py +0 -0
  68. {kader-0.1.3 → kader-0.1.5}/tests/test_agent_logger.py +0 -0
  69. {kader-0.1.3 → kader-0.1.5}/tests/test_agent_logger_integration.py +0 -0
  70. {kader-0.1.3 → kader-0.1.5}/tests/test_base_agent.py +0 -0
  71. {kader-0.1.3 → kader-0.1.5}/tests/test_file_memory.py +0 -0
  72. {kader-0.1.3 → kader-0.1.5}/tests/test_todo_tool.py +0 -0
  73. {kader-0.1.3 → kader-0.1.5}/tests/tools/test_exec_commands.py +0 -0
  74. {kader-0.1.3 → kader-0.1.5}/tests/tools/test_filesys_tools.py +0 -0
  75. {kader-0.1.3 → kader-0.1.5}/tests/tools/test_filesystem_tools.py +0 -0
  76. {kader-0.1.3 → kader-0.1.5}/tests/tools/test_rag.py +0 -0
  77. {kader-0.1.3 → kader-0.1.5}/tests/tools/test_tools_base.py +0 -0
  78. {kader-0.1.3 → kader-0.1.5}/tests/tools/test_web.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kader
3
- Version: 0.1.3
3
+ Version: 0.1.5
4
4
  Summary: kader coding agent
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: faiss-cpu>=1.9.0
@@ -10,12 +10,12 @@ from textual.app import App, ComposeResult
10
10
  from textual.binding import Binding
11
11
  from textual.containers import Container, Horizontal, Vertical
12
12
  from textual.widgets import (
13
- DirectoryTree,
14
13
  Footer,
15
14
  Header,
16
15
  Input,
17
16
  Markdown,
18
17
  Static,
18
+ Tree,
19
19
  )
20
20
 
21
21
  from kader.agent.agents import ReActAgent
@@ -66,11 +66,18 @@ MIN_WIDTH = 89
66
66
  MIN_HEIGHT = 29
67
67
 
68
68
 
69
+ class ASCIITree(Tree):
70
+ """A Tree widget that uses no icons."""
71
+
72
+ ICON_NODE = ""
73
+ ICON_NODE_EXPANDED = ""
74
+
75
+
69
76
  class KaderApp(App):
70
77
  """Main Kader CLI application."""
71
78
 
72
79
  TITLE = "Kader CLI"
73
- SUB_TITLE = "Modern Vibe Coding Assistant"
80
+ SUB_TITLE = f"v{get_version('kader')}"
74
81
  CSS_PATH = "app.tcss"
75
82
 
76
83
  BINDINGS = [
@@ -171,12 +178,16 @@ class KaderApp(App):
171
178
  self._confirmation_result = (event.confirmed, None)
172
179
 
173
180
  # Remove selector and show result message
181
+ tool_message = None
174
182
  if self._inline_selector:
183
+ tool_message = self._inline_selector.message
175
184
  self._inline_selector.remove()
176
185
  self._inline_selector = None
177
186
 
178
187
  if event.confirmed:
179
- conversation.add_message("✅ Executing tool...", "assistant")
188
+ if tool_message:
189
+ conversation.add_message(tool_message, "assistant")
190
+ conversation.add_message("(+) Executing tool...", "assistant")
180
191
  # Restart spinner
181
192
  try:
182
193
  spinner = self.query_one(LoadingSpinner)
@@ -184,7 +195,7 @@ class KaderApp(App):
184
195
  except Exception:
185
196
  pass
186
197
  else:
187
- conversation.add_message(" Tool execution skipped.", "assistant")
198
+ conversation.add_message("(-) Tool execution skipped.", "assistant")
188
199
 
189
200
  # Re-enable input
190
201
  prompt_input = self.query_one("#prompt-input", Input)
@@ -206,7 +217,8 @@ class KaderApp(App):
206
217
  models = OllamaProvider.get_supported_models()
207
218
  if not models:
208
219
  conversation.add_message(
209
- "## Models 🤖\n\n*No models found. Is Ollama running?*", "assistant"
220
+ "## Models (^^)\n\n*No models found. Is Ollama running?*",
221
+ "assistant",
210
222
  )
211
223
  return
212
224
 
@@ -224,7 +236,7 @@ class KaderApp(App):
224
236
 
225
237
  except Exception as e:
226
238
  conversation.add_message(
227
- f"## Models 🤖\n\n*Error fetching models: {e}*", "assistant"
239
+ f"## Models (^^)\n\n*Error fetching models: {e}*", "assistant"
228
240
  )
229
241
 
230
242
  def on_model_selector_model_selected(
@@ -244,7 +256,7 @@ class KaderApp(App):
244
256
  self._agent = self._create_agent(self._current_model)
245
257
 
246
258
  conversation.add_message(
247
- f" Model changed from `{old_model}` to `{self._current_model}`",
259
+ f"(+) Model changed from `{old_model}` to `{self._current_model}`",
248
260
  "assistant",
249
261
  )
250
262
 
@@ -281,8 +293,8 @@ class KaderApp(App):
281
293
  with Horizontal(id="main-container"):
282
294
  # Sidebar with directory tree
283
295
  with Vertical(id="sidebar"):
284
- yield Static("📁 Files", id="sidebar-title")
285
- yield DirectoryTree(Path.cwd(), id="directory-tree")
296
+ yield Static("Files", id="sidebar-title")
297
+ yield ASCIITree(str(Path.cwd().name), id="directory-tree")
286
298
 
287
299
  # Main content area
288
300
  with Vertical(id="content-area"):
@@ -312,9 +324,32 @@ class KaderApp(App):
312
324
  # Check initial size
313
325
  self._check_terminal_size()
314
326
 
327
+ # Start background update check
315
328
  # Start background update check
316
329
  threading.Thread(target=self._check_for_updates, daemon=True).start()
317
330
 
331
+ # Initial tree population
332
+ self._refresh_directory_tree()
333
+
334
+ def _populate_tree(self, node, path: Path) -> None:
335
+ """Recursively populate the tree with ASCII symbols."""
336
+ try:
337
+ # Sort: directories first, then files
338
+ items = sorted(
339
+ path.iterdir(), key=lambda p: (not p.is_dir(), p.name.lower())
340
+ )
341
+ for child in items:
342
+ if child.name.startswith((".", "__pycache__")):
343
+ continue
344
+
345
+ if child.is_dir():
346
+ new_node = node.add(f"[+] {child.name}", expand=False)
347
+ self._populate_tree(new_node, child)
348
+ else:
349
+ node.add(f"{child.name}")
350
+ except Exception:
351
+ pass
352
+
318
353
  def _check_for_updates(self) -> None:
319
354
  """Check for package updates in background thread."""
320
355
  try:
@@ -365,7 +400,7 @@ class KaderApp(App):
365
400
  except Exception:
366
401
  if too_small:
367
402
  # Show warning overlay
368
- warning_text = f"""⚠️ Terminal Too Small
403
+ warning_text = f"""<!> Terminal Too Small
369
404
 
370
405
  Current: {width}x{height}
371
406
  Minimum: {MIN_WIDTH}x{MIN_HEIGHT}
@@ -402,7 +437,7 @@ Please resize your terminal."""
402
437
  self._cycle_theme()
403
438
  theme_name = THEME_NAMES[self._current_theme_index]
404
439
  conversation.add_message(
405
- f"🎨 Theme changed to **{theme_name}**!", "assistant"
440
+ f"{{~}} Theme changed to **{theme_name}**!", "assistant"
406
441
  )
407
442
  elif cmd == "/clear":
408
443
  conversation.clear_messages()
@@ -478,7 +513,7 @@ Please resize your terminal."""
478
513
 
479
514
  except Exception as e:
480
515
  spinner.stop()
481
- error_msg = f" **Error:** {str(e)}\n\nMake sure Ollama is running and the model `{self._current_model}` is available."
516
+ error_msg = f"(-) **Error:** {str(e)}\n\nMake sure Ollama is running and the model `{self._current_model}` is available."
482
517
  conversation.add_message(error_msg, "assistant")
483
518
  self.notify(f"Error: {e}", severity="error")
484
519
 
@@ -526,10 +561,13 @@ Please resize your terminal."""
526
561
  self.notify("Directory tree refreshed!", severity="information")
527
562
 
528
563
  def _refresh_directory_tree(self) -> None:
529
- """Refresh the directory tree to show new/modified files."""
564
+ """Refresh the directory tree with ASCII symbols."""
530
565
  try:
531
- tree = self.query_one("#directory-tree", DirectoryTree)
532
- tree.reload()
566
+ tree = self.query_one("#directory-tree", ASCIITree)
567
+ tree.clear()
568
+ tree.root.label = str(Path.cwd().name)
569
+ self._populate_tree(tree.root, Path.cwd())
570
+ tree.root.expand()
533
571
  except Exception:
534
572
  pass # Silently ignore if tree not found
535
573
 
@@ -546,12 +584,12 @@ Please resize your terminal."""
546
584
  self._session_manager.save_conversation(self._current_session_id, messages)
547
585
 
548
586
  conversation.add_message(
549
- f" Session saved!\n\n**Session ID:** `{self._current_session_id}`",
587
+ f"(+) Session saved!\n\n**Session ID:** `{self._current_session_id}`",
550
588
  "assistant",
551
589
  )
552
590
  self.notify("Session saved!", severity="information")
553
591
  except Exception as e:
554
- conversation.add_message(f" Error saving session: {e}", "assistant")
592
+ conversation.add_message(f"(-) Error saving session: {e}", "assistant")
555
593
  self.notify(f"Error: {e}", severity="error")
556
594
 
557
595
  def _handle_load_session(
@@ -563,7 +601,7 @@ Please resize your terminal."""
563
601
  session = self._session_manager.get_session(session_id)
564
602
  if not session:
565
603
  conversation.add_message(
566
- f" Session `{session_id}` not found.\n\nUse `/sessions` to see available sessions.",
604
+ f"(-) Session `{session_id}` not found.\n\nUse `/sessions` to see available sessions.",
567
605
  "assistant",
568
606
  )
569
607
  return
@@ -585,12 +623,12 @@ Please resize your terminal."""
585
623
 
586
624
  self._current_session_id = session_id
587
625
  conversation.add_message(
588
- f" Session `{session_id}` loaded with {len(messages)} messages.",
626
+ f"(+) Session `{session_id}` loaded with {len(messages)} messages.",
589
627
  "assistant",
590
628
  )
591
629
  self.notify("Session loaded!", severity="information")
592
630
  except Exception as e:
593
- conversation.add_message(f" Error loading session: {e}", "assistant")
631
+ conversation.add_message(f"(-) Error loading session: {e}", "assistant")
594
632
  self.notify(f"Error: {e}", severity="error")
595
633
 
596
634
  def _handle_list_sessions(self, conversation: ConversationView) -> None:
@@ -600,13 +638,13 @@ Please resize your terminal."""
600
638
 
601
639
  if not sessions:
602
640
  conversation.add_message(
603
- "📭 No saved sessions found.\n\nUse `/save` to save the current session.",
641
+ "[ ] No saved sessions found.\n\nUse `/save` to save the current session.",
604
642
  "assistant",
605
643
  )
606
644
  return
607
645
 
608
646
  lines = [
609
- "## Saved Sessions 📂\n",
647
+ "## Saved Sessions [=]\n",
610
648
  "| Session ID | Created | Updated |",
611
649
  "|------------|---------|---------|",
612
650
  ]
@@ -631,7 +669,7 @@ Please resize your terminal."""
631
669
  model = self._agent.provider.model
632
670
 
633
671
  lines = [
634
- "## Usage Costs 💰\n",
672
+ "## Usage Costs ($)\n",
635
673
  f"**Model:** `{model}`\n",
636
674
  "### Cost Breakdown",
637
675
  "| Type | Amount |",
@@ -650,12 +688,12 @@ Please resize your terminal."""
650
688
 
651
689
  if cost.total_cost == 0.0:
652
690
  lines.append(
653
- "\n> 💡 *Note: Ollama runs locally, so there are no API costs.*"
691
+ "\n> (!) *Note: Ollama runs locally, so there are no API costs.*"
654
692
  )
655
693
 
656
694
  conversation.add_message("\n".join(lines), "assistant")
657
695
  except Exception as e:
658
- conversation.add_message(f" Error getting costs: {e}", "assistant")
696
+ conversation.add_message(f"(-) Error getting costs: {e}", "assistant")
659
697
  self.notify(f"Error: {e}", severity="error")
660
698
 
661
699
 
@@ -42,7 +42,7 @@ Header {
42
42
  background: $primary;
43
43
  color: $text;
44
44
  text-style: bold;
45
- height: 3;
45
+ height: 1;
46
46
  dock: top;
47
47
  }
48
48
 
@@ -75,9 +75,9 @@ FooterKey > .footer-key--key {
75
75
  /* ===== Sidebar (Directory Tree) ===== */
76
76
 
77
77
  #sidebar {
78
- width: 30;
79
- min-width: 20;
80
- max-width: 50;
78
+ width: 22;
79
+ min-width: 18;
80
+ max-width: 35;
81
81
  background: $surface;
82
82
  border-right: thick $primary;
83
83
  padding: 0;
@@ -126,7 +126,6 @@ DirectoryTree:focus > .directory-tree--cursor {
126
126
  #conversation {
127
127
  height: 1fr;
128
128
  background: $background;
129
- border-bottom: thick $surface;
130
129
  }
131
130
 
132
131
  ConversationView {
@@ -148,15 +147,15 @@ ConversationView {
148
147
 
149
148
  #input-container {
150
149
  height: auto;
151
- min-height: 3;
152
- max-height: 5;
153
- background: $surface;
150
+ min-height: 4;
151
+ max-height: 7;
152
+ background: $background;
154
153
  padding: 1;
155
- border-top: thick $primary;
154
+ margin-bottom: 1;
156
155
  }
157
156
 
158
157
  #prompt-input {
159
- background: $background;
158
+ background: transparent;
160
159
  border: round $primary;
161
160
  padding: 0 1;
162
161
  height: 3;
@@ -8,7 +8,7 @@ THEME_NAMES = ["dark", "ocean", "forest", "sunset"]
8
8
  # Default model
9
9
  DEFAULT_MODEL = "qwen3-coder:480b-cloud"
10
10
 
11
- HELP_TEXT = """## Kader CLI Commands 📖
11
+ HELP_TEXT = """## Kader CLI Commands
12
12
 
13
13
  | Command | Description |
14
14
  |---------|-------------|
@@ -23,7 +23,7 @@ HELP_TEXT = """## Kader CLI Commands 📖
23
23
  | `/refresh` | Refresh file tree |
24
24
  | `/exit` | Exit the CLI |
25
25
 
26
- ### Keyboard Shortcuts ⌨️
26
+ ### Keyboard Shortcuts
27
27
 
28
28
  | Shortcut | Action |
29
29
  |----------|--------|
@@ -33,7 +33,7 @@ HELP_TEXT = """## Kader CLI Commands 📖
33
33
  | `Ctrl+R` | Refresh file tree |
34
34
  | `Ctrl+Q` | Quit |
35
35
 
36
- ### Input Editing 📝
36
+ ### Input Editing
37
37
 
38
38
  | Shortcut | Action |
39
39
  |----------|--------|
@@ -44,7 +44,8 @@ HELP_TEXT = """## Kader CLI Commands 📖
44
44
 
45
45
  ### Tips:
46
46
  - Type any question to chat with the AI
47
- - Use **Tab** to navigate between panels"""
47
+ - Use **Tab** to navigate between panels
48
+ """
48
49
 
49
50
 
50
51
  def get_models_text() -> str:
@@ -52,12 +53,16 @@ def get_models_text() -> str:
52
53
  try:
53
54
  models = OllamaProvider.get_supported_models()
54
55
  if not models:
55
- return "## Available Models 🤖\n\n*No models found. Is Ollama running?*"
56
+ return "## Available Models (^^)\n\n*No models found. Is Ollama running?*"
56
57
 
57
- lines = ["## Available Models 🤖\n", "| Model | Status |", "|-------|--------|"]
58
+ lines = [
59
+ "## Available Models (^^)\n",
60
+ "| Model | Status |",
61
+ "|-------|--------|",
62
+ ]
58
63
  for model in models:
59
- lines.append(f"| {model} | Available |")
64
+ lines.append(f"| {model} | (+) Available |")
60
65
  lines.append(f"\n*Currently using: **{DEFAULT_MODEL}***")
61
66
  return "\n".join(lines)
62
67
  except Exception as e:
63
- return f"## Available Models 🤖\n\n*Error fetching models: {e}*"
68
+ return f"## Available Models (^^)\n\n*Error fetching models: {e}*"
@@ -84,12 +84,12 @@ class InlineSelector(Widget, can_focus=True):
84
84
  def __init__(self, message: str, options: list[str] = None, **kwargs) -> None:
85
85
  super().__init__(**kwargs)
86
86
  self.message = message
87
- self.options = options or [" Yes", " No"]
87
+ self.options = options or ["(+) Yes", "(-) No"]
88
88
 
89
89
  def compose(self) -> ComposeResult:
90
90
  from textual.containers import Horizontal
91
91
 
92
- yield Static(f"🔧 {self.message}", classes="message-text")
92
+ yield Static(f">_ {self.message}", classes="message-text")
93
93
  yield Static(
94
94
  "↑↓ to select • Enter to confirm • Y/N for quick select",
95
95
  classes="prompt-text",
@@ -230,7 +230,7 @@ class ModelSelector(Widget, can_focus=True):
230
230
  self.selected_index = models.index(current_model)
231
231
 
232
232
  def compose(self) -> ComposeResult:
233
- yield Static("🤖 Select Model", classes="title-text")
233
+ yield Static("(^^) Select Model", classes="title-text")
234
234
  yield Static(
235
235
  "↑↓ to navigate • Enter to select • Esc to cancel", classes="prompt-text"
236
236
  )
@@ -245,7 +245,7 @@ class ModelSelector(Widget, can_focus=True):
245
245
  classes += " not-selected"
246
246
  if is_current:
247
247
  classes += " current"
248
- label = f" {model}" if is_selected else f" {model}"
248
+ label = f" >> {model}" if is_selected else f" {model}"
249
249
  if is_current:
250
250
  label += " (current)"
251
251
  yield Static(label, classes=classes, id=f"model-{i}")
@@ -272,7 +272,7 @@ class ModelSelector(Widget, can_focus=True):
272
272
  new_option.remove_class("not-selected")
273
273
  new_option.add_class("selected")
274
274
  new_model = self.models[new_index]
275
- new_label = f" {new_model}"
275
+ new_label = f" >> {new_model}"
276
276
  if new_model == self.current_model:
277
277
  new_label += " (current)"
278
278
  new_option.update(new_label)
@@ -15,7 +15,7 @@ class Message(Static):
15
15
  self.add_class(f"message-{role}")
16
16
 
17
17
  def compose(self) -> ComposeResult:
18
- prefix = "👤 **You:**" if self.role == "user" else "🤖 **Kader:**"
18
+ prefix = "(**) **You:**" if self.role == "user" else "(^^) **Kader:**"
19
19
  yield Markdown(f"{prefix}\n\n{self.content}")
20
20
 
21
21
 
@@ -21,7 +21,7 @@ class LoadingSpinner(Static):
21
21
  }
22
22
  """
23
23
 
24
- SPINNER_FRAMES = ["", "", "⠹", "⠸", "⠼", "⠴", "", "", "", "⠏"]
24
+ SPINNER_FRAMES = ["= ", "== ", "=== ", " ===", " ==", " =", " "]
25
25
 
26
26
  frame_index: reactive[int] = reactive(0)
27
27
  is_spinning: reactive[bool] = reactive(False)
@@ -322,7 +322,7 @@ class BaseAgent:
322
322
  return f"execute {tool_name}"
323
323
 
324
324
  def _confirm_tool_execution(
325
- self, tool_call_dict: dict
325
+ self, tool_call_dict: dict, llm_content: Optional[str] = None
326
326
  ) -> tuple[bool, Optional[str]]:
327
327
  """
328
328
  Ask user for confirmation before executing a tool.
@@ -336,6 +336,9 @@ class BaseAgent:
336
336
  """
337
337
  display_str = self._format_tool_call_for_display(tool_call_dict)
338
338
 
339
+ if llm_content and len(llm_content) > 0:
340
+ display_str = f"{llm_content}\n\n{display_str}"
341
+
339
342
  # Use callback if provided (e.g., for GUI/TUI)
340
343
  if self.tool_confirmation_callback:
341
344
  return self.tool_confirmation_callback(display_str)
@@ -357,7 +360,7 @@ class BaseAgent:
357
360
  print("Please enter 'yes' or 'no'.")
358
361
 
359
362
  async def _aconfirm_tool_execution(
360
- self, tool_call_dict: dict
363
+ self, tool_call_dict: dict, llm_content: Optional[str] = None
361
364
  ) -> tuple[bool, Optional[str]]:
362
365
  """
363
366
  Async version - Ask user for confirmation before executing a tool.
@@ -375,7 +378,9 @@ class BaseAgent:
375
378
  # In production, use asyncio.to_thread or aioconsole
376
379
  import asyncio
377
380
 
378
- return await asyncio.to_thread(self._confirm_tool_execution, tool_call_dict)
381
+ return await asyncio.to_thread(
382
+ self._confirm_tool_execution, tool_call_dict, llm_content
383
+ )
379
384
 
380
385
  def _process_tool_calls(
381
386
  self, response: LLMResponse
@@ -396,7 +401,7 @@ class BaseAgent:
396
401
  # Check for interrupt before tool execution
397
402
  if self.interrupt_before_tool:
398
403
  should_execute, user_input = self._confirm_tool_execution(
399
- tool_call_dict
404
+ tool_call_dict, response.content
400
405
  )
401
406
  if not should_execute:
402
407
  # Return the user's elaboration to be processed
@@ -455,7 +460,7 @@ class BaseAgent:
455
460
  # Check for interrupt before tool execution
456
461
  if self.interrupt_before_tool:
457
462
  should_execute, user_input = await self._aconfirm_tool_execution(
458
- tool_call_dict
463
+ tool_call_dict, response.content
459
464
  )
460
465
  if not should_execute:
461
466
  return (False, user_input)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kader"
3
- version = "0.1.3"
3
+ version = "0.1.5"
4
4
  description = "kader coding agent"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -487,7 +487,7 @@ wheels = [
487
487
 
488
488
  [[package]]
489
489
  name = "kader"
490
- version = "0.1.3"
490
+ version = "0.1.5"
491
491
  source = { editable = "." }
492
492
  dependencies = [
493
493
  { name = "faiss-cpu" },
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
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