kader 2.7.0__tar.gz → 2.7.2__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 (159) hide show
  1. kader-2.7.2/.github/workflows/pages.yml +58 -0
  2. {kader-2.7.0 → kader-2.7.2}/.gitignore +4 -1
  3. {kader-2.7.0 → kader-2.7.2}/PKG-INFO +2 -1
  4. {kader-2.7.0 → kader-2.7.2}/cli/app.py +165 -45
  5. kader-2.7.2/docs/assets/imgs/kader-cli.png +0 -0
  6. kader-2.7.2/docs/cli/index.md +183 -0
  7. kader-2.7.2/docs/configuration.md +107 -0
  8. kader-2.7.2/docs/core-framework/agents.md +128 -0
  9. kader-2.7.2/docs/core-framework/index.md +81 -0
  10. kader-2.7.2/docs/core-framework/memory.md +179 -0
  11. kader-2.7.2/docs/core-framework/providers.md +176 -0
  12. kader-2.7.2/docs/core-framework/tools.md +268 -0
  13. kader-2.7.2/docs/guide.md +140 -0
  14. kader-2.7.2/docs/index.md +64 -0
  15. kader-2.7.2/kader/tools/exec_commands.py +532 -0
  16. kader-2.7.2/mkdocs.yml +65 -0
  17. {kader-2.7.0 → kader-2.7.2}/pyproject.toml +6 -1
  18. {kader-2.7.0 → kader-2.7.2}/tests/tools/test_exec_commands.py +14 -12
  19. {kader-2.7.0 → kader-2.7.2}/uv.lock +244 -1
  20. kader-2.7.0/kader/tools/exec_commands.py +0 -307
  21. {kader-2.7.0 → kader-2.7.2}/.github/workflows/ci.yml +0 -0
  22. {kader-2.7.0 → kader-2.7.2}/.github/workflows/release.yml +0 -0
  23. {kader-2.7.0 → kader-2.7.2}/.kader/KADER.md +0 -0
  24. {kader-2.7.0 → kader-2.7.2}/.kader/commands/lint-test/CONTENT.md +0 -0
  25. {kader-2.7.0 → kader-2.7.2}/.kader/skills/contributing-to-kader/SKILL.md +0 -0
  26. {kader-2.7.0 → kader-2.7.2}/.kader/skills/contributing-to-kader/assets/contributor_checklist.md +0 -0
  27. {kader-2.7.0 → kader-2.7.2}/.kader/skills/contributing-to-kader/references/kader_agent_instructions.md +0 -0
  28. {kader-2.7.0 → kader-2.7.2}/.kader/skills/contributing-to-kader/scripts/dev_helper.py +0 -0
  29. {kader-2.7.0 → kader-2.7.2}/.opencode/skills/contributing-to-kader/SKILL.md +0 -0
  30. {kader-2.7.0 → kader-2.7.2}/.opencode/skills/contributing-to-kader/assets/contributor_checklist.md +0 -0
  31. {kader-2.7.0 → kader-2.7.2}/.opencode/skills/contributing-to-kader/references/kader_agent_instructions.md +0 -0
  32. {kader-2.7.0 → kader-2.7.2}/.opencode/skills/contributing-to-kader/scripts/dev_helper.py +0 -0
  33. {kader-2.7.0 → kader-2.7.2}/.python-version +0 -0
  34. {kader-2.7.0 → kader-2.7.2}/AGENTS.md +0 -0
  35. {kader-2.7.0 → kader-2.7.2}/CONTRIBUTING.md +0 -0
  36. {kader-2.7.0 → kader-2.7.2}/LICENSE +0 -0
  37. {kader-2.7.0 → kader-2.7.2}/README.md +0 -0
  38. {kader-2.7.0 → kader-2.7.2}/assets/architecture/cli_integration.mmd +0 -0
  39. {kader-2.7.0 → kader-2.7.2}/assets/architecture/cli_message_flow.mmd +0 -0
  40. {kader-2.7.0 → kader-2.7.2}/assets/architecture/cli_tool_confirmation.mmd +0 -0
  41. {kader-2.7.0 → kader-2.7.2}/assets/architecture/memory_flow.mmd +0 -0
  42. {kader-2.7.0 → kader-2.7.2}/assets/architecture/planner_executor_sequence.mmd +0 -0
  43. {kader-2.7.0 → kader-2.7.2}/assets/architecture/planner_executor_workflow.mmd +0 -0
  44. {kader-2.7.0 → kader-2.7.2}/assets/design/v2/code.html +0 -0
  45. {kader-2.7.0 → kader-2.7.2}/assets/design/v2/code1.html +0 -0
  46. {kader-2.7.0 → kader-2.7.2}/assets/design/v2/code2.html +0 -0
  47. {kader-2.7.0 → kader-2.7.2}/assets/design/v2/code3.html +0 -0
  48. {kader-2.7.0 → kader-2.7.2}/assets/design/v2/screen.png +0 -0
  49. {kader-2.7.0 → kader-2.7.2}/assets/design/v2/screen1.png +0 -0
  50. {kader-2.7.0 → kader-2.7.2}/assets/design/v2/screen2.png +0 -0
  51. {kader-2.7.0 → kader-2.7.2}/assets/design/v2/screen3.png +0 -0
  52. {kader-2.7.0 → kader-2.7.2}/cli/README.md +0 -0
  53. {kader-2.7.0 → kader-2.7.2}/cli/__init__.py +0 -0
  54. {kader-2.7.0 → kader-2.7.2}/cli/__main__.py +0 -0
  55. {kader-2.7.0 → kader-2.7.2}/cli/commands/__init__.py +0 -0
  56. {kader-2.7.0 → kader-2.7.2}/cli/commands/base.py +0 -0
  57. {kader-2.7.0 → kader-2.7.2}/cli/commands/initialize.py +0 -0
  58. {kader-2.7.0 → kader-2.7.2}/cli/llm_factory.py +0 -0
  59. {kader-2.7.0 → kader-2.7.2}/cli/utils.py +0 -0
  60. {kader-2.7.0 → kader-2.7.2}/examples/.gitignore +0 -0
  61. {kader-2.7.0 → kader-2.7.2}/examples/README.md +0 -0
  62. {kader-2.7.0 → kader-2.7.2}/examples/anthropic_example.py +0 -0
  63. {kader-2.7.0 → kader-2.7.2}/examples/google_example.py +0 -0
  64. {kader-2.7.0 → kader-2.7.2}/examples/memory_example.py +0 -0
  65. {kader-2.7.0 → kader-2.7.2}/examples/mistral_example.py +0 -0
  66. {kader-2.7.0 → kader-2.7.2}/examples/ollama_example.py +0 -0
  67. {kader-2.7.0 → kader-2.7.2}/examples/openai_compatible_example.py +0 -0
  68. {kader-2.7.0 → kader-2.7.2}/examples/planner_executor_example.py +0 -0
  69. {kader-2.7.0 → kader-2.7.2}/examples/planning_agent_example.py +0 -0
  70. {kader-2.7.0 → kader-2.7.2}/examples/python_developer/main.py +0 -0
  71. {kader-2.7.0 → kader-2.7.2}/examples/python_developer/template.yaml +0 -0
  72. {kader-2.7.0 → kader-2.7.2}/examples/react_agent_example.py +0 -0
  73. {kader-2.7.0 → kader-2.7.2}/examples/simple_agent.py +0 -0
  74. {kader-2.7.0 → kader-2.7.2}/examples/skills/hello_example.py +0 -0
  75. {kader-2.7.0 → kader-2.7.2}/examples/skills/react_agent.py +0 -0
  76. {kader-2.7.0 → kader-2.7.2}/examples/skills/skills/calculator/SKILL.md +0 -0
  77. {kader-2.7.0 → kader-2.7.2}/examples/skills/skills/calculator/scripts/calculate.py +0 -0
  78. {kader-2.7.0 → kader-2.7.2}/examples/skills/skills/github/SKILL.md +0 -0
  79. {kader-2.7.0 → kader-2.7.2}/examples/skills/skills/hello/SKILL.md +0 -0
  80. {kader-2.7.0 → kader-2.7.2}/examples/skills/skills/hello/scripts/hello.py +0 -0
  81. {kader-2.7.0 → kader-2.7.2}/examples/skills/skills/joke/SKILL.md +0 -0
  82. {kader-2.7.0 → kader-2.7.2}/examples/skills/skills/visualization/SKILL.md +0 -0
  83. {kader-2.7.0 → kader-2.7.2}/examples/todo_agent/main.py +0 -0
  84. {kader-2.7.0 → kader-2.7.2}/examples/tools_example.py +0 -0
  85. {kader-2.7.0 → kader-2.7.2}/kader/README.md +0 -0
  86. {kader-2.7.0 → kader-2.7.2}/kader/__init__.py +0 -0
  87. {kader-2.7.0 → kader-2.7.2}/kader/agent/__init__.py +0 -0
  88. {kader-2.7.0 → kader-2.7.2}/kader/agent/agents.py +0 -0
  89. {kader-2.7.0 → kader-2.7.2}/kader/agent/base.py +0 -0
  90. {kader-2.7.0 → kader-2.7.2}/kader/agent/logger.py +0 -0
  91. {kader-2.7.0 → kader-2.7.2}/kader/config.py +0 -0
  92. {kader-2.7.0 → kader-2.7.2}/kader/memory/__init__.py +0 -0
  93. {kader-2.7.0 → kader-2.7.2}/kader/memory/compression.py +0 -0
  94. {kader-2.7.0 → kader-2.7.2}/kader/memory/conversation.py +0 -0
  95. {kader-2.7.0 → kader-2.7.2}/kader/memory/session.py +0 -0
  96. {kader-2.7.0 → kader-2.7.2}/kader/memory/state.py +0 -0
  97. {kader-2.7.0 → kader-2.7.2}/kader/memory/summarization.py +0 -0
  98. {kader-2.7.0 → kader-2.7.2}/kader/memory/types.py +0 -0
  99. {kader-2.7.0 → kader-2.7.2}/kader/prompts/__init__.py +0 -0
  100. {kader-2.7.0 → kader-2.7.2}/kader/prompts/agent_prompts.py +0 -0
  101. {kader-2.7.0 → kader-2.7.2}/kader/prompts/base.py +0 -0
  102. {kader-2.7.0 → kader-2.7.2}/kader/prompts/cli_prompts.py +0 -0
  103. {kader-2.7.0 → kader-2.7.2}/kader/prompts/templates/command_agent.j2 +0 -0
  104. {kader-2.7.0 → kader-2.7.2}/kader/prompts/templates/executor_agent.j2 +0 -0
  105. {kader-2.7.0 → kader-2.7.2}/kader/prompts/templates/init_command_prompt.j2 +0 -0
  106. {kader-2.7.0 → kader-2.7.2}/kader/prompts/templates/kader_planner.j2 +0 -0
  107. {kader-2.7.0 → kader-2.7.2}/kader/prompts/templates/planning_agent.j2 +0 -0
  108. {kader-2.7.0 → kader-2.7.2}/kader/prompts/templates/react_agent.j2 +0 -0
  109. {kader-2.7.0 → kader-2.7.2}/kader/providers/__init__.py +0 -0
  110. {kader-2.7.0 → kader-2.7.2}/kader/providers/anthropic.py +0 -0
  111. {kader-2.7.0 → kader-2.7.2}/kader/providers/base.py +0 -0
  112. {kader-2.7.0 → kader-2.7.2}/kader/providers/google.py +0 -0
  113. {kader-2.7.0 → kader-2.7.2}/kader/providers/mistral.py +0 -0
  114. {kader-2.7.0 → kader-2.7.2}/kader/providers/mock.py +0 -0
  115. {kader-2.7.0 → kader-2.7.2}/kader/providers/ollama.py +0 -0
  116. {kader-2.7.0 → kader-2.7.2}/kader/providers/openai_compatible.py +0 -0
  117. {kader-2.7.0 → kader-2.7.2}/kader/tools/README.md +0 -0
  118. {kader-2.7.0 → kader-2.7.2}/kader/tools/__init__.py +0 -0
  119. {kader-2.7.0 → kader-2.7.2}/kader/tools/agent.py +0 -0
  120. {kader-2.7.0 → kader-2.7.2}/kader/tools/base.py +0 -0
  121. {kader-2.7.0 → kader-2.7.2}/kader/tools/commands.py +0 -0
  122. {kader-2.7.0 → kader-2.7.2}/kader/tools/filesys.py +0 -0
  123. {kader-2.7.0 → kader-2.7.2}/kader/tools/filesystem.py +0 -0
  124. {kader-2.7.0 → kader-2.7.2}/kader/tools/protocol.py +0 -0
  125. {kader-2.7.0 → kader-2.7.2}/kader/tools/rag.py +0 -0
  126. {kader-2.7.0 → kader-2.7.2}/kader/tools/skills.py +0 -0
  127. {kader-2.7.0 → kader-2.7.2}/kader/tools/todo.py +0 -0
  128. {kader-2.7.0 → kader-2.7.2}/kader/tools/utils.py +0 -0
  129. {kader-2.7.0 → kader-2.7.2}/kader/tools/web.py +0 -0
  130. {kader-2.7.0 → kader-2.7.2}/kader/utils/__init__.py +0 -0
  131. {kader-2.7.0 → kader-2.7.2}/kader/utils/checkpointer.py +0 -0
  132. {kader-2.7.0 → kader-2.7.2}/kader/utils/context_aggregator.py +0 -0
  133. {kader-2.7.0 → kader-2.7.2}/kader/utils/ignore.py +0 -0
  134. {kader-2.7.0 → kader-2.7.2}/kader/workflows/__init__.py +0 -0
  135. {kader-2.7.0 → kader-2.7.2}/kader/workflows/base.py +0 -0
  136. {kader-2.7.0 → kader-2.7.2}/kader/workflows/planner_executor.py +0 -0
  137. {kader-2.7.0 → kader-2.7.2}/tests/conftest.py +0 -0
  138. {kader-2.7.0 → kader-2.7.2}/tests/providers/test_anthropic_provider.py +0 -0
  139. {kader-2.7.0 → kader-2.7.2}/tests/providers/test_google.py +0 -0
  140. {kader-2.7.0 → kader-2.7.2}/tests/providers/test_mistral_provider.py +0 -0
  141. {kader-2.7.0 → kader-2.7.2}/tests/providers/test_mock.py +0 -0
  142. {kader-2.7.0 → kader-2.7.2}/tests/providers/test_ollama.py +0 -0
  143. {kader-2.7.0 → kader-2.7.2}/tests/providers/test_openai_compatible_provider.py +0 -0
  144. {kader-2.7.0 → kader-2.7.2}/tests/providers/test_providers_base.py +0 -0
  145. {kader-2.7.0 → kader-2.7.2}/tests/test_agent_logger.py +0 -0
  146. {kader-2.7.0 → kader-2.7.2}/tests/test_agent_logger_integration.py +0 -0
  147. {kader-2.7.0 → kader-2.7.2}/tests/test_base_agent.py +0 -0
  148. {kader-2.7.0 → kader-2.7.2}/tests/test_file_memory.py +0 -0
  149. {kader-2.7.0 → kader-2.7.2}/tests/test_hierarchical_memory.py +0 -0
  150. {kader-2.7.0 → kader-2.7.2}/tests/test_todo_tool.py +0 -0
  151. {kader-2.7.0 → kader-2.7.2}/tests/tools/test_agent_tool.py +0 -0
  152. {kader-2.7.0 → kader-2.7.2}/tests/tools/test_agent_tool_persistence.py +0 -0
  153. {kader-2.7.0 → kader-2.7.2}/tests/tools/test_agent_tool_skills.py +0 -0
  154. {kader-2.7.0 → kader-2.7.2}/tests/tools/test_filesys_tools.py +0 -0
  155. {kader-2.7.0 → kader-2.7.2}/tests/tools/test_filesystem_tools.py +0 -0
  156. {kader-2.7.0 → kader-2.7.2}/tests/tools/test_rag.py +0 -0
  157. {kader-2.7.0 → kader-2.7.2}/tests/tools/test_skills.py +0 -0
  158. {kader-2.7.0 → kader-2.7.2}/tests/tools/test_tools_base.py +0 -0
  159. {kader-2.7.0 → kader-2.7.2}/tests/tools/test_web.py +0 -0
@@ -0,0 +1,58 @@
1
+ name: Deploy Documentation
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+ pages: write
12
+ id-token: write
13
+
14
+ concurrency:
15
+ group: "pages"
16
+ cancel-in-progress: false
17
+
18
+ jobs:
19
+ build:
20
+ runs-on: ubuntu-latest
21
+
22
+ steps:
23
+ - name: Checkout Repository
24
+ uses: actions/checkout@v4
25
+
26
+ - name: Setup Python
27
+ uses: actions/setup-python@v5
28
+ with:
29
+ python-version: '3.x'
30
+
31
+ - name: Install uv
32
+ run: |
33
+ pip install uv
34
+
35
+ - name: Install Dependencies
36
+ run: |
37
+ uv sync --group docs
38
+
39
+ - name: Build with MkDocs
40
+ run: |
41
+ uv run mkdocs build --verbose
42
+
43
+ - name: Upload Artifact
44
+ uses: actions/upload-pages-artifact@v3
45
+ with:
46
+ path: './site'
47
+
48
+ deploy:
49
+ environment:
50
+ name: github-pages
51
+ url: ${{ steps.deployment.outputs.page_url }}
52
+ runs-on: ubuntu-latest
53
+ needs: build
54
+
55
+ steps:
56
+ - name: Deploy to GitHub Pages
57
+ id: deployment
58
+ uses: actions/deploy-pages@v4
@@ -19,4 +19,7 @@ wheels/
19
19
  .env
20
20
 
21
21
  # optimization
22
- optimizers/
22
+ optimizers/
23
+
24
+ # MkDocs output
25
+ site/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kader
3
- Version: 2.7.0
3
+ Version: 2.7.2
4
4
  Summary: kader coding agent
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.11
@@ -17,6 +17,7 @@ Requires-Dist: openai>=2.20.0
17
17
  Requires-Dist: outdated>=0.2.2
18
18
  Requires-Dist: prompt-toolkit>=3.0.50
19
19
  Requires-Dist: python-frontmatter>=1.1.0
20
+ Requires-Dist: pywinpty>=3.0.0; platform_system == 'Windows'
20
21
  Requires-Dist: pyyaml>=6.0.3
21
22
  Requires-Dist: rich>=14.0.0
22
23
  Requires-Dist: tenacity>=9.1.2
@@ -7,7 +7,6 @@ beautiful terminal output and prompt_toolkit for async input handling.
7
7
  import asyncio
8
8
  import atexit
9
9
  import json
10
- import subprocess
11
10
  import threading
12
11
  import warnings
13
12
  from concurrent.futures import ThreadPoolExecutor
@@ -122,6 +121,11 @@ class KaderApp:
122
121
 
123
122
  def _create_workflow(self, model_name: str) -> PlannerExecutorWorkflow:
124
123
  """Create a new PlannerExecutorWorkflow with the specified model."""
124
+ from kader.tools.exec_commands import CommandExecutorTool
125
+
126
+ CommandExecutorTool.set_output_callback(self._command_output_callback)
127
+ CommandExecutorTool.set_input_callback(self._command_input_callback)
128
+
125
129
  provider = LLMProviderFactory.create_provider(model_name)
126
130
 
127
131
  workflow = PlannerExecutorWorkflow(
@@ -249,35 +253,32 @@ class KaderApp:
249
253
  format_plan_display(items)
250
254
  except (json.JSONDecodeError, TypeError):
251
255
  pass
252
- if tool_name == "execute_command" and ":\n" in result:
253
- output = result.split(":\n", 1)[1]
254
- self.console.print()
255
- self.console.print(
256
- Panel(
257
- output.strip(),
258
- title="[kader.orange]Command Output[/kader.orange]",
259
- border_style="dark_orange",
260
- padding=(0, 1),
261
- )
262
- )
263
256
  else:
264
257
  error_preview = result[:100] + "..." if len(result) > 100 else result
265
258
  self.console.print(
266
259
  rf" [kader.red]\[-] {tool_name}[/kader.red] failed: {error_preview}"
267
260
  )
268
- if tool_name == "execute_command" and ":\n" in result:
269
- output = result.split(":\n", 1)[1]
270
- self.console.print()
271
- self.console.print(
272
- Panel(
273
- output.strip(),
274
- title="[kader.red]Command Output/Error[/kader.red]",
275
- border_style="red",
276
- padding=(0, 1),
277
- )
278
- )
279
261
  self._start_spinner()
280
262
 
263
+ def _command_output_callback(self, output: str) -> None:
264
+ """Callback for streaming command output - called from agent thread."""
265
+ self.console.print(output, end="")
266
+
267
+ def _command_input_callback(self) -> str | None:
268
+ """Callback for getting user input during command execution."""
269
+ self._stop_spinner()
270
+ self.console.print()
271
+ self.console.print(
272
+ r"[kader.yellow]\[?] Command needs input:[/kader.yellow] ",
273
+ end="",
274
+ )
275
+ try:
276
+ user_input = input().strip()
277
+ except (EOFError, KeyboardInterrupt):
278
+ user_input = None
279
+ self._start_spinner()
280
+ return user_input
281
+
281
282
  def _tool_confirmation_callback(self, message: str) -> tuple[bool, Optional[str]]:
282
283
  """Callback for tool confirmation - called from agent thread.
283
284
 
@@ -611,37 +612,153 @@ class KaderApp:
611
612
  self.console.print(f"\n [kader.orange]\\[>] Executing:[/kader.orange] `{cmd}`")
612
613
 
613
614
  try:
614
- process = await asyncio.create_subprocess_shell(
615
- cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
616
- )
617
- stdout, stderr = await process.communicate()
618
-
619
- output = ""
620
- if stdout:
621
- output += stdout.decode().strip()
622
- if stderr:
623
- if output:
624
- output += "\n\n"
625
- output += f"Stderr:\n{stderr.decode().strip()}"
615
+ output = await self._run_terminal_command_pty(cmd)
626
616
 
627
617
  if not output:
628
- output = "Command executed successfully with no output."
629
-
630
- self.console.print()
631
- self.console.print(
632
- Panel(
633
- output,
634
- title="[kader.orange]Terminal Output[/kader.orange]",
635
- border_style="dark_orange",
636
- padding=(0, 1),
618
+ self.console.print(
619
+ " [dim]Command executed successfully with no output.[/dim]"
637
620
  )
638
- )
639
621
 
640
622
  except Exception as e:
641
623
  self.console.print(
642
624
  rf" [kader.red]\[-][/kader.red] Error executing command: {e}"
643
625
  )
644
626
 
627
+ async def _run_terminal_command_pty(self, command: str) -> str:
628
+ """Run a terminal command using PTY for interactive support."""
629
+ import platform
630
+
631
+ system = platform.system().lower()
632
+
633
+ if system == "windows":
634
+ return await self._run_terminal_command_winpty(command)
635
+ else:
636
+ return await self._run_terminal_command_unix_pty(command)
637
+
638
+ async def _run_terminal_command_unix_pty(self, command: str) -> str:
639
+ """Run a terminal command using PTY on Unix."""
640
+ import os
641
+ import pty
642
+
643
+ master_fd, slave_fd = pty.openpty()
644
+
645
+ env = os.environ.copy()
646
+ env["TERM"] = "xterm-256color"
647
+
648
+ shell = "/bin/bash"
649
+
650
+ try:
651
+ process = await asyncio.create_subprocess_exec(
652
+ shell,
653
+ "-c",
654
+ command,
655
+ stdin=slave_fd,
656
+ stdout=asyncio.subprocess.PIPE,
657
+ stderr=asyncio.subprocess.STDOUT,
658
+ env=env,
659
+ )
660
+ finally:
661
+ os.close(slave_fd)
662
+ os.close(master_fd)
663
+
664
+ output_parts = []
665
+ stdout = process.stdout
666
+ assert stdout is not None
667
+
668
+ while True:
669
+ line = await asyncio.wait_for(stdout.readline(), timeout=0.1)
670
+ if line:
671
+ text = line.decode("utf-8", errors="replace")
672
+ output_parts.append(text)
673
+ self.console.print(text, end="")
674
+ elif process.returncode is not None:
675
+ break
676
+
677
+ await process.wait()
678
+ return "".join(output_parts).strip()
679
+
680
+ async def _run_terminal_command_winpty(self, command: str) -> str:
681
+ """Run a terminal command using pywinpty on Windows."""
682
+
683
+ try:
684
+ import pywinpty
685
+ except ImportError:
686
+ return await self._run_terminal_command_direct(command)
687
+
688
+ command_lower = command.lower().strip()
689
+ is_powershell = command_lower.startswith("pwsh") or command_lower.startswith(
690
+ "powershell"
691
+ )
692
+
693
+ if is_powershell:
694
+ shell = "powershell.exe"
695
+ shell_args = ["-Command", command]
696
+ else:
697
+ shell = "cmd.exe"
698
+ shell_args = ["/c", command]
699
+
700
+ try:
701
+ pty_obj = pywinpty.PTY(width=80, height=24, visible=False)
702
+ except Exception:
703
+ return await self._run_terminal_command_direct(command)
704
+
705
+ try:
706
+ process = pty_obj.spawn(shell, shell_args)
707
+ except Exception:
708
+ return await self._run_terminal_command_direct(command)
709
+
710
+ output_parts = []
711
+ max_attempts = 100
712
+ attempts = 0
713
+
714
+ while attempts < max_attempts:
715
+ try:
716
+ data = process.read(blocking=False)
717
+ if data:
718
+ output_parts.append(data)
719
+ self.console.print(data, end="")
720
+ attempts = 0
721
+ except Exception:
722
+ pass
723
+
724
+ if not process.isalive():
725
+ await asyncio.sleep(0.1)
726
+ try:
727
+ data = process.read(blocking=False)
728
+ if data:
729
+ output_parts.append(data)
730
+ self.console.print(data, end="")
731
+ except Exception:
732
+ pass
733
+ if not process.isalive():
734
+ break
735
+
736
+ attempts += 1
737
+ await asyncio.sleep(0.05)
738
+
739
+ result = "".join(output_parts).strip()
740
+
741
+ if not result:
742
+ return await self._run_terminal_command_direct(command)
743
+
744
+ return result
745
+
746
+ async def _run_terminal_command_direct(self, command: str) -> str:
747
+ """Run a terminal command directly without PTY (fallback)."""
748
+
749
+ try:
750
+ process = await asyncio.create_subprocess_shell(
751
+ command,
752
+ stdout=asyncio.subprocess.PIPE,
753
+ stderr=asyncio.subprocess.STDOUT,
754
+ )
755
+ stdout, _ = await process.communicate()
756
+
757
+ output = stdout.decode("utf-8", errors="replace").strip()
758
+ return output
759
+ except Exception as e:
760
+ return f"Error: {str(e)}"
761
+
645
762
  def _handle_save_session(self) -> None:
646
763
  """Save the current session."""
647
764
  import shutil
@@ -1058,12 +1175,15 @@ class KaderApp:
1058
1175
 
1059
1176
  def run(self) -> None:
1060
1177
  """Run the Kader CLI application."""
1178
+ from kader.tools.exec_commands import CommandExecutorTool
1179
+
1061
1180
  try:
1062
1181
  asyncio.run(self._run_async())
1063
1182
  except KeyboardInterrupt:
1064
1183
  self.console.print("\n [kader.muted]Goodbye! 👋[/kader.muted]")
1065
1184
  finally:
1066
1185
  self._stop_spinner()
1186
+ CommandExecutorTool.clear_callbacks()
1067
1187
 
1068
1188
 
1069
1189
  def main() -> None:
@@ -0,0 +1,183 @@
1
+ # CLI Reference
2
+
3
+ ![Kader CLI](../assets/imgs/kader-cli.png)
4
+
5
+ The Kader CLI is an interactive terminal-based AI coding assistant built with Rich and prompt_toolkit.
6
+
7
+ ## Features
8
+
9
+ - **Planner-Executor Workflow** — Intelligent agent with reasoning, planning, and tool execution
10
+ - **Built-in Tools** — File system, command execution, web search
11
+ - **Rich Conversation** — Beautiful markdown-rendered chat with styled panels
12
+ - **Session Persistence** — Save and load conversation sessions
13
+ - **Tool Confirmation** — Interactive approval for tool execution
14
+ - **Model Selection** — Dynamic model switching interface
15
+ - **Multi-Provider Support** — Ollama, Google Gemini, Anthropic, Mistral, OpenAI, and more
16
+
17
+ ## Running the CLI
18
+
19
+ ```bash
20
+ # Using uv tool (recommended - installs globally)
21
+ kader
22
+
23
+ # Or using uv run
24
+ uv run python -m cli
25
+
26
+ # Or clone and run
27
+ git clone https://github.com/Kader-AI-hub/kader.git
28
+ cd kader
29
+ uv run python -m cli
30
+ ```
31
+
32
+ ## Commands
33
+
34
+ | Command | Description |
35
+ |---------|-------------|
36
+ | `/help` | Show command reference |
37
+ | `/models` | Show and switch available models |
38
+ | `/clear` | Clear conversation |
39
+ | `/save` | Save current session |
40
+ | `/load <id>` | Load a saved session |
41
+ | `/sessions` | List saved sessions |
42
+ | `/skills` | List loaded skills |
43
+ | `/commands` | List special commands |
44
+ | `/cost` | Show usage costs |
45
+ | `/init` | Initialize .kader directory with KADER.md |
46
+ | `/exit` | Exit the CLI |
47
+ | `!cmd` | Run terminal command |
48
+
49
+ ## Keyboard Shortcuts
50
+
51
+ | Shortcut | Action |
52
+ |----------|--------|
53
+ | `Ctrl+C` | Cancel current operation |
54
+ | `Ctrl+D` | Exit the CLI |
55
+
56
+ ## Session Management
57
+
58
+ Sessions are saved to `~/.kader/sessions/`. Use:
59
+
60
+ - `/save` — Save current conversation
61
+ - `/sessions` — List all saved sessions
62
+ - `/load <session_id>` — Restore a session
63
+
64
+ ## Tool Confirmation System
65
+
66
+ Kader includes an interactive tool confirmation system that prompts for approval before executing tools:
67
+
68
+ - Safe execution of potentially destructive operations
69
+ - Simple `[Y/n/reason]` prompt for quick approval
70
+ - Ability to provide context when rejecting a tool
71
+
72
+ ## Skills System
73
+
74
+ Skills are loaded from:
75
+
76
+ - `~/.kader/skills/` — User-level skills
77
+ - `./.kader/skills/` — Project-level skills
78
+
79
+ Use `/skills` to list all available skills.
80
+
81
+ ### Skill File Format
82
+
83
+ ```yaml
84
+ ---
85
+ name: python-expert
86
+ description: Expert in Python programming and best practices
87
+ ---
88
+
89
+ # Python Expert Skill
90
+
91
+ You are an expert Python developer...
92
+ ```
93
+
94
+ ## Special Commands
95
+
96
+ Commands are loaded from:
97
+
98
+ - `./.kader/commands/` — Project-level commands (higher priority)
99
+ - `~/.kader/commands/` — User-level commands
100
+
101
+ Use `/commands` to list all available special commands.
102
+
103
+ ### Creating a Command
104
+
105
+ Create a command directory with a `CONTENT.md` file:
106
+
107
+ ```bash
108
+ mkdir -p ~/.kader/commands/mycommand
109
+ ```
110
+
111
+ **CONTENT.md format:**
112
+
113
+ ```yaml
114
+ ---
115
+ description: What this command does
116
+ ---
117
+
118
+ # Command Instructions
119
+
120
+ Your command agent instructions here...
121
+ ```
122
+
123
+ ### Using Commands
124
+
125
+ ```
126
+ /mycommand
127
+ /mycommand do something specific
128
+ ```
129
+
130
+ ### Example: Lint and Test Command
131
+
132
+ ```yaml
133
+ ---
134
+ description: Lint and test the codebase following AGENTS.md
135
+ ---
136
+
137
+ You are a Lint and Test Agent specialized in maintaining code quality.
138
+
139
+ ## Instructions
140
+
141
+ 1. Run: uv run ruff check .
142
+ 2. Run: uv run ruff format --check .
143
+ 3. Run: uv run pytest -v
144
+ 4. Report results
145
+ ```
146
+
147
+ Usage: `/lint-test` or `/lint-test run full check`
148
+
149
+ ## Model Selection
150
+
151
+ The model selection interface allows you to:
152
+
153
+ - Browse all available models from configured providers
154
+ - Switch models on the fly during conversation
155
+ - See which model is currently active
156
+
157
+ ### Supported Providers
158
+
159
+ | Provider | Format | Example |
160
+ |----------|--------|---------|
161
+ | Ollama | `ollama:model` | `ollama:llama3` |
162
+ | Google Gemini | `google:model` | `google:gemini-2.5-flash` |
163
+ | Mistral | `mistral:model` | `mistral:small-3.1` |
164
+ | Anthropic | `anthropic:model` | `anthropic:claude-3.5-sonnet` |
165
+ | OpenAI | `openai:model` | `openai:gpt-4o` |
166
+ | Moonshot | `moonshot:model` | `moonshot:kimi-k2.5` |
167
+ | Z.ai | `zai:model` | `zai:glm-5` |
168
+ | OpenRouter | `openrouter:model` | `openrouter:anthropic/claude-3.5-sonnet` |
169
+ | OpenCode | `opencode:model` | `opencode:claude-3.5-sonnet` |
170
+ | Groq | `groq:model` | `groq:llama-3.3-70b-versatile` |
171
+
172
+ ### Setting API Keys
173
+
174
+ ```bash
175
+ export GOOGLE_API_KEY="your-google-api-key"
176
+ export ANTHROPIC_API_KEY="your-anthropic-api-key"
177
+ export MISTRAL_API_KEY="your-mistral-api-key"
178
+ export OPENAI_API_KEY="your-openai-api-key"
179
+ export MOONSHOT_API_KEY="your-kimi-api-key"
180
+ export ZAI_API_KEY="your-glm-api-key"
181
+ export OPENROUTER_API_KEY="your-openrouter-api-key"
182
+ export GROQ_API_KEY="your-groq-api-key"
183
+ ```
@@ -0,0 +1,107 @@
1
+ # Configuration
2
+
3
+ Kader can be configured through environment variables, YAML files, and the `.kader` directory in your home folder.
4
+
5
+ ## Environment Variables
6
+
7
+ | Variable | Description | Required |
8
+ |----------|-------------|----------|
9
+ | `KADER_DIR` | Kader config directory (default: `~/.kader`) | No |
10
+ | `GEMINI_API_KEY` | Google Gemini API key | For Google Provider |
11
+ | `MISTRAL_API_KEY` | Mistral API key | For Mistral Provider |
12
+ | `ANTHROPIC_API_KEY` | Anthropic API key | For Anthropic Provider |
13
+ | `OPENAI_API_KEY` | OpenAI API key | For OpenAI Provider |
14
+ | `MOONSHOT_API_KEY` | Moonshot AI key | For Moonshot Provider |
15
+ | `ZAI_API_KEY` | Z.ai (GLM) API key | For Z.ai Provider |
16
+ | `OPENROUTER_API_KEY` | OpenRouter key | For OpenRouter Provider |
17
+ | `OPENCODE_API_KEY` | OpenCode API key | For OpenCode Provider |
18
+ | `GROQ_API_KEY` | Groq API key | For Groq Provider |
19
+
20
+ ## .kader Directory
21
+
22
+ When the kader module is imported for the first time, it automatically creates a `.kader` directory in your home directory:
23
+
24
+ ```
25
+ ~/.kader/
26
+ ├── .env # Environment variables
27
+ ├── memory/ # Memory and session storage
28
+ │ └── sessions/ # Saved conversation sessions
29
+ ├── skills/ # User-level skills
30
+ ├── commands/ # User-level special commands
31
+ └── KADER.md # Agent instructions file
32
+ ```
33
+
34
+ ## YAML Agent Configuration
35
+
36
+ BaseAgent supports loading configuration from YAML files:
37
+
38
+ ```yaml
39
+ # agent.yaml
40
+ name: ConfiguredAgent
41
+ system_prompt: "You are a helpful coding assistant."
42
+ tools:
43
+ - read_file
44
+ - write_file
45
+ - todo_tool
46
+ provider:
47
+ model: llama3.2
48
+ provider: ollama
49
+ persistence: true
50
+ retry_attempts: 3
51
+ interrupt_before_tool: true
52
+ ```
53
+
54
+ ```python
55
+ agent = BaseAgent.from_yaml("agent.yaml")
56
+ ```
57
+
58
+ ## Provider Configuration Format
59
+
60
+ ### Ollama
61
+
62
+ ```yaml
63
+ provider:
64
+ provider: ollama
65
+ model: llama3.2
66
+ base_url: "http://localhost:11434"
67
+ timeout: 120
68
+ ```
69
+
70
+ ### Google Gemini
71
+
72
+ ```yaml
73
+ provider:
74
+ provider: google
75
+ model: gemini-2.0-flash
76
+ temperature: 0.7
77
+ max_tokens: 2048
78
+ ```
79
+
80
+ ### Anthropic
81
+
82
+ ```yaml
83
+ provider:
84
+ provider: anthropic
85
+ model: claude-3-5-sonnet-20241022
86
+ temperature: 0.7
87
+ ```
88
+
89
+ ### OpenAI-Compatible
90
+
91
+ ```yaml
92
+ provider:
93
+ provider: openai
94
+ model: gpt-4o
95
+ base_url: "https://api.openai.com/v1"
96
+ api_key: "your-openai-key"
97
+ ```
98
+
99
+ For Groq, OpenRouter, etc., use the appropriate `base_url`.
100
+
101
+ ## Session Storage
102
+
103
+ Sessions are saved to `~/.kader/memory/sessions/` and include:
104
+ - Conversation history
105
+ - Agent state
106
+ - Tool execution logs
107
+ - Sub-agent contexts (for aggregated context)