kader 0.1.4__tar.gz → 0.1.6__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.
- {kader-0.1.4 → kader-0.1.6}/PKG-INFO +1 -1
- {kader-0.1.4 → kader-0.1.6}/cli/app.py +58 -55
- kader-0.1.6/cli/app.tcss +289 -0
- {kader-0.1.4 → kader-0.1.6}/cli/utils.py +13 -13
- {kader-0.1.4 → kader-0.1.6}/cli/widgets/confirmation.py +5 -5
- {kader-0.1.4 → kader-0.1.6}/cli/widgets/conversation.py +1 -1
- {kader-0.1.4 → kader-0.1.6}/cli/widgets/loading.py +1 -1
- {kader-0.1.4 → kader-0.1.6}/pyproject.toml +1 -1
- {kader-0.1.4 → kader-0.1.6}/uv.lock +1 -1
- kader-0.1.4/cli/app.tcss +0 -665
- {kader-0.1.4 → kader-0.1.6}/.github/workflows/ci.yml +0 -0
- {kader-0.1.4 → kader-0.1.6}/.github/workflows/release.yml +0 -0
- {kader-0.1.4 → kader-0.1.6}/.gitignore +0 -0
- {kader-0.1.4 → kader-0.1.6}/.python-version +0 -0
- {kader-0.1.4 → kader-0.1.6}/.qwen/QWEN.md +0 -0
- {kader-0.1.4 → kader-0.1.6}/.qwen/agents/technical-writer.md +0 -0
- {kader-0.1.4 → kader-0.1.6}/.qwen/agents/test-automation-specialist.md +0 -0
- {kader-0.1.4 → kader-0.1.6}/README.md +0 -0
- {kader-0.1.4 → kader-0.1.6}/cli/README.md +0 -0
- {kader-0.1.4 → kader-0.1.6}/cli/__init__.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/cli/__main__.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/cli/widgets/__init__.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/examples/.gitignore +0 -0
- {kader-0.1.4 → kader-0.1.6}/examples/README.md +0 -0
- {kader-0.1.4 → kader-0.1.6}/examples/memory_example.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/examples/ollama_example.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/examples/planning_agent_example.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/examples/python_developer/main.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/examples/python_developer/template.yaml +0 -0
- {kader-0.1.4 → kader-0.1.6}/examples/react_agent_example.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/examples/simple_agent.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/examples/todo_agent/main.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/examples/tools_example.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/__init__.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/agent/__init__.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/agent/agents.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/agent/base.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/agent/logger.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/config.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/memory/__init__.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/memory/conversation.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/memory/session.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/memory/state.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/memory/types.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/prompts/__init__.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/prompts/agent_prompts.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/prompts/base.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/prompts/templates/planning_agent.j2 +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/prompts/templates/react_agent.j2 +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/providers/__init__.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/providers/base.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/providers/mock.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/providers/ollama.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/tools/README.md +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/tools/__init__.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/tools/base.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/tools/exec_commands.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/tools/filesys.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/tools/filesystem.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/tools/protocol.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/tools/rag.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/tools/todo.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/tools/utils.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/kader/tools/web.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/tests/conftest.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/tests/providers/test_mock.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/tests/providers/test_ollama.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/tests/providers/test_providers_base.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/tests/test_agent_logger.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/tests/test_agent_logger_integration.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/tests/test_base_agent.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/tests/test_file_memory.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/tests/test_todo_tool.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/tests/tools/test_exec_commands.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/tests/tools/test_filesys_tools.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/tests/tools/test_filesystem_tools.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/tests/tools/test_rag.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/tests/tools/test_tools_base.py +0 -0
- {kader-0.1.4 → kader-0.1.6}/tests/tools/test_web.py +0 -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
|
|
@@ -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 = "
|
|
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("
|
|
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("
|
|
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
|
|
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
|
|
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"
|
|
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("
|
|
289
|
-
yield
|
|
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"""
|
|
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"
|
|
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
|
|
533
|
+
"""Refresh the directory tree with ASCII symbols."""
|
|
534
534
|
try:
|
|
535
|
-
tree = self.query_one("#directory-tree",
|
|
536
|
-
tree.
|
|
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"
|
|
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"
|
|
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"
|
|
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"
|
|
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"
|
|
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
|
-
"
|
|
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
|
|
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
|
|
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>
|
|
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"
|
|
665
|
+
conversation.add_message(f"(-) Error getting costs: {e}", "assistant")
|
|
663
666
|
self.notify(f"Error: {e}", severity="error")
|
|
664
667
|
|
|
665
668
|
|
kader-0.1.6/cli/app.tcss
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/* Kader CLI - Modern Terminal UI Stylesheet */
|
|
2
|
+
|
|
3
|
+
/* ===== Color Themes ===== */
|
|
4
|
+
|
|
5
|
+
/* Default Dark Theme */
|
|
6
|
+
$primary: #7c3aed;
|
|
7
|
+
$secondary: #06b6d4;
|
|
8
|
+
$success: #10b981;
|
|
9
|
+
$warning: #f59e0b;
|
|
10
|
+
$error: #ef4444;
|
|
11
|
+
$surface: #1e1e2e;
|
|
12
|
+
$background: #11111b;
|
|
13
|
+
$text: #cdd6f4;
|
|
14
|
+
$text-muted: #6c7086;
|
|
15
|
+
|
|
16
|
+
/* ===== Root App ===== */
|
|
17
|
+
|
|
18
|
+
Screen {
|
|
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;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* ===== Header ===== */
|
|
40
|
+
|
|
41
|
+
Header {
|
|
42
|
+
background: $primary;
|
|
43
|
+
color: $text;
|
|
44
|
+
text-style: bold;
|
|
45
|
+
height: 1;
|
|
46
|
+
dock: top;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* ===== Footer ===== */
|
|
50
|
+
|
|
51
|
+
Footer {
|
|
52
|
+
background: $surface;
|
|
53
|
+
color: $text-muted;
|
|
54
|
+
height: 1;
|
|
55
|
+
dock: bottom;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
FooterKey {
|
|
59
|
+
background: transparent;
|
|
60
|
+
color: $text-muted;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
FooterKey > .footer-key--key {
|
|
64
|
+
background: $primary;
|
|
65
|
+
color: $text;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* ===== Main Layout ===== */
|
|
69
|
+
|
|
70
|
+
#main-container {
|
|
71
|
+
layout: horizontal;
|
|
72
|
+
height: 1fr;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* ===== Sidebar (Directory Tree) ===== */
|
|
76
|
+
|
|
77
|
+
#sidebar {
|
|
78
|
+
width: 22;
|
|
79
|
+
min-width: 18;
|
|
80
|
+
max-width: 35;
|
|
81
|
+
background: $surface;
|
|
82
|
+
border-right: thick $primary;
|
|
83
|
+
padding: 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
#sidebar-title {
|
|
87
|
+
background: $primary 20%;
|
|
88
|
+
color: $text;
|
|
89
|
+
text-style: bold;
|
|
90
|
+
padding: 1;
|
|
91
|
+
text-align: center;
|
|
92
|
+
height: 3;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
DirectoryTree {
|
|
96
|
+
background: transparent;
|
|
97
|
+
padding: 0 1;
|
|
98
|
+
scrollbar-size: 1 1;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
DirectoryTree > .directory-tree--folder {
|
|
102
|
+
color: $secondary;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
DirectoryTree > .directory-tree--file {
|
|
106
|
+
color: $text;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
DirectoryTree > .directory-tree--extension {
|
|
110
|
+
color: $text-muted;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
DirectoryTree:focus > .directory-tree--cursor {
|
|
114
|
+
background: $primary 40%;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* ===== Content Area ===== */
|
|
118
|
+
|
|
119
|
+
#content-area {
|
|
120
|
+
width: 1fr;
|
|
121
|
+
layout: vertical;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* ===== Conversation ===== */
|
|
125
|
+
|
|
126
|
+
#conversation {
|
|
127
|
+
height: 1fr;
|
|
128
|
+
background: $background;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
ConversationView {
|
|
132
|
+
scrollbar-size: 1 1;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* ===== Welcome Message ===== */
|
|
136
|
+
|
|
137
|
+
#welcome {
|
|
138
|
+
padding: 2 4;
|
|
139
|
+
text-align: center;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
#welcome Markdown {
|
|
143
|
+
text-align: center;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* ===== Input Area ===== */
|
|
147
|
+
|
|
148
|
+
#input-container {
|
|
149
|
+
height: auto;
|
|
150
|
+
min-height: 4;
|
|
151
|
+
max-height: 7;
|
|
152
|
+
background: $background;
|
|
153
|
+
padding: 1;
|
|
154
|
+
margin-bottom: 1;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
#prompt-input {
|
|
158
|
+
background: transparent;
|
|
159
|
+
border: round $primary;
|
|
160
|
+
padding: 0 1;
|
|
161
|
+
height: 3;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
#prompt-input:focus {
|
|
165
|
+
border: round $secondary;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
Input.-valid {
|
|
169
|
+
border: round $success;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
Input > .input--placeholder {
|
|
173
|
+
color: $text-muted;
|
|
174
|
+
text-style: italic;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/* ===== Loading Spinner ===== */
|
|
178
|
+
|
|
179
|
+
LoadingSpinner {
|
|
180
|
+
background: $surface;
|
|
181
|
+
margin: 0 2;
|
|
182
|
+
padding: 1;
|
|
183
|
+
border-left: thick $warning;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* ===== Command Hints in Footer ===== */
|
|
187
|
+
|
|
188
|
+
#command-hints {
|
|
189
|
+
dock: bottom;
|
|
190
|
+
height: 1;
|
|
191
|
+
background: $surface;
|
|
192
|
+
padding: 0 1;
|
|
193
|
+
text-align: center;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.command-hint {
|
|
197
|
+
margin: 0 1;
|
|
198
|
+
color: $text-muted;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.command-key {
|
|
202
|
+
color: $primary;
|
|
203
|
+
text-style: bold;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* ===== Markdown Styling ===== */
|
|
207
|
+
|
|
208
|
+
Markdown {
|
|
209
|
+
margin: 0;
|
|
210
|
+
padding: 0;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
MarkdownH1 {
|
|
214
|
+
color: $primary;
|
|
215
|
+
text-style: bold underline;
|
|
216
|
+
margin: 1 0;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
MarkdownH2 {
|
|
220
|
+
color: $secondary;
|
|
221
|
+
text-style: bold;
|
|
222
|
+
margin: 1 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
MarkdownH3 {
|
|
226
|
+
color: $success;
|
|
227
|
+
text-style: bold;
|
|
228
|
+
margin: 1 0;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
MarkdownFence {
|
|
232
|
+
background: $surface;
|
|
233
|
+
border: round #6c7086;
|
|
234
|
+
margin: 1 0;
|
|
235
|
+
padding: 1;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
MarkdownBlockQuote {
|
|
239
|
+
background: $primary 10%;
|
|
240
|
+
border-left: thick $primary;
|
|
241
|
+
padding: 0 1;
|
|
242
|
+
margin: 1 0;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
MarkdownBulletList {
|
|
246
|
+
margin: 0;
|
|
247
|
+
padding-left: 2;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
MarkdownTable {
|
|
251
|
+
margin: 1 0;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
MarkdownTH {
|
|
255
|
+
background: $primary 20%;
|
|
256
|
+
text-style: bold;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
MarkdownTD {
|
|
260
|
+
padding: 0 1;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/* ===== Scrollbars ===== */
|
|
264
|
+
|
|
265
|
+
Scrollbar {
|
|
266
|
+
background: $surface;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
ScrollbarSlider {
|
|
270
|
+
color: $primary;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
ScrollbarSlider:hover {
|
|
274
|
+
color: $secondary;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/* ===== Scrollbars ===== */
|
|
278
|
+
|
|
279
|
+
Scrollbar {
|
|
280
|
+
background: $surface;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
ScrollbarSlider {
|
|
284
|
+
color: $primary;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
ScrollbarSlider:hover {
|
|
288
|
+
color: $secondary;
|
|
289
|
+
}
|
|
@@ -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
|
|
51
|
+
return "## Available Models (^^)\n\n*No models found. Is Ollama running?*"
|
|
56
52
|
|
|
57
|
-
lines = [
|
|
53
|
+
lines = [
|
|
54
|
+
"## Available Models (^^)\n",
|
|
55
|
+
"| Model | Status |",
|
|
56
|
+
"|-------|--------|",
|
|
57
|
+
]
|
|
58
58
|
for model in models:
|
|
59
|
-
lines.append(f"| {model} |
|
|
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
|
|
63
|
+
return f"## Available Models (^^)\n\n*Error fetching models: {e}*"
|