kader 0.1.4__py3-none-any.whl → 0.1.6__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
@@ -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
@@ -29,7 +29,6 @@ from kader.tools import get_default_registry
29
29
  from .utils import (
30
30
  DEFAULT_MODEL,
31
31
  HELP_TEXT,
32
- THEME_NAMES,
33
32
  )
34
33
  from .widgets import ConversationView, InlineSelector, LoadingSpinner, ModelSelector
35
34
 
@@ -51,7 +50,6 @@ Type a message below to start chatting, or use one of the commands:
51
50
 
52
51
  - `/help` - Show available commands
53
52
  - `/models` - View available LLM models
54
- - `/theme` - Change the color theme
55
53
  - `/clear` - Clear the conversation
56
54
  - `/save` - Save current session
57
55
  - `/load` - Load a saved session
@@ -66,17 +64,23 @@ MIN_WIDTH = 89
66
64
  MIN_HEIGHT = 29
67
65
 
68
66
 
67
+ class ASCIITree(Tree):
68
+ """A Tree widget that uses no icons."""
69
+
70
+ ICON_NODE = ""
71
+ ICON_NODE_EXPANDED = ""
72
+
73
+
69
74
  class KaderApp(App):
70
75
  """Main Kader CLI application."""
71
76
 
72
77
  TITLE = "Kader CLI"
73
- SUB_TITLE = "Modern Vibe Coding Assistant"
78
+ SUB_TITLE = f"v{get_version('kader')}"
74
79
  CSS_PATH = "app.tcss"
75
80
 
76
81
  BINDINGS = [
77
82
  Binding("ctrl+q", "quit", "Quit"),
78
83
  Binding("ctrl+l", "clear", "Clear"),
79
- Binding("ctrl+t", "cycle_theme", "Theme"),
80
84
  Binding("ctrl+s", "save_session", "Save"),
81
85
  Binding("ctrl+r", "refresh_tree", "Refresh"),
82
86
  Binding("tab", "focus_next", "Next", show=False),
@@ -85,7 +89,6 @@ class KaderApp(App):
85
89
 
86
90
  def __init__(self) -> None:
87
91
  super().__init__()
88
- self._current_theme_index = 0
89
92
  self._is_processing = False
90
93
  self._current_model = DEFAULT_MODEL
91
94
  self._current_session_id: str | None = None
@@ -180,7 +183,7 @@ class KaderApp(App):
180
183
  if event.confirmed:
181
184
  if tool_message:
182
185
  conversation.add_message(tool_message, "assistant")
183
- conversation.add_message(" Executing tool...", "assistant")
186
+ conversation.add_message("(+) Executing tool...", "assistant")
184
187
  # Restart spinner
185
188
  try:
186
189
  spinner = self.query_one(LoadingSpinner)
@@ -188,7 +191,7 @@ class KaderApp(App):
188
191
  except Exception:
189
192
  pass
190
193
  else:
191
- conversation.add_message(" Tool execution skipped.", "assistant")
194
+ conversation.add_message("(-) Tool execution skipped.", "assistant")
192
195
 
193
196
  # Re-enable input
194
197
  prompt_input = self.query_one("#prompt-input", Input)
@@ -210,7 +213,8 @@ class KaderApp(App):
210
213
  models = OllamaProvider.get_supported_models()
211
214
  if not models:
212
215
  conversation.add_message(
213
- "## Models 🤖\n\n*No models found. Is Ollama running?*", "assistant"
216
+ "## Models (^^)\n\n*No models found. Is Ollama running?*",
217
+ "assistant",
214
218
  )
215
219
  return
216
220
 
@@ -228,7 +232,7 @@ class KaderApp(App):
228
232
 
229
233
  except Exception as e:
230
234
  conversation.add_message(
231
- f"## Models 🤖\n\n*Error fetching models: {e}*", "assistant"
235
+ f"## Models (^^)\n\n*Error fetching models: {e}*", "assistant"
232
236
  )
233
237
 
234
238
  def on_model_selector_model_selected(
@@ -248,7 +252,7 @@ class KaderApp(App):
248
252
  self._agent = self._create_agent(self._current_model)
249
253
 
250
254
  conversation.add_message(
251
- f" Model changed from `{old_model}` to `{self._current_model}`",
255
+ f"(+) Model changed from `{old_model}` to `{self._current_model}`",
252
256
  "assistant",
253
257
  )
254
258
 
@@ -285,8 +289,8 @@ class KaderApp(App):
285
289
  with Horizontal(id="main-container"):
286
290
  # Sidebar with directory tree
287
291
  with Vertical(id="sidebar"):
288
- yield Static("📁 Files", id="sidebar-title")
289
- yield DirectoryTree(Path.cwd(), id="directory-tree")
292
+ yield Static("Files", id="sidebar-title")
293
+ yield ASCIITree(str(Path.cwd().name), id="directory-tree")
290
294
 
291
295
  # Main content area
292
296
  with Vertical(id="content-area"):
@@ -316,9 +320,32 @@ class KaderApp(App):
316
320
  # Check initial size
317
321
  self._check_terminal_size()
318
322
 
323
+ # Start background update check
319
324
  # Start background update check
320
325
  threading.Thread(target=self._check_for_updates, daemon=True).start()
321
326
 
327
+ # Initial tree population
328
+ self._refresh_directory_tree()
329
+
330
+ def _populate_tree(self, node, path: Path) -> None:
331
+ """Recursively populate the tree with ASCII symbols."""
332
+ try:
333
+ # Sort: directories first, then files
334
+ items = sorted(
335
+ path.iterdir(), key=lambda p: (not p.is_dir(), p.name.lower())
336
+ )
337
+ for child in items:
338
+ if child.name.startswith((".", "__pycache__")):
339
+ continue
340
+
341
+ if child.is_dir():
342
+ new_node = node.add(f"[+] {child.name}", expand=False)
343
+ self._populate_tree(new_node, child)
344
+ else:
345
+ node.add(f"{child.name}")
346
+ except Exception:
347
+ pass
348
+
322
349
  def _check_for_updates(self) -> None:
323
350
  """Check for package updates in background thread."""
324
351
  try:
@@ -369,7 +396,7 @@ class KaderApp(App):
369
396
  except Exception:
370
397
  if too_small:
371
398
  # Show warning overlay
372
- warning_text = f"""⚠️ Terminal Too Small
399
+ warning_text = f"""<!> Terminal Too Small
373
400
 
374
401
  Current: {width}x{height}
375
402
  Minimum: {MIN_WIDTH}x{MIN_HEIGHT}
@@ -402,12 +429,6 @@ Please resize your terminal."""
402
429
  conversation.add_message(HELP_TEXT, "assistant")
403
430
  elif cmd == "/models":
404
431
  await self._show_model_selector(conversation)
405
- elif cmd == "/theme":
406
- self._cycle_theme()
407
- theme_name = THEME_NAMES[self._current_theme_index]
408
- conversation.add_message(
409
- f"🎨 Theme changed to **{theme_name}**!", "assistant"
410
- )
411
432
  elif cmd == "/clear":
412
433
  conversation.clear_messages()
413
434
  self._agent.memory.clear()
@@ -482,7 +503,7 @@ Please resize your terminal."""
482
503
 
483
504
  except Exception as e:
484
505
  spinner.stop()
485
- error_msg = f" **Error:** {str(e)}\n\nMake sure Ollama is running and the model `{self._current_model}` is available."
506
+ error_msg = f"(-) **Error:** {str(e)}\n\nMake sure Ollama is running and the model `{self._current_model}` is available."
486
507
  conversation.add_message(error_msg, "assistant")
487
508
  self.notify(f"Error: {e}", severity="error")
488
509
 
@@ -491,21 +512,6 @@ Please resize your terminal."""
491
512
  # Auto-refresh directory tree in case agent created/modified files
492
513
  self._refresh_directory_tree()
493
514
 
494
- def _cycle_theme(self) -> None:
495
- """Cycle through available themes."""
496
- # Remove current theme class if it's not dark
497
- current_theme = THEME_NAMES[self._current_theme_index]
498
- if current_theme != "dark":
499
- self.remove_class(f"theme-{current_theme}")
500
-
501
- # Move to next theme
502
- self._current_theme_index = (self._current_theme_index + 1) % len(THEME_NAMES)
503
- new_theme = THEME_NAMES[self._current_theme_index]
504
-
505
- # Apply new theme class (dark is default, no class needed)
506
- if new_theme != "dark":
507
- self.add_class(f"theme-{new_theme}")
508
-
509
515
  def action_clear(self) -> None:
510
516
  """Clear the conversation (Ctrl+L)."""
511
517
  conversation = self.query_one("#conversation-view", ConversationView)
@@ -513,12 +519,6 @@ Please resize your terminal."""
513
519
  self._agent.memory.clear()
514
520
  self.notify("Conversation cleared!", severity="information")
515
521
 
516
- def action_cycle_theme(self) -> None:
517
- """Cycle theme (Ctrl+T)."""
518
- self._cycle_theme()
519
- theme_name = THEME_NAMES[self._current_theme_index]
520
- self.notify(f"Theme: {theme_name}", severity="information")
521
-
522
522
  def action_save_session(self) -> None:
523
523
  """Save session (Ctrl+S)."""
524
524
  conversation = self.query_one("#conversation-view", ConversationView)
@@ -530,10 +530,13 @@ Please resize your terminal."""
530
530
  self.notify("Directory tree refreshed!", severity="information")
531
531
 
532
532
  def _refresh_directory_tree(self) -> None:
533
- """Refresh the directory tree to show new/modified files."""
533
+ """Refresh the directory tree with ASCII symbols."""
534
534
  try:
535
- tree = self.query_one("#directory-tree", DirectoryTree)
536
- tree.reload()
535
+ tree = self.query_one("#directory-tree", ASCIITree)
536
+ tree.clear()
537
+ tree.root.label = str(Path.cwd().name)
538
+ self._populate_tree(tree.root, Path.cwd())
539
+ tree.root.expand()
537
540
  except Exception:
538
541
  pass # Silently ignore if tree not found
539
542
 
@@ -550,12 +553,12 @@ Please resize your terminal."""
550
553
  self._session_manager.save_conversation(self._current_session_id, messages)
551
554
 
552
555
  conversation.add_message(
553
- f" Session saved!\n\n**Session ID:** `{self._current_session_id}`",
556
+ f"(+) Session saved!\n\n**Session ID:** `{self._current_session_id}`",
554
557
  "assistant",
555
558
  )
556
559
  self.notify("Session saved!", severity="information")
557
560
  except Exception as e:
558
- conversation.add_message(f" Error saving session: {e}", "assistant")
561
+ conversation.add_message(f"(-) Error saving session: {e}", "assistant")
559
562
  self.notify(f"Error: {e}", severity="error")
560
563
 
561
564
  def _handle_load_session(
@@ -567,7 +570,7 @@ Please resize your terminal."""
567
570
  session = self._session_manager.get_session(session_id)
568
571
  if not session:
569
572
  conversation.add_message(
570
- f" Session `{session_id}` not found.\n\nUse `/sessions` to see available sessions.",
573
+ f"(-) Session `{session_id}` not found.\n\nUse `/sessions` to see available sessions.",
571
574
  "assistant",
572
575
  )
573
576
  return
@@ -589,12 +592,12 @@ Please resize your terminal."""
589
592
 
590
593
  self._current_session_id = session_id
591
594
  conversation.add_message(
592
- f" Session `{session_id}` loaded with {len(messages)} messages.",
595
+ f"(+) Session `{session_id}` loaded with {len(messages)} messages.",
593
596
  "assistant",
594
597
  )
595
598
  self.notify("Session loaded!", severity="information")
596
599
  except Exception as e:
597
- conversation.add_message(f" Error loading session: {e}", "assistant")
600
+ conversation.add_message(f"(-) Error loading session: {e}", "assistant")
598
601
  self.notify(f"Error: {e}", severity="error")
599
602
 
600
603
  def _handle_list_sessions(self, conversation: ConversationView) -> None:
@@ -604,13 +607,13 @@ Please resize your terminal."""
604
607
 
605
608
  if not sessions:
606
609
  conversation.add_message(
607
- "📭 No saved sessions found.\n\nUse `/save` to save the current session.",
610
+ "[ ] No saved sessions found.\n\nUse `/save` to save the current session.",
608
611
  "assistant",
609
612
  )
610
613
  return
611
614
 
612
615
  lines = [
613
- "## Saved Sessions 📂\n",
616
+ "## Saved Sessions [=]\n",
614
617
  "| Session ID | Created | Updated |",
615
618
  "|------------|---------|---------|",
616
619
  ]
@@ -635,7 +638,7 @@ Please resize your terminal."""
635
638
  model = self._agent.provider.model
636
639
 
637
640
  lines = [
638
- "## Usage Costs 💰\n",
641
+ "## Usage Costs ($)\n",
639
642
  f"**Model:** `{model}`\n",
640
643
  "### Cost Breakdown",
641
644
  "| Type | Amount |",
@@ -654,12 +657,12 @@ Please resize your terminal."""
654
657
 
655
658
  if cost.total_cost == 0.0:
656
659
  lines.append(
657
- "\n> 💡 *Note: Ollama runs locally, so there are no API costs.*"
660
+ "\n> (!) *Note: Ollama runs locally, so there are no API costs.*"
658
661
  )
659
662
 
660
663
  conversation.add_message("\n".join(lines), "assistant")
661
664
  except Exception as e:
662
- conversation.add_message(f" Error getting costs: {e}", "assistant")
665
+ conversation.add_message(f"(-) Error getting costs: {e}", "assistant")
663
666
  self.notify(f"Error: {e}", severity="error")
664
667
 
665
668
 
cli/app.tcss CHANGED
@@ -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;
@@ -275,391 +274,16 @@ ScrollbarSlider:hover {
275
274
  color: $secondary;
276
275
  }
277
276
 
278
- /* ===== Theme Variants ===== */
279
-
280
- /* Ocean Theme */
281
- .theme-ocean Screen {
282
- background: #0f172a;
283
- }
284
-
285
- .theme-ocean Header {
286
- background: #3b82f6;
287
- color: #f1f5f9;
288
- }
289
-
290
- .theme-ocean Footer {
291
- background: #1e293b;
292
- color: #94a3b8;
293
- }
294
-
295
- .theme-ocean FooterKey > .footer-key--key {
296
- background: #3b82f6;
297
- color: #f1f5f9;
298
- }
299
-
300
- .theme-ocean #sidebar {
301
- background: #1e293b;
302
- border-right: thick #3b82f6;
303
- }
304
-
305
- .theme-ocean #sidebar-title {
306
- background: #3b82f6 20%;
307
- color: #f1f5f9;
308
- }
309
-
310
- .theme-ocean DirectoryTree > .directory-tree--folder {
311
- color: #06b6d4;
312
- }
313
-
314
- .theme-ocean DirectoryTree > .directory-tree--file {
315
- color: #f1f5f9;
316
- }
317
-
318
- .theme-ocean DirectoryTree > .directory-tree--extension {
319
- color: #94a3b8;
320
- }
321
-
322
- .theme-ocean DirectoryTree:focus > .directory-tree--cursor {
323
- background: #3b82f6 40%;
324
- }
325
-
326
- .theme-ocean #conversation {
327
- background: #0f172a;
328
- border-bottom: thick #1e293b;
329
- }
330
-
331
- .theme-ocean #input-container {
332
- background: #1e293b;
333
- border-top: thick #3b82f6;
334
- }
335
-
336
- .theme-ocean #prompt-input {
337
- background: #0f172a;
338
- border: round #3b82f6;
339
- }
340
-
341
- .theme-ocean #prompt-input:focus {
342
- border: round #06b6d4;
343
- }
344
-
345
- .theme-ocean Input.-valid {
346
- border: round #10b981;
347
- }
348
-
349
- .theme-ocean Input > .input--placeholder {
350
- color: #94a3b8;
351
- text-style: italic;
352
- }
353
-
354
- .theme-ocean LoadingSpinner {
355
- background: #1e293b;
356
- border-left: thick #f59e0b;
357
- }
358
-
359
- .theme-ocean #command-hints {
360
- background: #1e293b;
361
- }
362
-
363
- .theme-ocean .command-hint {
364
- color: #94a3b8;
365
- }
366
-
367
- .theme-ocean .command-key {
368
- color: #3b82f6;
369
- }
370
-
371
- .theme-ocean MarkdownH1 {
372
- color: #3b82f6;
373
- }
374
-
375
- .theme-ocean MarkdownH2 {
376
- color: #06b6d4;
377
- }
378
-
379
- .theme-ocean MarkdownH3 {
380
- color: #10b981;
381
- }
382
-
383
- .theme-ocean MarkdownFence {
384
- background: #1e293b;
385
- border: round #94a3b8;
386
- }
387
-
388
- .theme-ocean MarkdownBlockQuote {
389
- background: #3b82f6 10%;
390
- border-left: thick #3b82f6;
391
- }
392
-
393
- .theme-ocean MarkdownTH {
394
- background: #3b82f6 20%;
395
- }
396
-
397
- .theme-ocean Scrollbar {
398
- background: #1e293b;
399
- }
400
-
401
- .theme-ocean ScrollbarSlider {
402
- color: #3b82f6;
403
- }
404
-
405
- .theme-ocean ScrollbarSlider:hover {
406
- color: #06b6d4;
407
- }
408
-
409
- /* Forest Theme */
410
- .theme-forest Screen {
411
- background: #0d1f0d;
412
- }
413
-
414
- .theme-forest Header {
415
- background: #16a34a;
416
- color: #dcfce7;
417
- }
418
-
419
- .theme-forest Footer {
420
- background: #0f2f0f;
421
- color: #6ee7b7;
422
- }
423
-
424
- .theme-forest FooterKey > .footer-key--key {
425
- background: #16a34a;
426
- color: #dcfce7;
427
- }
428
-
429
- .theme-forest #sidebar {
430
- background: #0f2f0f;
431
- border-right: thick #16a34a;
432
- }
433
-
434
- .theme-forest #sidebar-title {
435
- background: #16a34a 20%;
436
- color: #dcfce7;
437
- }
438
-
439
- .theme-forest DirectoryTree > .directory-tree--folder {
440
- color: #0891b2;
441
- }
442
-
443
- .theme-forest DirectoryTree > .directory-tree--file {
444
- color: #dcfce7;
445
- }
446
-
447
- .theme-forest DirectoryTree > .directory-tree--extension {
448
- color: #6ee7b7;
449
- }
450
-
451
- .theme-forest DirectoryTree:focus > .directory-tree--cursor {
452
- background: #16a34a 40%;
453
- }
454
-
455
- .theme-forest #conversation {
456
- background: #0d1f0d;
457
- border-bottom: thick #0f2f0f;
458
- }
459
-
460
- .theme-forest #input-container {
461
- background: #0f2f0f;
462
- border-top: thick #16a34a;
463
- }
464
-
465
- .theme-forest #prompt-input {
466
- background: #0d1f0d;
467
- border: round #16a34a;
468
- }
469
-
470
- .theme-forest #prompt-input:focus {
471
- border: round #0891b2;
472
- }
473
-
474
- .theme-forest Input.-valid {
475
- border: round #22c55e;
476
- }
477
-
478
- .theme-forest Input > .input--placeholder {
479
- color: #6ee7b7;
480
- text-style: italic;
481
- }
482
-
483
- .theme-forest LoadingSpinner {
484
- background: #0f2f0f;
485
- border-left: thick #eab308;
486
- }
487
-
488
- .theme-forest #command-hints {
489
- background: #0f2f0f;
490
- }
491
-
492
- .theme-forest .command-hint {
493
- color: #6ee7b7;
494
- }
495
-
496
- .theme-forest .command-key {
497
- color: #16a34a;
498
- }
499
-
500
- .theme-forest MarkdownH1 {
501
- color: #16a34a;
502
- }
503
-
504
- .theme-forest MarkdownH2 {
505
- color: #0891b2;
506
- }
507
-
508
- .theme-forest MarkdownH3 {
509
- color: #22c55e;
510
- }
511
-
512
- .theme-forest MarkdownFence {
513
- background: #0f2f0f;
514
- border: round #6ee7b7;
515
- }
516
-
517
- .theme-forest MarkdownBlockQuote {
518
- background: #16a34a 10%;
519
- border-left: thick #16a34a;
520
- }
521
-
522
- .theme-forest MarkdownTH {
523
- background: #16a34a 20%;
524
- }
525
-
526
- .theme-forest Scrollbar {
527
- background: #0f2f0f;
528
- }
529
-
530
- .theme-forest ScrollbarSlider {
531
- color: #16a34a;
532
- }
533
-
534
- .theme-forest ScrollbarSlider:hover {
535
- color: #0891b2;
536
- }
537
-
538
- /* Sunset Theme */
539
- .theme-sunset Screen {
540
- background: #1f1315;
541
- }
542
-
543
- .theme-sunset Header {
544
- background: #dc2626;
545
- color: #fef2f2;
546
- }
547
-
548
- .theme-sunset Footer {
549
- background: #2f171a;
550
- color: #fecaca;
551
- }
552
-
553
- .theme-sunset FooterKey > .footer-key--key {
554
- background: #dc2626;
555
- color: #fef2f2;
556
- }
557
-
558
- .theme-sunset #sidebar {
559
- background: #2f171a;
560
- border-right: thick #dc2626;
561
- }
562
-
563
- .theme-sunset #sidebar-title {
564
- background: #dc2626 20%;
565
- color: #fef2f2;
566
- }
567
-
568
- .theme-sunset DirectoryTree > .directory-tree--folder {
569
- color: #ea580c;
570
- }
571
-
572
- .theme-sunset DirectoryTree > .directory-tree--file {
573
- color: #fef2f2;
574
- }
575
-
576
- .theme-sunset DirectoryTree > .directory-tree--extension {
577
- color: #fecaca;
578
- }
579
-
580
- .theme-sunset DirectoryTree:focus > .directory-tree--cursor {
581
- background: #dc2626 40%;
582
- }
583
-
584
- .theme-sunset #conversation {
585
- background: #1f1315;
586
- border-bottom: thick #2f171a;
587
- }
588
-
589
- .theme-sunset #input-container {
590
- background: #2f171a;
591
- border-top: thick #dc2626;
592
- }
593
-
594
- .theme-sunset #prompt-input {
595
- background: #1f1315;
596
- border: round #dc2626;
597
- }
598
-
599
- .theme-sunset #prompt-input:focus {
600
- border: round #ea580c;
601
- }
602
-
603
- .theme-sunset Input.-valid {
604
- border: round #16a34a;
605
- }
606
-
607
- .theme-sunset Input > .input--placeholder {
608
- color: #fecaca;
609
- text-style: italic;
610
- }
611
-
612
- .theme-sunset LoadingSpinner {
613
- background: #2f171a;
614
- border-left: thick #eab308;
615
- }
616
-
617
- .theme-sunset #command-hints {
618
- background: #2f171a;
619
- }
620
-
621
- .theme-sunset .command-hint {
622
- color: #fecaca;
623
- }
624
-
625
- .theme-sunset .command-key {
626
- color: #dc2626;
627
- }
628
-
629
- .theme-sunset MarkdownH1 {
630
- color: #dc2626;
631
- }
632
-
633
- .theme-sunset MarkdownH2 {
634
- color: #ea580c;
635
- }
636
-
637
- .theme-sunset MarkdownH3 {
638
- color: #16a34a;
639
- }
640
-
641
- .theme-sunset MarkdownFence {
642
- background: #2f171a;
643
- border: round #fecaca;
644
- }
645
-
646
- .theme-sunset MarkdownBlockQuote {
647
- background: #dc2626 10%;
648
- border-left: thick #dc2626;
649
- }
277
+ /* ===== Scrollbars ===== */
650
278
 
651
- .theme-sunset MarkdownTH {
652
- background: #dc2626 20%;
279
+ Scrollbar {
280
+ background: $surface;
653
281
  }
654
282
 
655
- .theme-sunset Scrollbar {
656
- background: #2f171a;
283
+ ScrollbarSlider {
284
+ color: $primary;
657
285
  }
658
286
 
659
- .theme-sunset ScrollbarSlider {
660
- color: #dc2626;
287
+ ScrollbarSlider:hover {
288
+ color: $secondary;
661
289
  }
662
-
663
- .theme-sunset ScrollbarSlider:hover {
664
- color: #ea580c;
665
- }
cli/utils.py CHANGED
@@ -2,18 +2,14 @@
2
2
 
3
3
  from kader.providers import OllamaProvider
4
4
 
5
- # Theme names for cycling
6
- THEME_NAMES = ["dark", "ocean", "forest", "sunset"]
7
-
8
5
  # Default model
9
6
  DEFAULT_MODEL = "qwen3-coder:480b-cloud"
10
7
 
11
- HELP_TEXT = """## Kader CLI Commands 📖
8
+ HELP_TEXT = """## Kader CLI Commands
12
9
 
13
10
  | Command | Description |
14
11
  |---------|-------------|
15
12
  | `/models` | Show available LLM models |
16
- | `/theme` | Cycle through color themes |
17
13
  | `/help` | Show this help message |
18
14
  | `/clear` | Clear the conversation |
19
15
  | `/save` | Save current session |
@@ -23,17 +19,16 @@ HELP_TEXT = """## Kader CLI Commands 📖
23
19
  | `/refresh` | Refresh file tree |
24
20
  | `/exit` | Exit the CLI |
25
21
 
26
- ### Keyboard Shortcuts ⌨️
22
+ ### Keyboard Shortcuts
27
23
 
28
24
  | Shortcut | Action |
29
25
  |----------|--------|
30
26
  | `Ctrl+L` | Clear conversation |
31
- | `Ctrl+T` | Cycle theme |
32
27
  | `Ctrl+S` | Save session |
33
28
  | `Ctrl+R` | Refresh file tree |
34
29
  | `Ctrl+Q` | Quit |
35
30
 
36
- ### Input Editing 📝
31
+ ### Input Editing
37
32
 
38
33
  | Shortcut | Action |
39
34
  |----------|--------|
@@ -44,7 +39,8 @@ HELP_TEXT = """## Kader CLI Commands 📖
44
39
 
45
40
  ### Tips:
46
41
  - Type any question to chat with the AI
47
- - Use **Tab** to navigate between panels"""
42
+ - Use **Tab** to navigate between panels
43
+ """
48
44
 
49
45
 
50
46
  def get_models_text() -> str:
@@ -52,12 +48,16 @@ def get_models_text() -> str:
52
48
  try:
53
49
  models = OllamaProvider.get_supported_models()
54
50
  if not models:
55
- return "## Available Models 🤖\n\n*No models found. Is Ollama running?*"
51
+ return "## Available Models (^^)\n\n*No models found. Is Ollama running?*"
56
52
 
57
- lines = ["## Available Models 🤖\n", "| Model | Status |", "|-------|--------|"]
53
+ lines = [
54
+ "## Available Models (^^)\n",
55
+ "| Model | Status |",
56
+ "|-------|--------|",
57
+ ]
58
58
  for model in models:
59
- lines.append(f"| {model} | Available |")
59
+ lines.append(f"| {model} | (+) Available |")
60
60
  lines.append(f"\n*Currently using: **{DEFAULT_MODEL}***")
61
61
  return "\n".join(lines)
62
62
  except Exception as e:
63
- return f"## Available Models 🤖\n\n*Error fetching models: {e}*"
63
+ 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
 
cli/widgets/loading.py CHANGED
@@ -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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kader
3
- Version: 0.1.4
3
+ Version: 0.1.6
4
4
  Summary: kader coding agent
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: faiss-cpu>=1.9.0
@@ -1,13 +1,13 @@
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=xRh92l8KA4TbIr-g981_WMyKa0VeSGZ3Eofsvfr2nTE,24987
5
- cli/app.tcss,sha256=fImM5JxlTCWvD4i3OmpJpBOEE8HGo87Q9E8rKbOKEGU,10416
6
- cli/utils.py,sha256=GkyVTkIjNsnEEYCVD2l5snQybReXknfg5RihKXWuK4w,1846
4
+ cli/app.py,sha256=Gh5QgDwBkhQtM6dJuIY8erVCuMnDOe5pBrlyLx9ihiA,24809
5
+ cli/app.tcss,sha256=2SeoN-1FrvYu9ERIPxSyGzz9evtb_-YjT4j0VOJsmzg,4207
6
+ cli/utils.py,sha256=BcvQJWGyIC3wm5yT18Py7F7dcyT-h0W6F7AZwHovbS8,1729
7
7
  cli/widgets/__init__.py,sha256=1vj31CrJyxZROLthkKr79i_GbNyj8g3q60ZQPbJHK5k,300
8
- cli/widgets/confirmation.py,sha256=s6h3ZY98xHeeF3G-OtHYYw-xPlAP6K7i73jo_ftOMQc,9400
9
- cli/widgets/conversation.py,sha256=vqI3eq7qQrn4UD8fB8rBr33kIesrLpNVhNIp3MdGeOM,1519
10
- cli/widgets/loading.py,sha256=xME_riHCJ0smN9Fo-usa45WEfGLmkdcf9ntMqnZAztA,1703
8
+ cli/widgets/confirmation.py,sha256=7hXqGyhW5V9fmtjgiWR4z2fJWmKxWhUH9RigqDrTKp4,9396
9
+ cli/widgets/conversation.py,sha256=tW2Euox239B9avkGqmRnVwP1RMLd3oGFpjRrAyS4rn0,1519
10
+ cli/widgets/loading.py,sha256=wlhQ47ppSj8vCEqjrbG2mk1yKnfo8dWC5429Z2q1-0g,1689
11
11
  kader/__init__.py,sha256=QXb0aQvySAiNmhP2kNMNtuuzG_eSUgRVcTRSafko1r0,602
12
12
  kader/config.py,sha256=B1s1PNgZ5SrFEJU5TtJG-ZAc7Umff4cwyHR7mfQgeLA,4261
13
13
  kader/agent/__init__.py,sha256=UJzUw9NIzggCrhIBHC6nJnfzkhCjCZnIzmD6uUn2SNA,159
@@ -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.4.dist-info/METADATA,sha256=qfivfbam-Ve7_OJW9Mt4nLhCXiRwfm_8kWFyGK-lVSw,9521
43
- kader-0.1.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
44
- kader-0.1.4.dist-info/entry_points.txt,sha256=TK0VOtrfDFqZ8JQfxpuAHHvDLHyoiafUjS-VOixl02c,39
45
- kader-0.1.4.dist-info/RECORD,,
42
+ kader-0.1.6.dist-info/METADATA,sha256=u3PFuX7e__OMAAhf2UGjxTY-BF90AvVQQFM47jw0EfI,9521
43
+ kader-0.1.6.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
44
+ kader-0.1.6.dist-info/entry_points.txt,sha256=TK0VOtrfDFqZ8JQfxpuAHHvDLHyoiafUjS-VOixl02c,39
45
+ kader-0.1.6.dist-info/RECORD,,
File without changes