aru-code 0.9.0__tar.gz → 0.10.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 (57) hide show
  1. {aru_code-0.9.0/aru_code.egg-info → aru_code-0.10.0}/PKG-INFO +2 -2
  2. {aru_code-0.9.0 → aru_code-0.10.0}/README.md +1 -1
  3. aru_code-0.10.0/aru/__init__.py +1 -0
  4. {aru_code-0.9.0 → aru_code-0.10.0}/aru/agent_factory.py +7 -2
  5. {aru_code-0.9.0 → aru_code-0.10.0}/aru/cli.py +10 -1
  6. {aru_code-0.9.0 → aru_code-0.10.0}/aru/config.py +20 -15
  7. {aru_code-0.9.0 → aru_code-0.10.0/aru_code.egg-info}/PKG-INFO +2 -2
  8. {aru_code-0.9.0 → aru_code-0.10.0}/pyproject.toml +1 -1
  9. {aru_code-0.9.0 → aru_code-0.10.0}/tests/test_config.py +78 -27
  10. aru_code-0.9.0/aru/__init__.py +0 -1
  11. {aru_code-0.9.0 → aru_code-0.10.0}/LICENSE +0 -0
  12. {aru_code-0.9.0 → aru_code-0.10.0}/aru/agents/__init__.py +0 -0
  13. {aru_code-0.9.0 → aru_code-0.10.0}/aru/agents/base.py +0 -0
  14. {aru_code-0.9.0 → aru_code-0.10.0}/aru/agents/executor.py +0 -0
  15. {aru_code-0.9.0 → aru_code-0.10.0}/aru/agents/planner.py +0 -0
  16. {aru_code-0.9.0 → aru_code-0.10.0}/aru/commands.py +0 -0
  17. {aru_code-0.9.0 → aru_code-0.10.0}/aru/completers.py +0 -0
  18. {aru_code-0.9.0 → aru_code-0.10.0}/aru/context.py +0 -0
  19. {aru_code-0.9.0 → aru_code-0.10.0}/aru/display.py +0 -0
  20. {aru_code-0.9.0 → aru_code-0.10.0}/aru/permissions.py +0 -0
  21. {aru_code-0.9.0 → aru_code-0.10.0}/aru/providers.py +0 -0
  22. {aru_code-0.9.0 → aru_code-0.10.0}/aru/runner.py +0 -0
  23. {aru_code-0.9.0 → aru_code-0.10.0}/aru/runtime.py +0 -0
  24. {aru_code-0.9.0 → aru_code-0.10.0}/aru/session.py +0 -0
  25. {aru_code-0.9.0 → aru_code-0.10.0}/aru/tools/__init__.py +0 -0
  26. {aru_code-0.9.0 → aru_code-0.10.0}/aru/tools/ast_tools.py +0 -0
  27. {aru_code-0.9.0 → aru_code-0.10.0}/aru/tools/codebase.py +0 -0
  28. {aru_code-0.9.0 → aru_code-0.10.0}/aru/tools/gitignore.py +0 -0
  29. {aru_code-0.9.0 → aru_code-0.10.0}/aru/tools/mcp_client.py +0 -0
  30. {aru_code-0.9.0 → aru_code-0.10.0}/aru/tools/ranker.py +0 -0
  31. {aru_code-0.9.0 → aru_code-0.10.0}/aru/tools/tasklist.py +0 -0
  32. {aru_code-0.9.0 → aru_code-0.10.0}/aru_code.egg-info/SOURCES.txt +0 -0
  33. {aru_code-0.9.0 → aru_code-0.10.0}/aru_code.egg-info/dependency_links.txt +0 -0
  34. {aru_code-0.9.0 → aru_code-0.10.0}/aru_code.egg-info/entry_points.txt +0 -0
  35. {aru_code-0.9.0 → aru_code-0.10.0}/aru_code.egg-info/requires.txt +0 -0
  36. {aru_code-0.9.0 → aru_code-0.10.0}/aru_code.egg-info/top_level.txt +0 -0
  37. {aru_code-0.9.0 → aru_code-0.10.0}/setup.cfg +0 -0
  38. {aru_code-0.9.0 → aru_code-0.10.0}/tests/test_agents_base.py +0 -0
  39. {aru_code-0.9.0 → aru_code-0.10.0}/tests/test_ast_tools.py +0 -0
  40. {aru_code-0.9.0 → aru_code-0.10.0}/tests/test_cli.py +0 -0
  41. {aru_code-0.9.0 → aru_code-0.10.0}/tests/test_cli_advanced.py +0 -0
  42. {aru_code-0.9.0 → aru_code-0.10.0}/tests/test_cli_base.py +0 -0
  43. {aru_code-0.9.0 → aru_code-0.10.0}/tests/test_cli_completers.py +0 -0
  44. {aru_code-0.9.0 → aru_code-0.10.0}/tests/test_cli_new.py +0 -0
  45. {aru_code-0.9.0 → aru_code-0.10.0}/tests/test_cli_run_cli.py +0 -0
  46. {aru_code-0.9.0 → aru_code-0.10.0}/tests/test_cli_session.py +0 -0
  47. {aru_code-0.9.0 → aru_code-0.10.0}/tests/test_cli_shell.py +0 -0
  48. {aru_code-0.9.0 → aru_code-0.10.0}/tests/test_codebase.py +0 -0
  49. {aru_code-0.9.0 → aru_code-0.10.0}/tests/test_context.py +0 -0
  50. {aru_code-0.9.0 → aru_code-0.10.0}/tests/test_executor.py +0 -0
  51. {aru_code-0.9.0 → aru_code-0.10.0}/tests/test_gitignore.py +0 -0
  52. {aru_code-0.9.0 → aru_code-0.10.0}/tests/test_main.py +0 -0
  53. {aru_code-0.9.0 → aru_code-0.10.0}/tests/test_mcp_client.py +0 -0
  54. {aru_code-0.9.0 → aru_code-0.10.0}/tests/test_permissions.py +0 -0
  55. {aru_code-0.9.0 → aru_code-0.10.0}/tests/test_planner.py +0 -0
  56. {aru_code-0.9.0 → aru_code-0.10.0}/tests/test_providers.py +0 -0
  57. {aru_code-0.9.0 → aru_code-0.10.0}/tests/test_ranker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aru-code
3
- Version: 0.9.0
3
+ Version: 0.10.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
@@ -340,7 +340,7 @@ All resolved content is combined and appended to the agent's system prompt along
340
340
  └── SKILL.md
341
341
  ```
342
342
 
343
- Command files support frontmatter with `description` and the `$INPUT` template variable for arguments.
343
+ Command files support frontmatter with `description`, `agent`, and `model` fields, plus OpenCode-style argument placeholders: `$ARGUMENTS` (full string), `$1`/`$2` (positional), and `$ARGUMENTS[N]` (0-indexed).
344
344
 
345
345
  ### Custom Agents
346
346
 
@@ -293,7 +293,7 @@ All resolved content is combined and appended to the agent's system prompt along
293
293
  └── SKILL.md
294
294
  ```
295
295
 
296
- Command files support frontmatter with `description` and the `$INPUT` template variable for arguments.
296
+ Command files support frontmatter with `description`, `agent`, and `model` fields, plus OpenCode-style argument placeholders: `$ARGUMENTS` (full string), `$1`/`$2` (positional), and `$ARGUMENTS[N]` (0-indexed).
297
297
 
298
298
  ### Custom Agents
299
299
 
@@ -0,0 +1 @@
1
+ __version__ = "0.10.0"
@@ -8,7 +8,11 @@ from aru.providers import create_model
8
8
  from aru.session import Session
9
9
 
10
10
 
11
- def create_general_agent(session: Session, config: AgentConfig | None = None):
11
+ def create_general_agent(
12
+ session: Session,
13
+ config: AgentConfig | None = None,
14
+ model_override: str | None = None,
15
+ ):
12
16
  """Create the general-purpose agent."""
13
17
  from agno.agent import Agent
14
18
  from agno.compression.manager import CompressionManager
@@ -17,10 +21,11 @@ def create_general_agent(session: Session, config: AgentConfig | None = None):
17
21
  from aru.runtime import get_ctx
18
22
 
19
23
  extra = config.get_extra_instructions() if config else ""
24
+ model_ref = model_override or session.model_ref
20
25
 
21
26
  return Agent(
22
27
  name="Aru",
23
- model=create_model(session.model_ref, max_tokens=8192),
28
+ model=create_model(model_ref, max_tokens=8192),
24
29
  tools=GENERAL_TOOLS,
25
30
  instructions=_build_instructions("general", extra),
26
31
  markdown=True,
@@ -430,7 +430,16 @@ async def run_cli(skip_permissions: bool = False, resume_id: str | None = None):
430
430
  prompt = render_command_template(cmd_def.template, cmd_args)
431
431
  console.print(f"[bold magenta]Running /{cmd_name}...[/bold magenta]")
432
432
 
433
- agent = create_general_agent(session, config)
433
+ if cmd_def.agent and cmd_def.agent in config.custom_agents:
434
+ agent_def = config.custom_agents[cmd_def.agent]
435
+ agent = create_custom_agent_instance(agent_def, session, config)
436
+ elif cmd_def.agent:
437
+ console.print(f"[yellow]Warning: agent '{cmd_def.agent}' not found, using default[/yellow]")
438
+ agent = create_general_agent(session, config, model_override=cmd_def.model)
439
+ elif cmd_def.model:
440
+ agent = create_general_agent(session, config, model_override=cmd_def.model)
441
+ else:
442
+ agent = create_general_agent(session, config)
434
443
  session.add_message("user", user_input)
435
444
  run_result = await run_agent_capture(agent, prompt, session)
436
445
  if run_result.content:
@@ -27,6 +27,8 @@ class CustomCommand:
27
27
  description: str
28
28
  template: str
29
29
  source_path: str
30
+ agent: str | None = None
31
+ model: str | None = None
30
32
 
31
33
 
32
34
  @dataclass
@@ -325,6 +327,8 @@ def _load_commands(agents_dir: Path) -> dict[str, CustomCommand]:
325
327
  description=description,
326
328
  template=body,
327
329
  source_path=str(filepath),
330
+ agent=metadata.get("agent") or None,
331
+ model=metadata.get("model") or None,
328
332
  )
329
333
 
330
334
  return commands
@@ -518,26 +522,17 @@ def load_config(cwd: str | None = None) -> AgentConfig:
518
522
  return config
519
523
 
520
524
 
521
- def render_command_template(template: str, user_input: str) -> str:
522
- """Render a command template with user input.
523
-
524
- Replaces $INPUT with the user's arguments.
525
- Also supports $SELECTION (empty if not provided) for future use.
526
- """
527
- result = template.replace("$INPUT", user_input)
528
- result = result.replace("$SELECTION", "")
529
- return result
530
-
531
-
532
- def render_skill_template(content: str, arguments: str) -> str:
533
- """Render a skill template with argument substitution (agentskills.io).
525
+ def render_template_arguments(
526
+ content: str, arguments: str, *, context_label: str = "Argument",
527
+ ) -> str:
528
+ """Render a template with $ARGUMENTS / $1 / $2 substitution.
534
529
 
535
530
  Supports:
536
531
  - $ARGUMENTS: Full argument string
537
532
  - $ARGUMENTS[N]: Nth argument (0-indexed)
538
533
  - $1, $2, ...: Nth argument (1-indexed, shell-style)
539
534
 
540
- Also prepends an explicit argument context block so the agent cannot
535
+ Also prepends an explicit context block so the agent cannot
541
536
  miss or misread the user-supplied value.
542
537
  """
543
538
  parts = arguments.split() if arguments else []
@@ -561,7 +556,17 @@ def render_skill_template(content: str, arguments: str) -> str:
561
556
 
562
557
  # Prepend an explicit context block so the agent cannot miss the argument
563
558
  if arguments and arguments.strip():
564
- header = f"> **Skill argument:** `{arguments.strip()}`\n> Use this value exactly where the skill instructions reference the argument.\n\n"
559
+ header = f"> **{context_label}:** `{arguments.strip()}`\n> Use this value exactly where the instructions reference the argument.\n\n"
565
560
  result = header + result
566
561
 
567
562
  return result
563
+
564
+
565
+ def render_command_template(template: str, user_input: str) -> str:
566
+ """Render a command template with OpenCode-style argument substitution."""
567
+ return render_template_arguments(template, user_input, context_label="Command argument")
568
+
569
+
570
+ def render_skill_template(content: str, arguments: str) -> str:
571
+ """Render a skill template with argument substitution (agentskills.io)."""
572
+ return render_template_arguments(content, arguments, context_label="Skill argument")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aru-code
3
- Version: 0.9.0
3
+ Version: 0.10.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
@@ -340,7 +340,7 @@ All resolved content is combined and appended to the agent's system prompt along
340
340
  └── SKILL.md
341
341
  ```
342
342
 
343
- Command files support frontmatter with `description` and the `$INPUT` template variable for arguments.
343
+ Command files support frontmatter with `description`, `agent`, and `model` fields, plus OpenCode-style argument placeholders: `$ARGUMENTS` (full string), `$1`/`$2` (positional), and `$ARGUMENTS[N]` (0-indexed).
344
344
 
345
345
  ### Custom Agents
346
346
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "aru-code"
7
- version = "0.9.0"
7
+ version = "0.10.0"
8
8
  description = "A Claude Code clone built with Agno agents"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -19,6 +19,7 @@ from aru.config import (
19
19
  _url_cache,
20
20
  render_command_template,
21
21
  render_skill_template,
22
+ render_template_arguments,
22
23
  load_config,
23
24
  MAX_README_CHARS,
24
25
  )
@@ -31,13 +32,27 @@ class TestDataClasses:
31
32
  cmd = CustomCommand(
32
33
  name="test",
33
34
  description="Test command",
34
- template="Do $INPUT",
35
+ template="Do $ARGUMENTS",
35
36
  source_path="/path/to/test.md",
36
37
  )
37
38
  assert cmd.name == "test"
38
39
  assert cmd.description == "Test command"
39
- assert cmd.template == "Do $INPUT"
40
+ assert cmd.template == "Do $ARGUMENTS"
40
41
  assert cmd.source_path == "/path/to/test.md"
42
+ assert cmd.agent is None
43
+ assert cmd.model is None
44
+
45
+ def test_custom_command_with_agent_and_model(self):
46
+ cmd = CustomCommand(
47
+ name="review",
48
+ description="Review code",
49
+ template="Review $ARGUMENTS",
50
+ source_path="/path/to/review.md",
51
+ agent="reviewer",
52
+ model="anthropic/claude-sonnet-4-5",
53
+ )
54
+ assert cmd.agent == "reviewer"
55
+ assert cmd.model == "anthropic/claude-sonnet-4-5"
41
56
 
42
57
  def test_skill_creation(self):
43
58
  skill = Skill(
@@ -198,37 +213,57 @@ class TestParseFrontmatter:
198
213
 
199
214
 
200
215
  class TestRenderCommandTemplate:
201
- """Test command template rendering."""
216
+ """Test command template rendering with OpenCode-style arguments."""
202
217
 
203
- def test_render_with_input(self):
204
- template = "Execute $INPUT in the codebase"
205
- result = render_command_template(template, "refactoring")
206
- assert result == "Execute refactoring in the codebase"
218
+ def test_render_with_arguments(self):
219
+ result = render_command_template("Execute $ARGUMENTS in the codebase", "refactoring")
220
+ assert "Execute refactoring in the codebase" in result
207
221
 
208
- def test_render_multiple_input_placeholders(self):
209
- template = "Run $INPUT and verify $INPUT works"
210
- result = render_command_template(template, "tests")
211
- assert result == "Run tests and verify tests works"
222
+ def test_render_positional_args(self):
223
+ result = render_command_template("File: $1, Line: $2", "main.py 42")
224
+ assert "File: main.py, Line: 42" in result
212
225
 
213
- def test_render_removes_selection(self):
214
- template = "Process $INPUT and $SELECTION"
215
- result = render_command_template(template, "data")
216
- assert result == "Process data and "
226
+ def test_render_indexed_args(self):
227
+ result = render_command_template("$ARGUMENTS[0] and $ARGUMENTS[1]", "foo bar")
228
+ assert "foo and bar" in result
217
229
 
218
230
  def test_render_no_placeholders(self):
219
231
  template = "Just plain text"
220
232
  result = render_command_template(template, "input")
221
- assert result == "Just plain text"
233
+ # Header is prepended but template body is unchanged
234
+ assert "Just plain text" in result
222
235
 
223
236
  def test_render_empty_input(self):
224
- template = "Do $INPUT now"
225
- result = render_command_template(template, "")
237
+ result = render_command_template("Do $ARGUMENTS now", "")
226
238
  assert result == "Do now"
227
239
 
228
240
  def test_render_with_special_characters(self):
229
- template = "Run $INPUT"
230
- result = render_command_template(template, "pytest -v --cov")
231
- assert result == "Run pytest -v --cov"
241
+ result = render_command_template("Run $ARGUMENTS", "pytest -v --cov")
242
+ assert "Run pytest -v --cov" in result
243
+
244
+ def test_render_missing_positional(self):
245
+ result = render_command_template("$1 and $3", "first second")
246
+ assert "first and " in result
247
+
248
+ def test_command_context_header(self):
249
+ result = render_command_template("Do $ARGUMENTS", "something")
250
+ assert result.startswith("> **Command argument:**")
251
+
252
+ def test_no_header_when_empty_args(self):
253
+ result = render_command_template("Do $ARGUMENTS", "")
254
+ assert not result.startswith(">")
255
+
256
+
257
+ class TestRenderTemplateArguments:
258
+ """Test the shared render_template_arguments function."""
259
+
260
+ def test_custom_context_label(self):
261
+ result = render_template_arguments("Do $ARGUMENTS", "test", context_label="Custom label")
262
+ assert "> **Custom label:** `test`" in result
263
+
264
+ def test_delegates_correctly(self):
265
+ result = render_template_arguments("$1 + $2 = $ARGUMENTS", "a b")
266
+ assert "a + b = a b" in result
232
267
 
233
268
 
234
269
  class TestLoadConfig:
@@ -274,16 +309,32 @@ class TestLoadConfig:
274
309
  def test_load_config_with_commands(self, tmp_path):
275
310
  commands_dir = tmp_path / ".agents" / "commands"
276
311
  commands_dir.mkdir(parents=True)
277
-
312
+
278
313
  cmd_file = commands_dir / "deploy.md"
279
- cmd_file.write_text("---\ndescription: Deploy the app\n---\nDeploy $INPUT")
280
-
314
+ cmd_file.write_text("---\ndescription: Deploy the app\n---\nDeploy $ARGUMENTS")
315
+
281
316
  config = load_config(str(tmp_path))
282
317
  assert "deploy" in config.commands
283
318
  cmd = config.commands["deploy"]
284
319
  assert cmd.name == "deploy"
285
320
  assert cmd.description == "Deploy the app"
286
- assert cmd.template == "Deploy $INPUT"
321
+ assert cmd.template == "Deploy $ARGUMENTS"
322
+ assert cmd.agent is None
323
+ assert cmd.model is None
324
+
325
+ def test_load_config_command_with_agent_and_model(self, tmp_path):
326
+ commands_dir = tmp_path / ".agents" / "commands"
327
+ commands_dir.mkdir(parents=True)
328
+
329
+ cmd_file = commands_dir / "review.md"
330
+ cmd_file.write_text(
331
+ "---\ndescription: Review code\nagent: reviewer\nmodel: anthropic/claude-sonnet-4-5\n---\nReview $ARGUMENTS"
332
+ )
333
+
334
+ config = load_config(str(tmp_path))
335
+ cmd = config.commands["review"]
336
+ assert cmd.agent == "reviewer"
337
+ assert cmd.model == "anthropic/claude-sonnet-4-5"
287
338
 
288
339
  def test_load_config_command_without_frontmatter(self, tmp_path):
289
340
  commands_dir = tmp_path / ".agents" / "commands"
@@ -409,7 +460,7 @@ class TestLoadConfig:
409
460
 
410
461
  commands_dir = tmp_path / ".agents" / "commands"
411
462
  commands_dir.mkdir(parents=True)
412
- (commands_dir / "test.md").write_text("---\ndescription: Test\n---\nRun $INPUT")
463
+ (commands_dir / "test.md").write_text("---\ndescription: Test\n---\nRun $ARGUMENTS")
413
464
 
414
465
  skill_dir = tmp_path / ".agents" / "skills" / "review"
415
466
  skill_dir.mkdir(parents=True)
@@ -529,7 +580,7 @@ class TestParseSkillMetadata:
529
580
  class TestRenderSkillTemplate:
530
581
  """Test render_skill_template with argument substitution."""
531
582
 
532
- _HEADER = "> **Skill argument:** `{arg}`\n> Use this value exactly where the skill instructions reference the argument.\n\n"
583
+ _HEADER = "> **Skill argument:** `{arg}`\n> Use this value exactly where the instructions reference the argument.\n\n"
533
584
 
534
585
  def test_arguments_substitution(self):
535
586
  result = render_skill_template("Review $ARGUMENTS carefully", "src/main.py")
@@ -1 +0,0 @@
1
- __version__ = "0.9.0"
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