aru-code 0.1.0__tar.gz → 0.3.0__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 (49) hide show
  1. {aru_code-0.1.0 → aru_code-0.3.0}/LICENSE +21 -21
  2. {aru_code-0.1.0 → aru_code-0.3.0}/PKG-INFO +19 -60
  3. {aru_code-0.1.0 → aru_code-0.3.0}/README.md +15 -58
  4. aru_code-0.3.0/aru/__init__.py +1 -0
  5. {aru_code-0.1.0 → aru_code-0.3.0}/aru/agents/base.py +10 -1
  6. aru_code-0.3.0/aru/agents/executor.py +58 -0
  7. {aru_code-0.1.0 → aru_code-0.3.0}/aru/cli.py +81 -1
  8. {aru_code-0.1.0 → aru_code-0.3.0}/aru/config.py +2 -0
  9. {aru_code-0.1.0 → aru_code-0.3.0}/aru/providers.py +1 -0
  10. {aru_code-0.1.0 → aru_code-0.3.0}/aru/tools/ast_tools.py +2 -0
  11. {aru_code-0.1.0 → aru_code-0.3.0}/aru/tools/gitignore.py +2 -0
  12. {aru_code-0.1.0 → aru_code-0.3.0}/aru/tools/mcp_client.py +2 -0
  13. {aru_code-0.1.0 → aru_code-0.3.0}/aru/tools/tasklist.py +2 -0
  14. {aru_code-0.1.0 → aru_code-0.3.0}/aru_code.egg-info/PKG-INFO +19 -60
  15. {aru_code-0.1.0 → aru_code-0.3.0}/pyproject.toml +5 -3
  16. aru_code-0.1.0/aru/__init__.py +0 -1
  17. aru_code-0.1.0/aru/agents/executor.py +0 -32
  18. {aru_code-0.1.0 → aru_code-0.3.0}/aru/agents/__init__.py +0 -0
  19. {aru_code-0.1.0 → aru_code-0.3.0}/aru/agents/planner.py +0 -0
  20. {aru_code-0.1.0 → aru_code-0.3.0}/aru/context.py +0 -0
  21. {aru_code-0.1.0 → aru_code-0.3.0}/aru/tools/__init__.py +0 -0
  22. {aru_code-0.1.0 → aru_code-0.3.0}/aru/tools/codebase.py +0 -0
  23. {aru_code-0.1.0 → aru_code-0.3.0}/aru/tools/ranker.py +0 -0
  24. {aru_code-0.1.0 → aru_code-0.3.0}/aru_code.egg-info/SOURCES.txt +0 -0
  25. {aru_code-0.1.0 → aru_code-0.3.0}/aru_code.egg-info/dependency_links.txt +0 -0
  26. {aru_code-0.1.0 → aru_code-0.3.0}/aru_code.egg-info/entry_points.txt +0 -0
  27. {aru_code-0.1.0 → aru_code-0.3.0}/aru_code.egg-info/requires.txt +0 -0
  28. {aru_code-0.1.0 → aru_code-0.3.0}/aru_code.egg-info/top_level.txt +0 -0
  29. {aru_code-0.1.0 → aru_code-0.3.0}/setup.cfg +0 -0
  30. {aru_code-0.1.0 → aru_code-0.3.0}/tests/test_agents_base.py +0 -0
  31. {aru_code-0.1.0 → aru_code-0.3.0}/tests/test_ast_tools.py +0 -0
  32. {aru_code-0.1.0 → aru_code-0.3.0}/tests/test_cli.py +0 -0
  33. {aru_code-0.1.0 → aru_code-0.3.0}/tests/test_cli_advanced.py +0 -0
  34. {aru_code-0.1.0 → aru_code-0.3.0}/tests/test_cli_base.py +0 -0
  35. {aru_code-0.1.0 → aru_code-0.3.0}/tests/test_cli_completers.py +0 -0
  36. {aru_code-0.1.0 → aru_code-0.3.0}/tests/test_cli_new.py +0 -0
  37. {aru_code-0.1.0 → aru_code-0.3.0}/tests/test_cli_run_cli.py +0 -0
  38. {aru_code-0.1.0 → aru_code-0.3.0}/tests/test_cli_session.py +0 -0
  39. {aru_code-0.1.0 → aru_code-0.3.0}/tests/test_cli_shell.py +0 -0
  40. {aru_code-0.1.0 → aru_code-0.3.0}/tests/test_codebase.py +0 -0
  41. {aru_code-0.1.0 → aru_code-0.3.0}/tests/test_config.py +0 -0
  42. {aru_code-0.1.0 → aru_code-0.3.0}/tests/test_context.py +0 -0
  43. {aru_code-0.1.0 → aru_code-0.3.0}/tests/test_executor.py +0 -0
  44. {aru_code-0.1.0 → aru_code-0.3.0}/tests/test_gitignore.py +0 -0
  45. {aru_code-0.1.0 → aru_code-0.3.0}/tests/test_main.py +0 -0
  46. {aru_code-0.1.0 → aru_code-0.3.0}/tests/test_mcp_client.py +0 -0
  47. {aru_code-0.1.0 → aru_code-0.3.0}/tests/test_planner.py +0 -0
  48. {aru_code-0.1.0 → aru_code-0.3.0}/tests/test_providers.py +0 -0
  49. {aru_code-0.1.0 → aru_code-0.3.0}/tests/test_ranker.py +0 -0
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Estevao
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Estevao
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aru-code
3
- Version: 0.1.0
3
+ Version: 0.3.0
4
4
  Summary: A Claude Code clone built with Agno agents
5
5
  Author-email: Estevao <estevaofon@gmail.com>
6
6
  License-Expression: MIT
@@ -11,9 +11,11 @@ Keywords: ai,coding-assistant,claude,cli,agents
11
11
  Classifier: Development Status :: 3 - Alpha
12
12
  Classifier: Environment :: Console
13
13
  Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
14
16
  Classifier: Programming Language :: Python :: 3.13
15
17
  Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
16
- Requires-Python: >=3.13
18
+ Requires-Python: >=3.11
17
19
  Description-Content-Type: text/markdown
18
20
  License-File: LICENSE
19
21
  Requires-Dist: agno<3,>=2.5.10
@@ -50,6 +52,10 @@ An intelligent coding assistant for the terminal, powered by LLMs and [Agno](htt
50
52
 
51
53
 
52
54
 
55
+ https://github.com/user-attachments/assets/17674bfc-3d49-4e25-bdc7-dc445d5a089d
56
+
57
+
58
+
53
59
  ## Highlights
54
60
 
55
61
  - **Multi-Agent Architecture** — Specialized agents for planning, execution, and conversation
@@ -65,7 +71,7 @@ An intelligent coding assistant for the terminal, powered by LLMs and [Agno](htt
65
71
  ### 1. Install
66
72
 
67
73
  ```bash
68
- pip install -e .
74
+ pip install aru-code
69
75
  ```
70
76
 
71
77
  > **Requirements:** Python 3.13+
@@ -74,13 +80,7 @@ pip install -e .
74
80
 
75
81
  Aru uses **Claude Sonnet 4.6** from Anthropic as the default model. You need an [Anthropic API key](https://console.anthropic.com/) to get started.
76
82
 
77
- Create a `.env` file in the project root:
78
-
79
- ```bash
80
- cp .env.example .env
81
- ```
82
-
83
- Edit the `.env` with your key:
83
+ Set your API key as an environment variable or create a `.env` file in your project directory:
84
84
 
85
85
  ```env
86
86
  ANTHROPIC_API_KEY=sk-ant-your-key-here
@@ -94,50 +94,7 @@ ANTHROPIC_API_KEY=sk-ant-your-key-here
94
94
  aru
95
95
  ```
96
96
 
97
- ### Global Installation (run `aru` from anywhere)
98
-
99
- To use aru as a global command in the terminal, create a dedicated virtual environment and a wrapper script:
100
-
101
- <details>
102
- <summary><strong>Windows</strong></summary>
103
-
104
- 1. Create the virtual environment and install:
105
- ```bash
106
- python -m venv C:\aru-env
107
- C:\aru-env\Scripts\pip install -e C:\path\to\aru
108
- ```
109
-
110
- 2. Create `aru.bat` in a folder on your `PATH` (e.g., `C:\Users\<user>\bin\`):
111
- ```bat
112
- @echo off
113
- C:\aru-env\Scripts\python -m aru.cli %*
114
- ```
115
-
116
- </details>
117
-
118
- <details>
119
- <summary><strong>Linux / macOS</strong></summary>
120
-
121
- 1. Create the virtual environment and install:
122
- ```bash
123
- python3 -m venv ~/.aru-env
124
- ~/.aru-env/bin/pip install -e /path/to/aru
125
- ```
126
-
127
- 2. Create the script `~/.local/bin/aru`:
128
- ```bash
129
- #!/bin/bash
130
- ~/.aru-env/bin/python -m aru.cli "$@"
131
- ```
132
-
133
- 3. Make it executable:
134
- ```bash
135
- chmod +x ~/.local/bin/aru
136
- ```
137
-
138
- </details>
139
-
140
- Done — now `aru` works from any directory.
97
+ That's it `aru` is available globally after install.
141
98
 
142
99
  ## Usage
143
100
 
@@ -187,15 +144,15 @@ By default, aru uses **Claude Sonnet 4.6** (Anthropic). You can switch to any su
187
144
  | Provider | Command | API Key (`.env`) | Extra Installation |
188
145
  |----------|---------|-------------------|------------------|
189
146
  | **Anthropic** | `/model anthropic/claude-sonnet-4-6` | `ANTHROPIC_API_KEY` | — (included) |
190
- | **Ollama** | `/model ollama/llama3.1` | — (local) | `pip install -e ".[ollama]"` |
191
- | **OpenAI** | `/model openai/gpt-4o` | `OPENAI_API_KEY` | `pip install -e ".[openai]"` |
192
- | **Groq** | `/model groq/llama-3.3-70b-versatile` | `GROQ_API_KEY` | `pip install -e ".[groq]"` |
193
- | **OpenRouter** | `/model openrouter/deepseek/deepseek-chat-v3-0324` | `OPENROUTER_API_KEY` | `pip install -e ".[openai]"` |
147
+ | **Ollama** | `/model ollama/llama3.1` | — (local) | `pip install "aru-code[ollama]"` |
148
+ | **OpenAI** | `/model openai/gpt-4o` | `OPENAI_API_KEY` | `pip install "aru-code[openai]"` |
149
+ | **Groq** | `/model groq/llama-3.3-70b-versatile` | `GROQ_API_KEY` | `pip install "aru-code[groq]"` |
150
+ | **OpenRouter** | `/model openrouter/deepseek/deepseek-chat-v3-0324` | `OPENROUTER_API_KEY` | `pip install "aru-code[openai]"` |
194
151
 
195
152
  To install all providers at once:
196
153
 
197
154
  ```bash
198
- pip install -e ".[all-providers]"
155
+ pip install "aru-code[all-providers]"
199
156
  ```
200
157
 
201
158
  #### Ollama (local models)
@@ -370,7 +327,9 @@ aru-code/
370
327
  ## Development
371
328
 
372
329
  ```bash
373
- # Install with development dependencies
330
+ # Clone and install in editable mode with dev dependencies
331
+ git clone https://github.com/estevaofon/aru.git
332
+ cd aru
374
333
  pip install -e ".[dev]"
375
334
 
376
335
  # Run tests
@@ -6,6 +6,10 @@ An intelligent coding assistant for the terminal, powered by LLMs and [Agno](htt
6
6
 
7
7
 
8
8
 
9
+ https://github.com/user-attachments/assets/17674bfc-3d49-4e25-bdc7-dc445d5a089d
10
+
11
+
12
+
9
13
  ## Highlights
10
14
 
11
15
  - **Multi-Agent Architecture** — Specialized agents for planning, execution, and conversation
@@ -21,7 +25,7 @@ An intelligent coding assistant for the terminal, powered by LLMs and [Agno](htt
21
25
  ### 1. Install
22
26
 
23
27
  ```bash
24
- pip install -e .
28
+ pip install aru-code
25
29
  ```
26
30
 
27
31
  > **Requirements:** Python 3.13+
@@ -30,13 +34,7 @@ pip install -e .
30
34
 
31
35
  Aru uses **Claude Sonnet 4.6** from Anthropic as the default model. You need an [Anthropic API key](https://console.anthropic.com/) to get started.
32
36
 
33
- Create a `.env` file in the project root:
34
-
35
- ```bash
36
- cp .env.example .env
37
- ```
38
-
39
- Edit the `.env` with your key:
37
+ Set your API key as an environment variable or create a `.env` file in your project directory:
40
38
 
41
39
  ```env
42
40
  ANTHROPIC_API_KEY=sk-ant-your-key-here
@@ -50,50 +48,7 @@ ANTHROPIC_API_KEY=sk-ant-your-key-here
50
48
  aru
51
49
  ```
52
50
 
53
- ### Global Installation (run `aru` from anywhere)
54
-
55
- To use aru as a global command in the terminal, create a dedicated virtual environment and a wrapper script:
56
-
57
- <details>
58
- <summary><strong>Windows</strong></summary>
59
-
60
- 1. Create the virtual environment and install:
61
- ```bash
62
- python -m venv C:\aru-env
63
- C:\aru-env\Scripts\pip install -e C:\path\to\aru
64
- ```
65
-
66
- 2. Create `aru.bat` in a folder on your `PATH` (e.g., `C:\Users\<user>\bin\`):
67
- ```bat
68
- @echo off
69
- C:\aru-env\Scripts\python -m aru.cli %*
70
- ```
71
-
72
- </details>
73
-
74
- <details>
75
- <summary><strong>Linux / macOS</strong></summary>
76
-
77
- 1. Create the virtual environment and install:
78
- ```bash
79
- python3 -m venv ~/.aru-env
80
- ~/.aru-env/bin/pip install -e /path/to/aru
81
- ```
82
-
83
- 2. Create the script `~/.local/bin/aru`:
84
- ```bash
85
- #!/bin/bash
86
- ~/.aru-env/bin/python -m aru.cli "$@"
87
- ```
88
-
89
- 3. Make it executable:
90
- ```bash
91
- chmod +x ~/.local/bin/aru
92
- ```
93
-
94
- </details>
95
-
96
- Done — now `aru` works from any directory.
51
+ That's it `aru` is available globally after install.
97
52
 
98
53
  ## Usage
99
54
 
@@ -143,15 +98,15 @@ By default, aru uses **Claude Sonnet 4.6** (Anthropic). You can switch to any su
143
98
  | Provider | Command | API Key (`.env`) | Extra Installation |
144
99
  |----------|---------|-------------------|------------------|
145
100
  | **Anthropic** | `/model anthropic/claude-sonnet-4-6` | `ANTHROPIC_API_KEY` | — (included) |
146
- | **Ollama** | `/model ollama/llama3.1` | — (local) | `pip install -e ".[ollama]"` |
147
- | **OpenAI** | `/model openai/gpt-4o` | `OPENAI_API_KEY` | `pip install -e ".[openai]"` |
148
- | **Groq** | `/model groq/llama-3.3-70b-versatile` | `GROQ_API_KEY` | `pip install -e ".[groq]"` |
149
- | **OpenRouter** | `/model openrouter/deepseek/deepseek-chat-v3-0324` | `OPENROUTER_API_KEY` | `pip install -e ".[openai]"` |
101
+ | **Ollama** | `/model ollama/llama3.1` | — (local) | `pip install "aru-code[ollama]"` |
102
+ | **OpenAI** | `/model openai/gpt-4o` | `OPENAI_API_KEY` | `pip install "aru-code[openai]"` |
103
+ | **Groq** | `/model groq/llama-3.3-70b-versatile` | `GROQ_API_KEY` | `pip install "aru-code[groq]"` |
104
+ | **OpenRouter** | `/model openrouter/deepseek/deepseek-chat-v3-0324` | `OPENROUTER_API_KEY` | `pip install "aru-code[openai]"` |
150
105
 
151
106
  To install all providers at once:
152
107
 
153
108
  ```bash
154
- pip install -e ".[all-providers]"
109
+ pip install "aru-code[all-providers]"
155
110
  ```
156
111
 
157
112
  #### Ollama (local models)
@@ -326,7 +281,9 @@ aru-code/
326
281
  ## Development
327
282
 
328
283
  ```bash
329
- # Install with development dependencies
284
+ # Clone and install in editable mode with dev dependencies
285
+ git clone https://github.com/estevaofon/aru.git
286
+ cd aru
330
287
  pip install -e ".[dev]"
331
288
 
332
289
  # Run tests
@@ -0,0 +1 @@
1
+ __version__ = "0.3.0"
@@ -92,6 +92,12 @@ Define 1-10 concrete subtasks for the current step. Then execute them in order,
92
92
  calling `update_task` to mark each as "completed" or "failed" as you go. \
93
93
  When all subtasks are done, STOP. Do not add extra actions beyond the task list.
94
94
 
95
+ ## Subtask granularity — CRITICAL
96
+ Each subtask should touch at most **3-4 files**. If the step involves many files, \
97
+ split into subtasks grouped by concern (e.g. "Create model files", "Create route files", \
98
+ "Update config and main"). Batch independent file writes using `write_files` or `edit_files` \
99
+ to minimize tool calls. Batch independent file writes using `write_files` or `edit_files` to minimize tool calls.
100
+
95
101
  ## Guidelines
96
102
  - Read files before editing them
97
103
  - Use edit_file for targeted changes (preferred over rewriting entire files)
@@ -129,7 +135,10 @@ Only output text AFTER all subtasks are finished — a brief summary of what was
129
135
  Text output is ONLY for the final result or when you hit a blocker that needs user input.
130
136
 
131
137
  **Never retry failed shell commands with alternative syntax.** If a command fails, diagnose \
132
- the error — do not try `cmd /c`, absolute paths, or other wrappers hoping one works.\
138
+ the error — do not try `cmd /c`, absolute paths, or other wrappers hoping one works.
139
+
140
+ **Tool call limit**: If you see "Tool call limit reached" errors, STOP trying to use tools immediately. \
141
+ Output a summary of what you accomplished so far and what remains. Do NOT retry rejected tool calls.\
133
142
  """
134
143
 
135
144
  # General-purpose agent (combines read + write, conversational)
@@ -0,0 +1,58 @@
1
+ """Executor agent - implements changes based on plans or direct instructions."""
2
+
3
+ from agno.agent import Agent
4
+ from agno.compression.manager import CompressionManager
5
+ from agno.utils.log import log_warning
6
+
7
+ from aru.agents.base import build_instructions
8
+ from aru.providers import create_model
9
+ from aru.tools.codebase import EXECUTOR_TOOLS, _get_small_model_ref
10
+
11
+ # Max chars for truncation fallback when compression fails
12
+ _TRUNCATE_FALLBACK = 3000
13
+
14
+
15
+ class _SafeCompressionManager(CompressionManager):
16
+ """CompressionManager that truncates on failure instead of leaving messages uncompressed.
17
+
18
+ Agno's default behavior: if compression returns None, the message stays with
19
+ compressed_content=None → should_compress() fires again → infinite retry loop.
20
+ This subclass marks failed messages with a truncated version so the loop moves on.
21
+ """
22
+
23
+ async def acompress(self, messages, run_metrics=None):
24
+ # Track which messages are currently uncompressed
25
+ before = {id(m) for m in messages if m.role == "tool" and m.compressed_content is None}
26
+ await super().acompress(messages, run_metrics=run_metrics)
27
+ # Any message still uncompressed after super() = compression failed
28
+ for msg in messages:
29
+ if id(msg) in before and msg.compressed_content is None:
30
+ content_str = str(msg.content or "")
31
+ msg.compressed_content = content_str[:_TRUNCATE_FALLBACK] + (
32
+ "... [truncated, compression failed]" if len(content_str) > _TRUNCATE_FALLBACK else ""
33
+ )
34
+ log_warning(f"Compression fallback (truncate) for {msg.tool_name}")
35
+
36
+
37
+ def create_executor(model_ref: str = "anthropic/claude-sonnet-4-5", extra_instructions: str = "") -> Agent:
38
+ """Create and return the executor agent.
39
+
40
+ Args:
41
+ model_ref: Provider/model reference (e.g., "anthropic/claude-sonnet-4-5", "ollama/llama3.1").
42
+ extra_instructions: Additional instructions to append.
43
+ """
44
+ return Agent(
45
+ name="Executor",
46
+ model=create_model(model_ref, max_tokens=8192),
47
+ tools=EXECUTOR_TOOLS,
48
+ instructions=build_instructions("executor", extra_instructions),
49
+ markdown=True,
50
+ # Compress tool results after 5 uncompressed tool calls to save tokens
51
+ compress_tool_results=True,
52
+ compression_manager=_SafeCompressionManager(
53
+ model=create_model(_get_small_model_ref(), max_tokens=2048),
54
+ compress_tool_results=True,
55
+ compress_tool_results_limit=15,
56
+ ),
57
+ tool_call_limit=None,
58
+ )
@@ -1,5 +1,7 @@
1
1
  """Interactive CLI for aru - a Claude Code clone."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import asyncio
4
6
  import hashlib
5
7
  import json
@@ -982,6 +984,7 @@ def _show_help(config: AgentConfig | None):
982
984
  table.add_row("/sessions", "List recent sessions")
983
985
  table.add_row("/commands", "List custom commands")
984
986
  table.add_row("/skills", "List available skills")
987
+ table.add_row("/mcp", "List loaded MCP tools")
985
988
  table.add_row("/help", "Show this help")
986
989
  table.add_row("/quit", "Exit aru")
987
990
  table.add_row("! <cmd>", "Run shell command")
@@ -1068,6 +1071,7 @@ class AgentRunResult:
1068
1071
  """Result from run_agent_capture including text output and tool call history."""
1069
1072
  content: str | None = None
1070
1073
  tool_calls: list[str] = field(default_factory=list)
1074
+ stalled: bool = False
1071
1075
 
1072
1076
  def with_tools_summary(self) -> str | None:
1073
1077
  """Return content with appended tool call summary for session history."""
@@ -1254,6 +1258,7 @@ async def run_agent_capture(agent, message: str, session: "Session | None" = Non
1254
1258
  console.print()
1255
1259
  final_content = None
1256
1260
  collected_tool_calls: list[str] = []
1261
+ _stalled = False
1257
1262
 
1258
1263
  try:
1259
1264
  from aru.tools.codebase import set_display, set_live
@@ -1318,12 +1323,20 @@ async def run_agent_capture(agent, message: str, session: "Session | None" = Non
1318
1323
  tasklist_set_live(live)
1319
1324
  tasklist_set_display(display)
1320
1325
  accumulated = ""
1326
+ # Stall detection: count consecutive events without useful work
1327
+ # When tool_call_limit is hit, Agno silently rejects tool calls and
1328
+ # keeps calling the model in a loop. We detect this by tracking
1329
+ # consecutive events that aren't tool starts/completions/content.
1330
+ _stall_counter = 0
1331
+ _stalled = False
1332
+ _STALL_LIMIT = 20 # break after 20 non-useful events (prevents infinite loop)
1321
1333
  async for event in agent.arun(agent_input, stream=True, stream_events=True, yield_run_output=True):
1322
1334
  if isinstance(event, RunOutput):
1323
1335
  run_output = event
1324
1336
  break
1325
1337
 
1326
1338
  if isinstance(event, ToolCallStartedEvent):
1339
+ _stall_counter = 0
1327
1340
  if hasattr(event, "tool") and event.tool:
1328
1341
  tool_name = event.tool.tool_name or "tool"
1329
1342
  tool_args = event.tool.tool_args or None
@@ -1345,6 +1358,7 @@ async def run_agent_capture(agent, message: str, session: "Session | None" = Non
1345
1358
  live.update(display)
1346
1359
 
1347
1360
  elif isinstance(event, ToolCallCompletedEvent):
1361
+ _stall_counter = 0
1348
1362
  if hasattr(event, "tool") and event.tool:
1349
1363
  tool_id = getattr(event.tool, "tool_call_id", None) or getattr(event.tool, "tool_name", "tool")
1350
1364
  else:
@@ -1365,6 +1379,7 @@ async def run_agent_capture(agent, message: str, session: "Session | None" = Non
1365
1379
  live.update(display)
1366
1380
 
1367
1381
  elif isinstance(event, RunContentEvent):
1382
+ _stall_counter = 0
1368
1383
  if hasattr(event, "content") and event.content:
1369
1384
  accumulated += event.content
1370
1385
  unflushed = accumulated[display._flushed_len:]
@@ -1391,6 +1406,17 @@ async def run_agent_capture(agent, message: str, session: "Session | None" = Non
1391
1406
  display.set_content(accumulated)
1392
1407
  live.update(display)
1393
1408
 
1409
+ else:
1410
+ # Unknown/internal event (e.g. model cycling after tool_call_limit)
1411
+ _stall_counter += 1
1412
+ if _stall_counter >= _STALL_LIMIT:
1413
+ _stalled = True
1414
+ live.console.print(
1415
+ "[yellow]Agent stalled (tool call limit likely reached). "
1416
+ "Moving on.[/yellow]"
1417
+ )
1418
+ break
1419
+
1394
1420
  set_live(None)
1395
1421
  set_display(None)
1396
1422
 
@@ -1425,7 +1451,7 @@ async def run_agent_capture(agent, message: str, session: "Session | None" = Non
1425
1451
  console.print(f"[red]Error: {escape(str(e))}[/red]")
1426
1452
 
1427
1453
  console.print()
1428
- return AgentRunResult(content=final_content, tool_calls=collected_tool_calls)
1454
+ return AgentRunResult(content=final_content, tool_calls=collected_tool_calls, stalled=_stalled)
1429
1455
 
1430
1456
 
1431
1457
  def ask_yes_no(prompt: str) -> bool:
@@ -1533,6 +1559,59 @@ async def execute_plan_steps(session: Session, executor_factory) -> str | None:
1533
1559
  run_result = await run_agent_capture(executor, step_prompt, session, lightweight=True)
1534
1560
  content = run_result.content
1535
1561
 
1562
+ # If agent stalled (tool call limit loop), show progress and let user decide
1563
+ if run_result.stalled:
1564
+ from aru.tools.tasklist import get_task_store
1565
+ store = get_task_store()
1566
+ all_tasks = store.get_all()
1567
+ done = [t for t in all_tasks if t["status"] == "completed"]
1568
+ pending = [t for t in all_tasks if t["status"] not in ("completed", "failed")]
1569
+
1570
+ console.print(f"\n[yellow]Step {step.index} stalled (tool call limit reached).[/yellow]")
1571
+ if done:
1572
+ console.print(f" [green]Completed:[/green] {len(done)}/{len(all_tasks)} subtasks")
1573
+ if pending:
1574
+ console.print(f" [yellow]Pending:[/yellow]")
1575
+ for t in pending:
1576
+ console.print(f" - {t.get('description', t.get('id', '?'))}")
1577
+
1578
+ if get_skip_permissions():
1579
+ step.status = "failed"
1580
+ continue
1581
+
1582
+ console.print("\n[bold]Options:[/bold]")
1583
+ console.print(" [cyan](r)[/cyan] Retry step with additional instructions")
1584
+ console.print(" [cyan](s)[/cyan] Skip to next step")
1585
+ console.print(" [cyan](a)[/cyan] Abort plan execution")
1586
+ try:
1587
+ choice = console.input("[bold yellow]Choice (r/s/a):[/bold yellow] ").strip().lower()
1588
+ except (EOFError, KeyboardInterrupt):
1589
+ choice = "a"
1590
+
1591
+ if choice in ("r", "retry"):
1592
+ try:
1593
+ extra = console.input("[bold cyan]Additional instructions:[/bold cyan] ").strip()
1594
+ except (EOFError, KeyboardInterrupt):
1595
+ extra = ""
1596
+ if extra:
1597
+ # Re-run the same step with user's extra context appended
1598
+ step.status = "in_progress"
1599
+ reset_task_store()
1600
+ retry_prompt = step_prompt + f"\n\n## Additional Instructions\n{extra}"
1601
+ executor = executor_factory()
1602
+ run_result = await run_agent_capture(executor, retry_prompt, session, lightweight=True)
1603
+ content = run_result.content
1604
+ # Fall through to normal completion check below
1605
+ else:
1606
+ step.status = "failed"
1607
+ continue
1608
+ elif choice in ("s", "skip"):
1609
+ step.status = "failed"
1610
+ continue
1611
+ else:
1612
+ step.status = "failed"
1613
+ break
1614
+
1536
1615
  # Check task store as ground truth for step completion
1537
1616
  from aru.tools.tasklist import get_task_store
1538
1617
  store = get_task_store()
@@ -1665,6 +1744,7 @@ async def run_cli(skip_permissions: bool = False, resume_id: str | None = None):
1665
1744
  # Apply default model from config if set
1666
1745
  if config.model_defaults.get("default"):
1667
1746
  session.model_ref = config.model_defaults["default"]
1747
+ _sync_model(session)
1668
1748
  _render_home(session, skip_permissions)
1669
1749
 
1670
1750
  # Wire file-mutation callback so context cache (tree/git) is invalidated
@@ -8,6 +8,8 @@ Supports:
8
8
  Follows the Gemini .agents convention for cross-platform compatibility.
9
9
  """
10
10
 
11
+ from __future__ import annotations
12
+
11
13
  import json
12
14
  import os
13
15
  from dataclasses import dataclass, field
@@ -219,6 +219,7 @@ def resolve_model_ref(model_ref: str) -> tuple[str, str]:
219
219
 
220
220
  if "/" in model_ref:
221
221
  provider_key, model_name = model_ref.split("/", 1)
222
+ provider_key = provider_key.lower()
222
223
  else:
223
224
  # Could be a provider name (use its default) or unknown
224
225
  if model_ref in _providers:
@@ -1,5 +1,7 @@
1
1
  """AST-based code analysis tools using tree-sitter."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import os
4
6
  import re
5
7
  from typing import Any
@@ -1,5 +1,7 @@
1
1
  """Gitignore-aware file filtering for codebase operations."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import os
4
6
  from typing import Iterator
5
7
 
@@ -1,5 +1,7 @@
1
1
  """Model Context Protocol (MCP) client manager and tool generation."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import asyncio
4
6
  import json
5
7
  import os
@@ -5,6 +5,8 @@ to plan and track subtasks within each plan step. Inspired by Claude Code
5
5
  and Antigravity's task management approach.
6
6
  """
7
7
 
8
+ from __future__ import annotations
9
+
8
10
  import threading
9
11
 
10
12
  from rich.console import Console, Group
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aru-code
3
- Version: 0.1.0
3
+ Version: 0.3.0
4
4
  Summary: A Claude Code clone built with Agno agents
5
5
  Author-email: Estevao <estevaofon@gmail.com>
6
6
  License-Expression: MIT
@@ -11,9 +11,11 @@ Keywords: ai,coding-assistant,claude,cli,agents
11
11
  Classifier: Development Status :: 3 - Alpha
12
12
  Classifier: Environment :: Console
13
13
  Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
14
16
  Classifier: Programming Language :: Python :: 3.13
15
17
  Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
16
- Requires-Python: >=3.13
18
+ Requires-Python: >=3.11
17
19
  Description-Content-Type: text/markdown
18
20
  License-File: LICENSE
19
21
  Requires-Dist: agno<3,>=2.5.10
@@ -50,6 +52,10 @@ An intelligent coding assistant for the terminal, powered by LLMs and [Agno](htt
50
52
 
51
53
 
52
54
 
55
+ https://github.com/user-attachments/assets/17674bfc-3d49-4e25-bdc7-dc445d5a089d
56
+
57
+
58
+
53
59
  ## Highlights
54
60
 
55
61
  - **Multi-Agent Architecture** — Specialized agents for planning, execution, and conversation
@@ -65,7 +71,7 @@ An intelligent coding assistant for the terminal, powered by LLMs and [Agno](htt
65
71
  ### 1. Install
66
72
 
67
73
  ```bash
68
- pip install -e .
74
+ pip install aru-code
69
75
  ```
70
76
 
71
77
  > **Requirements:** Python 3.13+
@@ -74,13 +80,7 @@ pip install -e .
74
80
 
75
81
  Aru uses **Claude Sonnet 4.6** from Anthropic as the default model. You need an [Anthropic API key](https://console.anthropic.com/) to get started.
76
82
 
77
- Create a `.env` file in the project root:
78
-
79
- ```bash
80
- cp .env.example .env
81
- ```
82
-
83
- Edit the `.env` with your key:
83
+ Set your API key as an environment variable or create a `.env` file in your project directory:
84
84
 
85
85
  ```env
86
86
  ANTHROPIC_API_KEY=sk-ant-your-key-here
@@ -94,50 +94,7 @@ ANTHROPIC_API_KEY=sk-ant-your-key-here
94
94
  aru
95
95
  ```
96
96
 
97
- ### Global Installation (run `aru` from anywhere)
98
-
99
- To use aru as a global command in the terminal, create a dedicated virtual environment and a wrapper script:
100
-
101
- <details>
102
- <summary><strong>Windows</strong></summary>
103
-
104
- 1. Create the virtual environment and install:
105
- ```bash
106
- python -m venv C:\aru-env
107
- C:\aru-env\Scripts\pip install -e C:\path\to\aru
108
- ```
109
-
110
- 2. Create `aru.bat` in a folder on your `PATH` (e.g., `C:\Users\<user>\bin\`):
111
- ```bat
112
- @echo off
113
- C:\aru-env\Scripts\python -m aru.cli %*
114
- ```
115
-
116
- </details>
117
-
118
- <details>
119
- <summary><strong>Linux / macOS</strong></summary>
120
-
121
- 1. Create the virtual environment and install:
122
- ```bash
123
- python3 -m venv ~/.aru-env
124
- ~/.aru-env/bin/pip install -e /path/to/aru
125
- ```
126
-
127
- 2. Create the script `~/.local/bin/aru`:
128
- ```bash
129
- #!/bin/bash
130
- ~/.aru-env/bin/python -m aru.cli "$@"
131
- ```
132
-
133
- 3. Make it executable:
134
- ```bash
135
- chmod +x ~/.local/bin/aru
136
- ```
137
-
138
- </details>
139
-
140
- Done — now `aru` works from any directory.
97
+ That's it `aru` is available globally after install.
141
98
 
142
99
  ## Usage
143
100
 
@@ -187,15 +144,15 @@ By default, aru uses **Claude Sonnet 4.6** (Anthropic). You can switch to any su
187
144
  | Provider | Command | API Key (`.env`) | Extra Installation |
188
145
  |----------|---------|-------------------|------------------|
189
146
  | **Anthropic** | `/model anthropic/claude-sonnet-4-6` | `ANTHROPIC_API_KEY` | — (included) |
190
- | **Ollama** | `/model ollama/llama3.1` | — (local) | `pip install -e ".[ollama]"` |
191
- | **OpenAI** | `/model openai/gpt-4o` | `OPENAI_API_KEY` | `pip install -e ".[openai]"` |
192
- | **Groq** | `/model groq/llama-3.3-70b-versatile` | `GROQ_API_KEY` | `pip install -e ".[groq]"` |
193
- | **OpenRouter** | `/model openrouter/deepseek/deepseek-chat-v3-0324` | `OPENROUTER_API_KEY` | `pip install -e ".[openai]"` |
147
+ | **Ollama** | `/model ollama/llama3.1` | — (local) | `pip install "aru-code[ollama]"` |
148
+ | **OpenAI** | `/model openai/gpt-4o` | `OPENAI_API_KEY` | `pip install "aru-code[openai]"` |
149
+ | **Groq** | `/model groq/llama-3.3-70b-versatile` | `GROQ_API_KEY` | `pip install "aru-code[groq]"` |
150
+ | **OpenRouter** | `/model openrouter/deepseek/deepseek-chat-v3-0324` | `OPENROUTER_API_KEY` | `pip install "aru-code[openai]"` |
194
151
 
195
152
  To install all providers at once:
196
153
 
197
154
  ```bash
198
- pip install -e ".[all-providers]"
155
+ pip install "aru-code[all-providers]"
199
156
  ```
200
157
 
201
158
  #### Ollama (local models)
@@ -370,7 +327,9 @@ aru-code/
370
327
  ## Development
371
328
 
372
329
  ```bash
373
- # Install with development dependencies
330
+ # Clone and install in editable mode with dev dependencies
331
+ git clone https://github.com/estevaofon/aru.git
332
+ cd aru
374
333
  pip install -e ".[dev]"
375
334
 
376
335
  # Run tests
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "aru-code"
7
- version = "0.1.0"
7
+ version = "0.3.0"
8
8
  description = "A Claude Code clone built with Agno agents"
9
9
  readme = "README.md"
10
10
  license = "MIT"
11
- requires-python = ">=3.13"
11
+ requires-python = ">=3.11"
12
12
  authors = [
13
13
  { name = "Estevao", email = "estevaofon@gmail.com" },
14
14
  ]
@@ -17,7 +17,9 @@ classifiers = [
17
17
  "Development Status :: 3 - Alpha",
18
18
  "Environment :: Console",
19
19
  "Intended Audience :: Developers",
20
- "Programming Language :: Python :: 3.13",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
21
23
  "Topic :: Software Development :: Libraries :: Application Frameworks",
22
24
  ]
23
25
 
@@ -1 +0,0 @@
1
- __version__ = "0.1.0"
@@ -1,32 +0,0 @@
1
- """Executor agent - implements changes based on plans or direct instructions."""
2
-
3
- from agno.agent import Agent
4
- from agno.compression.manager import CompressionManager
5
-
6
- from aru.agents.base import build_instructions
7
- from aru.providers import create_model
8
- from aru.tools.codebase import EXECUTOR_TOOLS, _get_small_model_ref
9
-
10
-
11
- def create_executor(model_ref: str = "anthropic/claude-sonnet-4-5", extra_instructions: str = "") -> Agent:
12
- """Create and return the executor agent.
13
-
14
- Args:
15
- model_ref: Provider/model reference (e.g., "anthropic/claude-sonnet-4-5", "ollama/llama3.1").
16
- extra_instructions: Additional instructions to append.
17
- """
18
- return Agent(
19
- name="Executor",
20
- model=create_model(model_ref, max_tokens=8192),
21
- tools=EXECUTOR_TOOLS,
22
- instructions=build_instructions("executor", extra_instructions),
23
- markdown=True,
24
- # Compress tool results after 5 uncompressed tool calls to save tokens
25
- compress_tool_results=True,
26
- compression_manager=CompressionManager(
27
- model=create_model(_get_small_model_ref(), max_tokens=1024),
28
- compress_tool_results=True,
29
- compress_tool_results_limit=15,
30
- ),
31
- tool_call_limit=15,
32
- )
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