kader 2.7.2__tar.gz → 2.7.4__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 (158) hide show
  1. {kader-2.7.2 → kader-2.7.4}/PKG-INFO +5 -3
  2. {kader-2.7.2 → kader-2.7.4}/README.md +4 -2
  3. {kader-2.7.2 → kader-2.7.4}/cli/README.md +10 -3
  4. {kader-2.7.2 → kader-2.7.4}/cli/app.py +12 -123
  5. {kader-2.7.2 → kader-2.7.4}/cli/llm_factory.py +53 -2
  6. {kader-2.7.2 → kader-2.7.4}/docs/cli/index.md +8 -1
  7. {kader-2.7.2 → kader-2.7.4}/docs/configuration.md +12 -1
  8. {kader-2.7.2 → kader-2.7.4}/docs/guide.md +3 -0
  9. {kader-2.7.2 → kader-2.7.4}/kader/README.md +26 -2
  10. {kader-2.7.2 → kader-2.7.4}/kader/providers/ollama.py +34 -7
  11. {kader-2.7.2 → kader-2.7.4}/pyproject.toml +1 -1
  12. {kader-2.7.2 → kader-2.7.4}/uv.lock +1 -1
  13. {kader-2.7.2 → kader-2.7.4}/.github/workflows/ci.yml +0 -0
  14. {kader-2.7.2 → kader-2.7.4}/.github/workflows/pages.yml +0 -0
  15. {kader-2.7.2 → kader-2.7.4}/.github/workflows/release.yml +0 -0
  16. {kader-2.7.2 → kader-2.7.4}/.gitignore +0 -0
  17. {kader-2.7.2 → kader-2.7.4}/.kader/KADER.md +0 -0
  18. {kader-2.7.2 → kader-2.7.4}/.kader/commands/lint-test/CONTENT.md +0 -0
  19. {kader-2.7.2 → kader-2.7.4}/.kader/skills/contributing-to-kader/SKILL.md +0 -0
  20. {kader-2.7.2 → kader-2.7.4}/.kader/skills/contributing-to-kader/assets/contributor_checklist.md +0 -0
  21. {kader-2.7.2 → kader-2.7.4}/.kader/skills/contributing-to-kader/references/kader_agent_instructions.md +0 -0
  22. {kader-2.7.2 → kader-2.7.4}/.kader/skills/contributing-to-kader/scripts/dev_helper.py +0 -0
  23. {kader-2.7.2 → kader-2.7.4}/.opencode/skills/contributing-to-kader/SKILL.md +0 -0
  24. {kader-2.7.2 → kader-2.7.4}/.opencode/skills/contributing-to-kader/assets/contributor_checklist.md +0 -0
  25. {kader-2.7.2 → kader-2.7.4}/.opencode/skills/contributing-to-kader/references/kader_agent_instructions.md +0 -0
  26. {kader-2.7.2 → kader-2.7.4}/.opencode/skills/contributing-to-kader/scripts/dev_helper.py +0 -0
  27. {kader-2.7.2 → kader-2.7.4}/.python-version +0 -0
  28. {kader-2.7.2 → kader-2.7.4}/AGENTS.md +0 -0
  29. {kader-2.7.2 → kader-2.7.4}/CONTRIBUTING.md +0 -0
  30. {kader-2.7.2 → kader-2.7.4}/LICENSE +0 -0
  31. {kader-2.7.2 → kader-2.7.4}/assets/architecture/cli_integration.mmd +0 -0
  32. {kader-2.7.2 → kader-2.7.4}/assets/architecture/cli_message_flow.mmd +0 -0
  33. {kader-2.7.2 → kader-2.7.4}/assets/architecture/cli_tool_confirmation.mmd +0 -0
  34. {kader-2.7.2 → kader-2.7.4}/assets/architecture/memory_flow.mmd +0 -0
  35. {kader-2.7.2 → kader-2.7.4}/assets/architecture/planner_executor_sequence.mmd +0 -0
  36. {kader-2.7.2 → kader-2.7.4}/assets/architecture/planner_executor_workflow.mmd +0 -0
  37. {kader-2.7.2 → kader-2.7.4}/assets/design/v2/code.html +0 -0
  38. {kader-2.7.2 → kader-2.7.4}/assets/design/v2/code1.html +0 -0
  39. {kader-2.7.2 → kader-2.7.4}/assets/design/v2/code2.html +0 -0
  40. {kader-2.7.2 → kader-2.7.4}/assets/design/v2/code3.html +0 -0
  41. {kader-2.7.2 → kader-2.7.4}/assets/design/v2/screen.png +0 -0
  42. {kader-2.7.2 → kader-2.7.4}/assets/design/v2/screen1.png +0 -0
  43. {kader-2.7.2 → kader-2.7.4}/assets/design/v2/screen2.png +0 -0
  44. {kader-2.7.2 → kader-2.7.4}/assets/design/v2/screen3.png +0 -0
  45. {kader-2.7.2 → kader-2.7.4}/cli/__init__.py +0 -0
  46. {kader-2.7.2 → kader-2.7.4}/cli/__main__.py +0 -0
  47. {kader-2.7.2 → kader-2.7.4}/cli/commands/__init__.py +0 -0
  48. {kader-2.7.2 → kader-2.7.4}/cli/commands/base.py +0 -0
  49. {kader-2.7.2 → kader-2.7.4}/cli/commands/initialize.py +0 -0
  50. {kader-2.7.2 → kader-2.7.4}/cli/utils.py +0 -0
  51. {kader-2.7.2 → kader-2.7.4}/docs/assets/imgs/kader-cli.png +0 -0
  52. {kader-2.7.2 → kader-2.7.4}/docs/core-framework/agents.md +0 -0
  53. {kader-2.7.2 → kader-2.7.4}/docs/core-framework/index.md +0 -0
  54. {kader-2.7.2 → kader-2.7.4}/docs/core-framework/memory.md +0 -0
  55. {kader-2.7.2 → kader-2.7.4}/docs/core-framework/providers.md +0 -0
  56. {kader-2.7.2 → kader-2.7.4}/docs/core-framework/tools.md +0 -0
  57. {kader-2.7.2 → kader-2.7.4}/docs/index.md +0 -0
  58. {kader-2.7.2 → kader-2.7.4}/examples/.gitignore +0 -0
  59. {kader-2.7.2 → kader-2.7.4}/examples/README.md +0 -0
  60. {kader-2.7.2 → kader-2.7.4}/examples/anthropic_example.py +0 -0
  61. {kader-2.7.2 → kader-2.7.4}/examples/google_example.py +0 -0
  62. {kader-2.7.2 → kader-2.7.4}/examples/memory_example.py +0 -0
  63. {kader-2.7.2 → kader-2.7.4}/examples/mistral_example.py +0 -0
  64. {kader-2.7.2 → kader-2.7.4}/examples/ollama_example.py +0 -0
  65. {kader-2.7.2 → kader-2.7.4}/examples/openai_compatible_example.py +0 -0
  66. {kader-2.7.2 → kader-2.7.4}/examples/planner_executor_example.py +0 -0
  67. {kader-2.7.2 → kader-2.7.4}/examples/planning_agent_example.py +0 -0
  68. {kader-2.7.2 → kader-2.7.4}/examples/python_developer/main.py +0 -0
  69. {kader-2.7.2 → kader-2.7.4}/examples/python_developer/template.yaml +0 -0
  70. {kader-2.7.2 → kader-2.7.4}/examples/react_agent_example.py +0 -0
  71. {kader-2.7.2 → kader-2.7.4}/examples/simple_agent.py +0 -0
  72. {kader-2.7.2 → kader-2.7.4}/examples/skills/hello_example.py +0 -0
  73. {kader-2.7.2 → kader-2.7.4}/examples/skills/react_agent.py +0 -0
  74. {kader-2.7.2 → kader-2.7.4}/examples/skills/skills/calculator/SKILL.md +0 -0
  75. {kader-2.7.2 → kader-2.7.4}/examples/skills/skills/calculator/scripts/calculate.py +0 -0
  76. {kader-2.7.2 → kader-2.7.4}/examples/skills/skills/github/SKILL.md +0 -0
  77. {kader-2.7.2 → kader-2.7.4}/examples/skills/skills/hello/SKILL.md +0 -0
  78. {kader-2.7.2 → kader-2.7.4}/examples/skills/skills/hello/scripts/hello.py +0 -0
  79. {kader-2.7.2 → kader-2.7.4}/examples/skills/skills/joke/SKILL.md +0 -0
  80. {kader-2.7.2 → kader-2.7.4}/examples/skills/skills/visualization/SKILL.md +0 -0
  81. {kader-2.7.2 → kader-2.7.4}/examples/todo_agent/main.py +0 -0
  82. {kader-2.7.2 → kader-2.7.4}/examples/tools_example.py +0 -0
  83. {kader-2.7.2 → kader-2.7.4}/kader/__init__.py +0 -0
  84. {kader-2.7.2 → kader-2.7.4}/kader/agent/__init__.py +0 -0
  85. {kader-2.7.2 → kader-2.7.4}/kader/agent/agents.py +0 -0
  86. {kader-2.7.2 → kader-2.7.4}/kader/agent/base.py +0 -0
  87. {kader-2.7.2 → kader-2.7.4}/kader/agent/logger.py +0 -0
  88. {kader-2.7.2 → kader-2.7.4}/kader/config.py +0 -0
  89. {kader-2.7.2 → kader-2.7.4}/kader/memory/__init__.py +0 -0
  90. {kader-2.7.2 → kader-2.7.4}/kader/memory/compression.py +0 -0
  91. {kader-2.7.2 → kader-2.7.4}/kader/memory/conversation.py +0 -0
  92. {kader-2.7.2 → kader-2.7.4}/kader/memory/session.py +0 -0
  93. {kader-2.7.2 → kader-2.7.4}/kader/memory/state.py +0 -0
  94. {kader-2.7.2 → kader-2.7.4}/kader/memory/summarization.py +0 -0
  95. {kader-2.7.2 → kader-2.7.4}/kader/memory/types.py +0 -0
  96. {kader-2.7.2 → kader-2.7.4}/kader/prompts/__init__.py +0 -0
  97. {kader-2.7.2 → kader-2.7.4}/kader/prompts/agent_prompts.py +0 -0
  98. {kader-2.7.2 → kader-2.7.4}/kader/prompts/base.py +0 -0
  99. {kader-2.7.2 → kader-2.7.4}/kader/prompts/cli_prompts.py +0 -0
  100. {kader-2.7.2 → kader-2.7.4}/kader/prompts/templates/command_agent.j2 +0 -0
  101. {kader-2.7.2 → kader-2.7.4}/kader/prompts/templates/executor_agent.j2 +0 -0
  102. {kader-2.7.2 → kader-2.7.4}/kader/prompts/templates/init_command_prompt.j2 +0 -0
  103. {kader-2.7.2 → kader-2.7.4}/kader/prompts/templates/kader_planner.j2 +0 -0
  104. {kader-2.7.2 → kader-2.7.4}/kader/prompts/templates/planning_agent.j2 +0 -0
  105. {kader-2.7.2 → kader-2.7.4}/kader/prompts/templates/react_agent.j2 +0 -0
  106. {kader-2.7.2 → kader-2.7.4}/kader/providers/__init__.py +0 -0
  107. {kader-2.7.2 → kader-2.7.4}/kader/providers/anthropic.py +0 -0
  108. {kader-2.7.2 → kader-2.7.4}/kader/providers/base.py +0 -0
  109. {kader-2.7.2 → kader-2.7.4}/kader/providers/google.py +0 -0
  110. {kader-2.7.2 → kader-2.7.4}/kader/providers/mistral.py +0 -0
  111. {kader-2.7.2 → kader-2.7.4}/kader/providers/mock.py +0 -0
  112. {kader-2.7.2 → kader-2.7.4}/kader/providers/openai_compatible.py +0 -0
  113. {kader-2.7.2 → kader-2.7.4}/kader/tools/README.md +0 -0
  114. {kader-2.7.2 → kader-2.7.4}/kader/tools/__init__.py +0 -0
  115. {kader-2.7.2 → kader-2.7.4}/kader/tools/agent.py +0 -0
  116. {kader-2.7.2 → kader-2.7.4}/kader/tools/base.py +0 -0
  117. {kader-2.7.2 → kader-2.7.4}/kader/tools/commands.py +0 -0
  118. {kader-2.7.2 → kader-2.7.4}/kader/tools/exec_commands.py +0 -0
  119. {kader-2.7.2 → kader-2.7.4}/kader/tools/filesys.py +0 -0
  120. {kader-2.7.2 → kader-2.7.4}/kader/tools/filesystem.py +0 -0
  121. {kader-2.7.2 → kader-2.7.4}/kader/tools/protocol.py +0 -0
  122. {kader-2.7.2 → kader-2.7.4}/kader/tools/rag.py +0 -0
  123. {kader-2.7.2 → kader-2.7.4}/kader/tools/skills.py +0 -0
  124. {kader-2.7.2 → kader-2.7.4}/kader/tools/todo.py +0 -0
  125. {kader-2.7.2 → kader-2.7.4}/kader/tools/utils.py +0 -0
  126. {kader-2.7.2 → kader-2.7.4}/kader/tools/web.py +0 -0
  127. {kader-2.7.2 → kader-2.7.4}/kader/utils/__init__.py +0 -0
  128. {kader-2.7.2 → kader-2.7.4}/kader/utils/checkpointer.py +0 -0
  129. {kader-2.7.2 → kader-2.7.4}/kader/utils/context_aggregator.py +0 -0
  130. {kader-2.7.2 → kader-2.7.4}/kader/utils/ignore.py +0 -0
  131. {kader-2.7.2 → kader-2.7.4}/kader/workflows/__init__.py +0 -0
  132. {kader-2.7.2 → kader-2.7.4}/kader/workflows/base.py +0 -0
  133. {kader-2.7.2 → kader-2.7.4}/kader/workflows/planner_executor.py +0 -0
  134. {kader-2.7.2 → kader-2.7.4}/mkdocs.yml +0 -0
  135. {kader-2.7.2 → kader-2.7.4}/tests/conftest.py +0 -0
  136. {kader-2.7.2 → kader-2.7.4}/tests/providers/test_anthropic_provider.py +0 -0
  137. {kader-2.7.2 → kader-2.7.4}/tests/providers/test_google.py +0 -0
  138. {kader-2.7.2 → kader-2.7.4}/tests/providers/test_mistral_provider.py +0 -0
  139. {kader-2.7.2 → kader-2.7.4}/tests/providers/test_mock.py +0 -0
  140. {kader-2.7.2 → kader-2.7.4}/tests/providers/test_ollama.py +0 -0
  141. {kader-2.7.2 → kader-2.7.4}/tests/providers/test_openai_compatible_provider.py +0 -0
  142. {kader-2.7.2 → kader-2.7.4}/tests/providers/test_providers_base.py +0 -0
  143. {kader-2.7.2 → kader-2.7.4}/tests/test_agent_logger.py +0 -0
  144. {kader-2.7.2 → kader-2.7.4}/tests/test_agent_logger_integration.py +0 -0
  145. {kader-2.7.2 → kader-2.7.4}/tests/test_base_agent.py +0 -0
  146. {kader-2.7.2 → kader-2.7.4}/tests/test_file_memory.py +0 -0
  147. {kader-2.7.2 → kader-2.7.4}/tests/test_hierarchical_memory.py +0 -0
  148. {kader-2.7.2 → kader-2.7.4}/tests/test_todo_tool.py +0 -0
  149. {kader-2.7.2 → kader-2.7.4}/tests/tools/test_agent_tool.py +0 -0
  150. {kader-2.7.2 → kader-2.7.4}/tests/tools/test_agent_tool_persistence.py +0 -0
  151. {kader-2.7.2 → kader-2.7.4}/tests/tools/test_agent_tool_skills.py +0 -0
  152. {kader-2.7.2 → kader-2.7.4}/tests/tools/test_exec_commands.py +0 -0
  153. {kader-2.7.2 → kader-2.7.4}/tests/tools/test_filesys_tools.py +0 -0
  154. {kader-2.7.2 → kader-2.7.4}/tests/tools/test_filesystem_tools.py +0 -0
  155. {kader-2.7.2 → kader-2.7.4}/tests/tools/test_rag.py +0 -0
  156. {kader-2.7.2 → kader-2.7.4}/tests/tools/test_skills.py +0 -0
  157. {kader-2.7.2 → kader-2.7.4}/tests/tools/test_tools_base.py +0 -0
  158. {kader-2.7.2 → kader-2.7.4}/tests/tools/test_web.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kader
3
- Version: 2.7.2
3
+ Version: 2.7.4
4
4
  Summary: kader coding agent
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.11
@@ -33,6 +33,7 @@ Kader is an intelligent coding agent designed to assist with software developmen
33
33
 
34
34
  - 🤖 **AI-powered Code Assistance** - Support for multiple LLM providers:
35
35
  - **Ollama**: Local LLM execution for privacy and speed.
36
+ - **Ollama Cloud**: Cloud-based models via [ollama.com](https://ollama.com).
36
37
  - **Google Gemini**: Cloud-based powerful models via the Google GenAI SDK.
37
38
  - **Anthropic**: High-quality Claude models via the Anthropic SDK.
38
39
  - 🖥️ **Interactive CLI** - Modern terminal interface built with Rich & prompt_toolkit:
@@ -125,7 +126,7 @@ When the kader module is imported for the first time, it automatically creates a
125
126
  ### Environment Variables
126
127
 
127
128
  The application automatically loads environment variables from `~/.kader/.env`:
128
- - `OLLAMA_API_KEY`: API key for Ollama service (if applicable).
129
+ - `OLLAMA_API_KEY`: API key for Ollama Cloud (for cloud models at ollama.com). Get your key from https://ollama.com/settings
129
130
  - `GOOGLE_API_KEY`: API key for Google Gemini (required for Google Provider).
130
131
  - `ANTHROPIC_API_KEY`: API key for Anthropic Claude (required for Anthropic Provider).
131
132
  - Additional variables can be added to the `.env` file and will be automatically loaded.
@@ -143,7 +144,7 @@ Kader stores data in `~/.kader/`:
143
144
  | Command | Description |
144
145
  |---------|-------------|
145
146
  | `/help` | Show command reference |
146
- | `/models` | Show available models (Ollama, Google & Anthropic) |
147
+ | `/models` | Show available models (Ollama local & cloud, Google & Anthropic) |
147
148
  | `/clear` | Clear conversation |
148
149
  | `/save` | Save current session |
149
150
  | `/load <id>` | Load a saved session |
@@ -211,6 +212,7 @@ Kader provides a robust agent architecture:
211
212
 
212
213
  Kader supports multiple backends:
213
214
  - **OllamaProvider**: Connects to locally running Ollama instances.
215
+ - **OllamaProvider (Cloud)**: Connects to cloud models at ollama.com (requires OLLAMA_API_KEY).
214
216
  - **GoogleProvider**: High-performance access to Gemini models.
215
217
  - **AnthropicProvider**: Full support for Claude models.
216
218
 
@@ -6,6 +6,7 @@ Kader is an intelligent coding agent designed to assist with software developmen
6
6
 
7
7
  - 🤖 **AI-powered Code Assistance** - Support for multiple LLM providers:
8
8
  - **Ollama**: Local LLM execution for privacy and speed.
9
+ - **Ollama Cloud**: Cloud-based models via [ollama.com](https://ollama.com).
9
10
  - **Google Gemini**: Cloud-based powerful models via the Google GenAI SDK.
10
11
  - **Anthropic**: High-quality Claude models via the Anthropic SDK.
11
12
  - 🖥️ **Interactive CLI** - Modern terminal interface built with Rich & prompt_toolkit:
@@ -98,7 +99,7 @@ When the kader module is imported for the first time, it automatically creates a
98
99
  ### Environment Variables
99
100
 
100
101
  The application automatically loads environment variables from `~/.kader/.env`:
101
- - `OLLAMA_API_KEY`: API key for Ollama service (if applicable).
102
+ - `OLLAMA_API_KEY`: API key for Ollama Cloud (for cloud models at ollama.com). Get your key from https://ollama.com/settings
102
103
  - `GOOGLE_API_KEY`: API key for Google Gemini (required for Google Provider).
103
104
  - `ANTHROPIC_API_KEY`: API key for Anthropic Claude (required for Anthropic Provider).
104
105
  - Additional variables can be added to the `.env` file and will be automatically loaded.
@@ -116,7 +117,7 @@ Kader stores data in `~/.kader/`:
116
117
  | Command | Description |
117
118
  |---------|-------------|
118
119
  | `/help` | Show command reference |
119
- | `/models` | Show available models (Ollama, Google & Anthropic) |
120
+ | `/models` | Show available models (Ollama local & cloud, Google & Anthropic) |
120
121
  | `/clear` | Clear conversation |
121
122
  | `/save` | Save current session |
122
123
  | `/load <id>` | Load a saved session |
@@ -184,6 +185,7 @@ Kader provides a robust agent architecture:
184
185
 
185
186
  Kader supports multiple backends:
186
187
  - **OllamaProvider**: Connects to locally running Ollama instances.
188
+ - **OllamaProvider (Cloud)**: Connects to cloud models at ollama.com (requires OLLAMA_API_KEY).
187
189
  - **GoogleProvider**: High-performance access to Gemini models.
188
190
  - **AnthropicProvider**: Full support for Claude models.
189
191
 
@@ -16,7 +16,7 @@ A modern terminal-based AI coding assistant built with [Rich](https://github.com
16
16
 
17
17
  ## Prerequisites
18
18
 
19
- - [Ollama](https://ollama.ai/) running locally (for local models)
19
+ - [Ollama](https://ollama.ai/) running locally (for local models) or [Ollama Cloud](https://ollama.com) (for cloud models)
20
20
  - Python 3.11 or higher
21
21
  - [uv](https://docs.astral.sh/uv/) package manager (recommended) or [pip](https://pypi.org/project/pip/)
22
22
  - API keys for cloud providers (optional, based on model selection)
@@ -207,7 +207,8 @@ DEFAULT_MODEL = "minimax-m2.5:cloud" # Default model
207
207
 
208
208
  | Provider | Format | Example |
209
209
  |----------|--------|---------|
210
- | Ollama | `ollama:model` | `ollama:llama3` |
210
+ | Ollama (local) | `ollama:model` | `ollama:llama3` |
211
+ | Ollama (cloud) | `ollama:model:cloud` | `ollama:minimax-m2.5:cloud` |
211
212
  | Google Gemini | `google:model` | `google:gemini-2.5-flash` |
212
213
  | Mistral | `mistral:model` | `mistral:small-3.1` |
213
214
  | Anthropic | `anthropic:model` | `anthropic:claude-3.5-sonnet` |
@@ -223,10 +224,16 @@ DEFAULT_MODEL = "minimax-m2.5:cloud" # Default model
223
224
  Set API keys for cloud providers:
224
225
 
225
226
  ```bash
227
+ # Ollama Cloud (get from https://ollama.com/settings)
228
+ export OLLAMA_API_KEY="your-ollama-api-key"
229
+
230
+ # Google Gemini
226
231
  export GOOGLE_API_KEY="your-google-api-key"
232
+
233
+ # Other providers...
227
234
  export ANTHROPIC_API_KEY="your-anthropic-api-key"
228
235
  export MISTRAL_API_KEY="your-mistral-api-key"
229
- export OPENAI_API_KEY="your-openai-api-key"
236
+ export OPENAI_API_KEY="your-openapi-key"
230
237
  export MOONSHOT_API_KEY="your-kimi-api-key"
231
238
  export ZAI_API_KEY="your-glm-api-key"
232
239
  export OPENROUTER_API_KEY="your-openrouter-api-key"
@@ -612,139 +612,28 @@ class KaderApp:
612
612
  self.console.print(f"\n [kader.orange]\\[>] Executing:[/kader.orange] `{cmd}`")
613
613
 
614
614
  try:
615
- output = await self._run_terminal_command_pty(cmd)
615
+ output = await self._run_terminal_command_direct(cmd)
616
616
 
617
617
  if not output:
618
- self.console.print(
619
- " [dim]Command executed successfully with no output.[/dim]"
618
+ output = "Command executed successfully with no output."
619
+
620
+ self.console.print()
621
+ self.console.print(
622
+ Panel(
623
+ output,
624
+ title="[kader.orange]Terminal Output[/kader.orange]",
625
+ border_style="dark_orange",
626
+ padding=(0, 1),
620
627
  )
628
+ )
621
629
 
622
630
  except Exception as e:
623
631
  self.console.print(
624
632
  rf" [kader.red]\[-][/kader.red] Error executing command: {e}"
625
633
  )
626
634
 
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
635
  async def _run_terminal_command_direct(self, command: str) -> str:
747
- """Run a terminal command directly without PTY (fallback)."""
636
+ """Run a terminal command directly without PTY."""
748
637
 
749
638
  try:
750
639
  process = await asyncio.create_subprocess_shell(
@@ -16,6 +16,7 @@ from kader.providers import (
16
16
  OpenAIProviderConfig,
17
17
  )
18
18
  from kader.providers.base import BaseLLMProvider, ModelConfig
19
+ from kader.providers.ollama import DEFAULT_CLOUD_HOST, DEFAULT_LOCAL_HOST
19
20
 
20
21
 
21
22
  class LLMProviderFactory:
@@ -168,6 +169,23 @@ class LLMProviderFactory:
168
169
  default_config=config,
169
170
  )
170
171
 
172
+ # Handle Ollama provider (needs api_key for cloud models)
173
+ if provider_name == "ollama":
174
+ ollama_api_key = os.environ.get("OLLAMA_API_KEY")
175
+
176
+ # Strip :cloud suffix if present (we add it for display purposes)
177
+ actual_model_name = model_name.replace(":cloud", "")
178
+
179
+ # Use cloud host if API key is available
180
+ host = DEFAULT_CLOUD_HOST if ollama_api_key else None
181
+
182
+ return OllamaProvider(
183
+ model=actual_model_name,
184
+ host=host,
185
+ api_key=ollama_api_key,
186
+ default_config=config,
187
+ )
188
+
171
189
  return provider_class(model=model_name, default_config=config)
172
190
 
173
191
  @classmethod
@@ -226,13 +244,29 @@ class LLMProviderFactory:
226
244
  """
227
245
  models: dict[str, list[str]] = {}
228
246
 
229
- # Get Ollama models
247
+ # Get Ollama models (local first)
230
248
  try:
231
- ollama_models = OllamaProvider.get_supported_models()
249
+ ollama_models = OllamaProvider.get_supported_models(host=DEFAULT_LOCAL_HOST)
232
250
  models["ollama"] = [f"ollama:{m}" for m in ollama_models]
233
251
  except Exception:
234
252
  models["ollama"] = []
235
253
 
254
+ # Also try Ollama Cloud if API key is available
255
+ ollama_api_key = os.environ.get("OLLAMA_API_KEY")
256
+ if ollama_api_key:
257
+ try:
258
+ cloud_models = OllamaProvider.get_supported_models(
259
+ host=DEFAULT_CLOUD_HOST, api_key=ollama_api_key
260
+ )
261
+ existing = set(models.get("ollama", []))
262
+ for m in cloud_models:
263
+ # Add :cloud suffix to distinguish from local models
264
+ prefixed = f"ollama:{m}:cloud"
265
+ if prefixed.replace(":cloud", "") not in existing:
266
+ models["ollama"].append(prefixed)
267
+ except Exception:
268
+ pass # Cloud failed, but local models still available
269
+
236
270
  # Get Google models
237
271
  try:
238
272
  google_models = GoogleProvider.get_supported_models()
@@ -339,6 +373,23 @@ class LLMProviderFactory:
339
373
  # Try to get models to verify provider is working
340
374
  try:
341
375
  provider_class = cls.PROVIDERS[provider_name]
376
+
377
+ # Special handling for Ollama: check both local and cloud
378
+ if provider_name == "ollama":
379
+ local_models = provider_class.get_supported_models(
380
+ host=DEFAULT_LOCAL_HOST
381
+ )
382
+ if local_models:
383
+ return True
384
+ # Also try cloud if API key is available
385
+ ollama_api_key = os.environ.get("OLLAMA_API_KEY")
386
+ if ollama_api_key:
387
+ cloud_models = provider_class.get_supported_models(
388
+ host=DEFAULT_CLOUD_HOST, api_key=ollama_api_key
389
+ )
390
+ return len(cloud_models) > 0
391
+ return False
392
+
342
393
  models = provider_class.get_supported_models()
343
394
  return len(models) > 0
344
395
  except Exception:
@@ -158,7 +158,8 @@ The model selection interface allows you to:
158
158
 
159
159
  | Provider | Format | Example |
160
160
  |----------|--------|---------|
161
- | Ollama | `ollama:model` | `ollama:llama3` |
161
+ | Ollama (local) | `ollama:model` | `ollama:llama3` |
162
+ | Ollama (cloud) | `ollama:model:cloud` | `ollama:minimax-m2.5:cloud` |
162
163
  | Google Gemini | `google:model` | `google:gemini-2.5-flash` |
163
164
  | Mistral | `mistral:model` | `mistral:small-3.1` |
164
165
  | Anthropic | `anthropic:model` | `anthropic:claude-3.5-sonnet` |
@@ -172,7 +173,13 @@ The model selection interface allows you to:
172
173
  ### Setting API Keys
173
174
 
174
175
  ```bash
176
+ # Ollama Cloud (get from https://ollama.com/settings)
177
+ export OLLAMA_API_KEY="your-ollama-api-key"
178
+
179
+ # Google Gemini
175
180
  export GOOGLE_API_KEY="your-google-api-key"
181
+
182
+ # Other providers...
176
183
  export ANTHROPIC_API_KEY="your-anthropic-api-key"
177
184
  export MISTRAL_API_KEY="your-mistral-api-key"
178
185
  export OPENAI_API_KEY="your-openai-api-key"
@@ -7,6 +7,7 @@ Kader can be configured through environment variables, YAML files, and the `.kad
7
7
  | Variable | Description | Required |
8
8
  |----------|-------------|----------|
9
9
  | `KADER_DIR` | Kader config directory (default: `~/.kader`) | No |
10
+ | `OLLAMA_API_KEY` | Ollama Cloud API key (get from https://ollama.com/settings) | For Ollama Cloud |
10
11
  | `GEMINI_API_KEY` | Google Gemini API key | For Google Provider |
11
12
  | `MISTRAL_API_KEY` | Mistral API key | For Mistral Provider |
12
13
  | `ANTHROPIC_API_KEY` | Anthropic API key | For Anthropic Provider |
@@ -57,7 +58,7 @@ agent = BaseAgent.from_yaml("agent.yaml")
57
58
 
58
59
  ## Provider Configuration Format
59
60
 
60
- ### Ollama
61
+ ### Ollama (Local)
61
62
 
62
63
  ```yaml
63
64
  provider:
@@ -67,6 +68,16 @@ provider:
67
68
  timeout: 120
68
69
  ```
69
70
 
71
+ ### Ollama (Cloud)
72
+
73
+ ```yaml
74
+ provider:
75
+ provider: ollama
76
+ model: minimax-m2.5
77
+ base_url: "https://ollama.com"
78
+ # api_key: "your-ollama-api-key" # Or set OLLAMA_API_KEY env var
79
+ ```
80
+
70
81
  ### Google Gemini
71
82
 
72
83
  ```yaml
@@ -54,6 +54,9 @@ ollama pull qwen2.5
54
54
  Create a `.env` file in `~/.kader/.env`:
55
55
 
56
56
  ```bash
57
+ # Ollama Cloud (get from https://ollama.com/settings)
58
+ OLLAMA_API_KEY='your-api-key'
59
+
57
60
  # Google Gemini
58
61
  GEMINI_API_KEY='your-api-key'
59
62
 
@@ -41,6 +41,9 @@ pip install -e .
41
41
  For cloud providers, create a `.env` file in `~/.kader/.env`:
42
42
 
43
43
  ```bash
44
+ # Ollama Cloud (get from https://ollama.com/settings)
45
+ OLLAMA_API_KEY='your-api-key'
46
+
44
47
  # Google Gemini
45
48
  GEMINI_API_KEY='your-api-key'
46
49
 
@@ -66,6 +69,14 @@ ollama pull llama3.2
66
69
  ollama pull qwen2.5
67
70
  ```
68
71
 
72
+ For cloud models via Ollama Cloud, get an API key from https://ollama.com/settings and set it in your environment:
73
+
74
+ ```bash
75
+ export OLLAMA_API_KEY="your-api-key"
76
+ ```
77
+
78
+ Then use cloud models with the `OllamaProvider` by specifying the cloud host:
79
+
69
80
  ## Core Concepts
70
81
 
71
82
  ### 1. Providers
@@ -302,10 +313,22 @@ For local LLM inference. Best for privacy, speed, and offline capability.
302
313
  ```python
303
314
  from kader.providers import OllamaProvider, Message
304
315
 
316
+ # Local model (default - connects to localhost:11434)
305
317
  provider = OllamaProvider(
306
318
  model="llama3.2", # Model name
307
- base_url="http://localhost:11434", # Custom endpoint
308
- timeout=120, # Request timeout
319
+ )
320
+
321
+ # Cloud model (requires OLLAMA_API_KEY environment variable)
322
+ provider = OllamaProvider(
323
+ model="minimax-m2.5", # Cloud model name
324
+ host="https://ollama.com", # Cloud endpoint
325
+ api_key="your-api-key", # Or set OLLAMA_API_KEY env var
326
+ )
327
+
328
+ # Custom local endpoint
329
+ provider = OllamaProvider(
330
+ model="llama3.2",
331
+ host="http://localhost:11434",
309
332
  )
310
333
 
311
334
  # Basic invocation
@@ -907,6 +930,7 @@ python ollama_example.py
907
930
  | Variable | Description |
908
931
  |----------|-------------|
909
932
  | `KADER_DIR` | Kader config directory (default: `~/.kader`) |
933
+ | `OLLAMA_API_KEY` | Ollama Cloud API key (for cloud models at ollama.com) |
910
934
  | `GEMINI_API_KEY` | Google Gemini API key |
911
935
  | `MISTRAL_API_KEY` | Mistral API key |
912
936
  | `OPENAI_API_KEY` | OpenAI API key |
@@ -1,9 +1,11 @@
1
1
  """
2
2
  Ollama LLM Provider implementation.
3
3
 
4
- Provides synchronous and asynchronous access to Ollama models.
4
+ Provides synchronous and asynchronous access to Ollama models,
5
+ including both local and cloud (ollama.com) models.
5
6
  """
6
7
 
8
+ import os
7
9
  from typing import AsyncIterator, Iterator
8
10
 
9
11
  from ollama import AsyncClient, Client
@@ -20,6 +22,9 @@ from .base import (
20
22
  Usage,
21
23
  )
22
24
 
25
+ DEFAULT_LOCAL_HOST = "http://localhost:11434"
26
+ DEFAULT_CLOUD_HOST = "https://ollama.com"
27
+
23
28
 
24
29
  class OllamaProvider(BaseLLMProvider):
25
30
  """
@@ -38,6 +43,7 @@ class OllamaProvider(BaseLLMProvider):
38
43
  self,
39
44
  model: str,
40
45
  host: str | None = None,
46
+ api_key: str | None = None,
41
47
  default_config: ModelConfig | None = None,
42
48
  ) -> None:
43
49
  """
@@ -46,12 +52,23 @@ class OllamaProvider(BaseLLMProvider):
46
52
  Args:
47
53
  model: The Ollama model identifier (e.g., "llama3.2", "gpt-oss:120b-cloud")
48
54
  host: Optional Ollama server host (default: http://localhost:11434)
55
+ api_key: Optional API key for Ollama Cloud (ollama.com)
49
56
  default_config: Default configuration for all requests
50
57
  """
51
58
  super().__init__(model=model, default_config=default_config)
52
59
  self._host = host
53
- self._client = Client(host=host) if host else Client()
54
- self._async_client = AsyncClient(host=host) if host else AsyncClient()
60
+ self._api_key = api_key or os.environ.get("OLLAMA_API_KEY")
61
+
62
+ headers = None
63
+ if self._api_key:
64
+ headers = {"Authorization": f"Bearer {self._api_key}"}
65
+
66
+ if host:
67
+ self._client = Client(host=host, headers=headers)
68
+ self._async_client = AsyncClient(host=host, headers=headers)
69
+ else:
70
+ self._client = Client()
71
+ self._async_client = AsyncClient()
55
72
 
56
73
  def _convert_messages(self, messages: list[Message]) -> list[dict]:
57
74
  """Convert Message objects to Ollama format."""
@@ -416,18 +433,28 @@ class OllamaProvider(BaseLLMProvider):
416
433
  return None
417
434
 
418
435
  @classmethod
419
- def get_supported_models(cls, host: str | None = None) -> list[str]:
436
+ def get_supported_models(
437
+ cls, host: str | None = None, api_key: str | None = None
438
+ ) -> list[str]:
420
439
  """
421
440
  Get list of models available on the Ollama server.
422
441
 
423
442
  Args:
424
- host: Optional Ollama server host
443
+ host: Optional Ollama server host (e.g., "http://localhost:11434" or "https://ollama.com")
444
+ api_key: Optional API key for Ollama Cloud
425
445
 
426
446
  Returns:
427
447
  List of available model names
428
448
  """
449
+ api_key = api_key or os.environ.get("OLLAMA_API_KEY")
450
+ headers = None
451
+ if api_key:
452
+ headers = {"Authorization": f"Bearer {api_key}"}
453
+
429
454
  try:
430
- client = Client(host=host) if host else Client()
455
+ client = (
456
+ Client(host=host, headers=headers) if host else Client(headers=headers)
457
+ )
431
458
  response = client.list()
432
459
  models = [model.model for model in response.models]
433
460
  models_config = {}
@@ -444,4 +471,4 @@ class OllamaProvider(BaseLLMProvider):
444
471
 
445
472
  def list_models(self) -> list[str]:
446
473
  """List all available models on the Ollama server."""
447
- return self.get_supported_models(self._host)
474
+ return self.get_supported_models(self._host, self._api_key)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kader"
3
- version = "2.7.2"
3
+ version = "2.7.4"
4
4
  description = "kader coding agent"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -629,7 +629,7 @@ wheels = [
629
629
 
630
630
  [[package]]
631
631
  name = "kader"
632
- version = "2.7.2"
632
+ version = "2.7.4"
633
633
  source = { editable = "." }
634
634
  dependencies = [
635
635
  { name = "aiofiles" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes