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.
- kader-2.7.2/.github/workflows/pages.yml +58 -0
- {kader-2.7.0 → kader-2.7.2}/.gitignore +4 -1
- {kader-2.7.0 → kader-2.7.2}/PKG-INFO +2 -1
- {kader-2.7.0 → kader-2.7.2}/cli/app.py +165 -45
- kader-2.7.2/docs/assets/imgs/kader-cli.png +0 -0
- kader-2.7.2/docs/cli/index.md +183 -0
- kader-2.7.2/docs/configuration.md +107 -0
- kader-2.7.2/docs/core-framework/agents.md +128 -0
- kader-2.7.2/docs/core-framework/index.md +81 -0
- kader-2.7.2/docs/core-framework/memory.md +179 -0
- kader-2.7.2/docs/core-framework/providers.md +176 -0
- kader-2.7.2/docs/core-framework/tools.md +268 -0
- kader-2.7.2/docs/guide.md +140 -0
- kader-2.7.2/docs/index.md +64 -0
- kader-2.7.2/kader/tools/exec_commands.py +532 -0
- kader-2.7.2/mkdocs.yml +65 -0
- {kader-2.7.0 → kader-2.7.2}/pyproject.toml +6 -1
- {kader-2.7.0 → kader-2.7.2}/tests/tools/test_exec_commands.py +14 -12
- {kader-2.7.0 → kader-2.7.2}/uv.lock +244 -1
- kader-2.7.0/kader/tools/exec_commands.py +0 -307
- {kader-2.7.0 → kader-2.7.2}/.github/workflows/ci.yml +0 -0
- {kader-2.7.0 → kader-2.7.2}/.github/workflows/release.yml +0 -0
- {kader-2.7.0 → kader-2.7.2}/.kader/KADER.md +0 -0
- {kader-2.7.0 → kader-2.7.2}/.kader/commands/lint-test/CONTENT.md +0 -0
- {kader-2.7.0 → kader-2.7.2}/.kader/skills/contributing-to-kader/SKILL.md +0 -0
- {kader-2.7.0 → kader-2.7.2}/.kader/skills/contributing-to-kader/assets/contributor_checklist.md +0 -0
- {kader-2.7.0 → kader-2.7.2}/.kader/skills/contributing-to-kader/references/kader_agent_instructions.md +0 -0
- {kader-2.7.0 → kader-2.7.2}/.kader/skills/contributing-to-kader/scripts/dev_helper.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/.opencode/skills/contributing-to-kader/SKILL.md +0 -0
- {kader-2.7.0 → kader-2.7.2}/.opencode/skills/contributing-to-kader/assets/contributor_checklist.md +0 -0
- {kader-2.7.0 → kader-2.7.2}/.opencode/skills/contributing-to-kader/references/kader_agent_instructions.md +0 -0
- {kader-2.7.0 → kader-2.7.2}/.opencode/skills/contributing-to-kader/scripts/dev_helper.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/.python-version +0 -0
- {kader-2.7.0 → kader-2.7.2}/AGENTS.md +0 -0
- {kader-2.7.0 → kader-2.7.2}/CONTRIBUTING.md +0 -0
- {kader-2.7.0 → kader-2.7.2}/LICENSE +0 -0
- {kader-2.7.0 → kader-2.7.2}/README.md +0 -0
- {kader-2.7.0 → kader-2.7.2}/assets/architecture/cli_integration.mmd +0 -0
- {kader-2.7.0 → kader-2.7.2}/assets/architecture/cli_message_flow.mmd +0 -0
- {kader-2.7.0 → kader-2.7.2}/assets/architecture/cli_tool_confirmation.mmd +0 -0
- {kader-2.7.0 → kader-2.7.2}/assets/architecture/memory_flow.mmd +0 -0
- {kader-2.7.0 → kader-2.7.2}/assets/architecture/planner_executor_sequence.mmd +0 -0
- {kader-2.7.0 → kader-2.7.2}/assets/architecture/planner_executor_workflow.mmd +0 -0
- {kader-2.7.0 → kader-2.7.2}/assets/design/v2/code.html +0 -0
- {kader-2.7.0 → kader-2.7.2}/assets/design/v2/code1.html +0 -0
- {kader-2.7.0 → kader-2.7.2}/assets/design/v2/code2.html +0 -0
- {kader-2.7.0 → kader-2.7.2}/assets/design/v2/code3.html +0 -0
- {kader-2.7.0 → kader-2.7.2}/assets/design/v2/screen.png +0 -0
- {kader-2.7.0 → kader-2.7.2}/assets/design/v2/screen1.png +0 -0
- {kader-2.7.0 → kader-2.7.2}/assets/design/v2/screen2.png +0 -0
- {kader-2.7.0 → kader-2.7.2}/assets/design/v2/screen3.png +0 -0
- {kader-2.7.0 → kader-2.7.2}/cli/README.md +0 -0
- {kader-2.7.0 → kader-2.7.2}/cli/__init__.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/cli/__main__.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/cli/commands/__init__.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/cli/commands/base.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/cli/commands/initialize.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/cli/llm_factory.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/cli/utils.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/examples/.gitignore +0 -0
- {kader-2.7.0 → kader-2.7.2}/examples/README.md +0 -0
- {kader-2.7.0 → kader-2.7.2}/examples/anthropic_example.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/examples/google_example.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/examples/memory_example.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/examples/mistral_example.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/examples/ollama_example.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/examples/openai_compatible_example.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/examples/planner_executor_example.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/examples/planning_agent_example.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/examples/python_developer/main.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/examples/python_developer/template.yaml +0 -0
- {kader-2.7.0 → kader-2.7.2}/examples/react_agent_example.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/examples/simple_agent.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/examples/skills/hello_example.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/examples/skills/react_agent.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/examples/skills/skills/calculator/SKILL.md +0 -0
- {kader-2.7.0 → kader-2.7.2}/examples/skills/skills/calculator/scripts/calculate.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/examples/skills/skills/github/SKILL.md +0 -0
- {kader-2.7.0 → kader-2.7.2}/examples/skills/skills/hello/SKILL.md +0 -0
- {kader-2.7.0 → kader-2.7.2}/examples/skills/skills/hello/scripts/hello.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/examples/skills/skills/joke/SKILL.md +0 -0
- {kader-2.7.0 → kader-2.7.2}/examples/skills/skills/visualization/SKILL.md +0 -0
- {kader-2.7.0 → kader-2.7.2}/examples/todo_agent/main.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/examples/tools_example.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/README.md +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/__init__.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/agent/__init__.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/agent/agents.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/agent/base.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/agent/logger.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/config.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/memory/__init__.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/memory/compression.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/memory/conversation.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/memory/session.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/memory/state.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/memory/summarization.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/memory/types.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/prompts/__init__.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/prompts/agent_prompts.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/prompts/base.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/prompts/cli_prompts.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/prompts/templates/command_agent.j2 +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/prompts/templates/executor_agent.j2 +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/prompts/templates/init_command_prompt.j2 +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/prompts/templates/kader_planner.j2 +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/prompts/templates/planning_agent.j2 +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/prompts/templates/react_agent.j2 +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/providers/__init__.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/providers/anthropic.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/providers/base.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/providers/google.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/providers/mistral.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/providers/mock.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/providers/ollama.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/providers/openai_compatible.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/tools/README.md +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/tools/__init__.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/tools/agent.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/tools/base.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/tools/commands.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/tools/filesys.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/tools/filesystem.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/tools/protocol.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/tools/rag.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/tools/skills.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/tools/todo.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/tools/utils.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/tools/web.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/utils/__init__.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/utils/checkpointer.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/utils/context_aggregator.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/utils/ignore.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/workflows/__init__.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/workflows/base.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/kader/workflows/planner_executor.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/tests/conftest.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/tests/providers/test_anthropic_provider.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/tests/providers/test_google.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/tests/providers/test_mistral_provider.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/tests/providers/test_mock.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/tests/providers/test_ollama.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/tests/providers/test_openai_compatible_provider.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/tests/providers/test_providers_base.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/tests/test_agent_logger.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/tests/test_agent_logger_integration.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/tests/test_base_agent.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/tests/test_file_memory.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/tests/test_hierarchical_memory.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/tests/test_todo_tool.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/tests/tools/test_agent_tool.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/tests/tools/test_agent_tool_persistence.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/tests/tools/test_agent_tool_skills.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/tests/tools/test_filesys_tools.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/tests/tools/test_filesystem_tools.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/tests/tools/test_rag.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/tests/tools/test_skills.py +0 -0
- {kader-2.7.0 → kader-2.7.2}/tests/tools/test_tools_base.py +0 -0
- {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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kader
|
|
3
|
-
Version: 2.7.
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
Binary file
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# CLI Reference
|
|
2
|
+
|
|
3
|
+

|
|
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)
|