aru-code 0.20.0__tar.gz → 0.20.1__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 (65) hide show
  1. {aru_code-0.20.0/aru_code.egg-info → aru_code-0.20.1}/PKG-INFO +42 -3
  2. {aru_code-0.20.0 → aru_code-0.20.1}/README.md +41 -2
  3. aru_code-0.20.1/aru/__init__.py +1 -0
  4. {aru_code-0.20.0 → aru_code-0.20.1}/aru/config.py +78 -46
  5. {aru_code-0.20.0 → aru_code-0.20.1}/aru/runner.py +5 -0
  6. {aru_code-0.20.0 → aru_code-0.20.1/aru_code.egg-info}/PKG-INFO +42 -3
  7. {aru_code-0.20.0 → aru_code-0.20.1}/pyproject.toml +1 -1
  8. aru_code-0.20.0/aru/__init__.py +0 -1
  9. {aru_code-0.20.0 → aru_code-0.20.1}/LICENSE +0 -0
  10. {aru_code-0.20.0 → aru_code-0.20.1}/aru/agent_factory.py +0 -0
  11. {aru_code-0.20.0 → aru_code-0.20.1}/aru/agents/__init__.py +0 -0
  12. {aru_code-0.20.0 → aru_code-0.20.1}/aru/agents/base.py +0 -0
  13. {aru_code-0.20.0 → aru_code-0.20.1}/aru/agents/executor.py +0 -0
  14. {aru_code-0.20.0 → aru_code-0.20.1}/aru/agents/planner.py +0 -0
  15. {aru_code-0.20.0 → aru_code-0.20.1}/aru/cache_patch.py +0 -0
  16. {aru_code-0.20.0 → aru_code-0.20.1}/aru/cli.py +0 -0
  17. {aru_code-0.20.0 → aru_code-0.20.1}/aru/commands.py +0 -0
  18. {aru_code-0.20.0 → aru_code-0.20.1}/aru/completers.py +0 -0
  19. {aru_code-0.20.0 → aru_code-0.20.1}/aru/context.py +0 -0
  20. {aru_code-0.20.0 → aru_code-0.20.1}/aru/display.py +0 -0
  21. {aru_code-0.20.0 → aru_code-0.20.1}/aru/history_blocks.py +0 -0
  22. {aru_code-0.20.0 → aru_code-0.20.1}/aru/permissions.py +0 -0
  23. {aru_code-0.20.0 → aru_code-0.20.1}/aru/plugins/__init__.py +0 -0
  24. {aru_code-0.20.0 → aru_code-0.20.1}/aru/plugins/custom_tools.py +0 -0
  25. {aru_code-0.20.0 → aru_code-0.20.1}/aru/plugins/hooks.py +0 -0
  26. {aru_code-0.20.0 → aru_code-0.20.1}/aru/plugins/manager.py +0 -0
  27. {aru_code-0.20.0 → aru_code-0.20.1}/aru/plugins/tool_api.py +0 -0
  28. {aru_code-0.20.0 → aru_code-0.20.1}/aru/providers.py +0 -0
  29. {aru_code-0.20.0 → aru_code-0.20.1}/aru/runtime.py +0 -0
  30. {aru_code-0.20.0 → aru_code-0.20.1}/aru/session.py +0 -0
  31. {aru_code-0.20.0 → aru_code-0.20.1}/aru/tools/__init__.py +0 -0
  32. {aru_code-0.20.0 → aru_code-0.20.1}/aru/tools/ast_tools.py +0 -0
  33. {aru_code-0.20.0 → aru_code-0.20.1}/aru/tools/codebase.py +0 -0
  34. {aru_code-0.20.0 → aru_code-0.20.1}/aru/tools/gitignore.py +0 -0
  35. {aru_code-0.20.0 → aru_code-0.20.1}/aru/tools/mcp_client.py +0 -0
  36. {aru_code-0.20.0 → aru_code-0.20.1}/aru/tools/ranker.py +0 -0
  37. {aru_code-0.20.0 → aru_code-0.20.1}/aru/tools/tasklist.py +0 -0
  38. {aru_code-0.20.0 → aru_code-0.20.1}/aru_code.egg-info/SOURCES.txt +0 -0
  39. {aru_code-0.20.0 → aru_code-0.20.1}/aru_code.egg-info/dependency_links.txt +0 -0
  40. {aru_code-0.20.0 → aru_code-0.20.1}/aru_code.egg-info/entry_points.txt +0 -0
  41. {aru_code-0.20.0 → aru_code-0.20.1}/aru_code.egg-info/requires.txt +0 -0
  42. {aru_code-0.20.0 → aru_code-0.20.1}/aru_code.egg-info/top_level.txt +0 -0
  43. {aru_code-0.20.0 → aru_code-0.20.1}/setup.cfg +0 -0
  44. {aru_code-0.20.0 → aru_code-0.20.1}/tests/test_agents_base.py +0 -0
  45. {aru_code-0.20.0 → aru_code-0.20.1}/tests/test_cli.py +0 -0
  46. {aru_code-0.20.0 → aru_code-0.20.1}/tests/test_cli_advanced.py +0 -0
  47. {aru_code-0.20.0 → aru_code-0.20.1}/tests/test_cli_base.py +0 -0
  48. {aru_code-0.20.0 → aru_code-0.20.1}/tests/test_cli_completers.py +0 -0
  49. {aru_code-0.20.0 → aru_code-0.20.1}/tests/test_cli_new.py +0 -0
  50. {aru_code-0.20.0 → aru_code-0.20.1}/tests/test_cli_run_cli.py +0 -0
  51. {aru_code-0.20.0 → aru_code-0.20.1}/tests/test_cli_session.py +0 -0
  52. {aru_code-0.20.0 → aru_code-0.20.1}/tests/test_cli_shell.py +0 -0
  53. {aru_code-0.20.0 → aru_code-0.20.1}/tests/test_codebase.py +0 -0
  54. {aru_code-0.20.0 → aru_code-0.20.1}/tests/test_confabulation_regression.py +0 -0
  55. {aru_code-0.20.0 → aru_code-0.20.1}/tests/test_config.py +0 -0
  56. {aru_code-0.20.0 → aru_code-0.20.1}/tests/test_context.py +0 -0
  57. {aru_code-0.20.0 → aru_code-0.20.1}/tests/test_executor.py +0 -0
  58. {aru_code-0.20.0 → aru_code-0.20.1}/tests/test_gitignore.py +0 -0
  59. {aru_code-0.20.0 → aru_code-0.20.1}/tests/test_main.py +0 -0
  60. {aru_code-0.20.0 → aru_code-0.20.1}/tests/test_mcp_client.py +0 -0
  61. {aru_code-0.20.0 → aru_code-0.20.1}/tests/test_permissions.py +0 -0
  62. {aru_code-0.20.0 → aru_code-0.20.1}/tests/test_planner.py +0 -0
  63. {aru_code-0.20.0 → aru_code-0.20.1}/tests/test_plugins.py +0 -0
  64. {aru_code-0.20.0 → aru_code-0.20.1}/tests/test_providers.py +0 -0
  65. {aru_code-0.20.0 → aru_code-0.20.1}/tests/test_ranker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aru-code
3
- Version: 0.20.0
3
+ Version: 0.20.1
4
4
  Summary: A Claude Code clone built with Agno agents
5
5
  Author-email: Estevao <estevaofon@gmail.com>
6
6
  License-Expression: MIT
@@ -312,8 +312,47 @@ Without any `aru.json` config, aru applies safe defaults:
312
312
  - Bash → ~40 safe command prefixes auto-allowed (`ls`, `git status`, `grep`, etc.), rest → `ask`
313
313
  - Sensitive files (`*.env`, `*.env.*`) → `deny` for read/edit/write (except `*.env.example`)
314
314
 
315
- > `aru.json` can also be placed at `.aru/config.json`.
316
- >
315
+ #### Config file locations
316
+
317
+ Aru loads configuration from two levels, with project settings overriding global ones:
318
+
319
+ | Level | Path | Purpose |
320
+ |-------|------|---------|
321
+ | **Global (user)** | `~/.aru/config.json` | Defaults that apply to all projects (model, aliases, permissions, providers) |
322
+ | **Project** | `aru.json` or `.aru/config.json` | Project-specific overrides |
323
+
324
+ Global config is loaded first, then the project config is **deep-merged** on top — scalar values and lists are replaced, nested objects (like `permission`, `providers`, `model_aliases`) are merged recursively. This means you can set your preferred model and aliases globally and only override what's different per project.
325
+
326
+ **Example `~/.aru/config.json`:**
327
+
328
+ ```json
329
+ {
330
+ "default_model": "anthropic/claude-sonnet-4-6",
331
+ "model_aliases": {
332
+ "sonnet": "anthropic/claude-sonnet-4-6",
333
+ "opus": "anthropic/claude-opus-4-6"
334
+ },
335
+ "permission": {
336
+ "read": "allow",
337
+ "glob": "allow",
338
+ "grep": "allow"
339
+ }
340
+ }
341
+ ```
342
+
343
+ Then a project `aru.json` only needs project-specific settings:
344
+
345
+ ```json
346
+ {
347
+ "default_model": "ollama/codellama",
348
+ "permission": {
349
+ "bash": { "pytest *": "allow" }
350
+ }
351
+ }
352
+ ```
353
+
354
+ The result: `default_model` becomes `ollama/codellama`, `model_aliases` come from global, and `permission` merges both levels (`read`, `glob`, `grep` from global + `bash` from project).
355
+
317
356
  > A full `aru.json` config reference here: [`aru.json`](./aru.json)
318
357
 
319
358
  ### AGENTS.md
@@ -265,8 +265,47 @@ Without any `aru.json` config, aru applies safe defaults:
265
265
  - Bash → ~40 safe command prefixes auto-allowed (`ls`, `git status`, `grep`, etc.), rest → `ask`
266
266
  - Sensitive files (`*.env`, `*.env.*`) → `deny` for read/edit/write (except `*.env.example`)
267
267
 
268
- > `aru.json` can also be placed at `.aru/config.json`.
269
- >
268
+ #### Config file locations
269
+
270
+ Aru loads configuration from two levels, with project settings overriding global ones:
271
+
272
+ | Level | Path | Purpose |
273
+ |-------|------|---------|
274
+ | **Global (user)** | `~/.aru/config.json` | Defaults that apply to all projects (model, aliases, permissions, providers) |
275
+ | **Project** | `aru.json` or `.aru/config.json` | Project-specific overrides |
276
+
277
+ Global config is loaded first, then the project config is **deep-merged** on top — scalar values and lists are replaced, nested objects (like `permission`, `providers`, `model_aliases`) are merged recursively. This means you can set your preferred model and aliases globally and only override what's different per project.
278
+
279
+ **Example `~/.aru/config.json`:**
280
+
281
+ ```json
282
+ {
283
+ "default_model": "anthropic/claude-sonnet-4-6",
284
+ "model_aliases": {
285
+ "sonnet": "anthropic/claude-sonnet-4-6",
286
+ "opus": "anthropic/claude-opus-4-6"
287
+ },
288
+ "permission": {
289
+ "read": "allow",
290
+ "glob": "allow",
291
+ "grep": "allow"
292
+ }
293
+ }
294
+ ```
295
+
296
+ Then a project `aru.json` only needs project-specific settings:
297
+
298
+ ```json
299
+ {
300
+ "default_model": "ollama/codellama",
301
+ "permission": {
302
+ "bash": { "pytest *": "allow" }
303
+ }
304
+ }
305
+ ```
306
+
307
+ The result: `default_model` becomes `ollama/codellama`, `model_aliases` come from global, and `permission` merges both levels (`read`, `glob`, `grep` from global + `bash` from project).
308
+
270
309
  > A full `aru.json` config reference here: [`aru.json`](./aru.json)
271
310
 
272
311
  ### AGENTS.md
@@ -0,0 +1 @@
1
+ __version__ = "0.20.1"
@@ -436,6 +436,63 @@ def _discover_agents(search_roots: list[Path]) -> dict[str, CustomAgent]:
436
436
  return agents
437
437
 
438
438
 
439
+ def _load_json_file(path: Path) -> dict | None:
440
+ """Read and parse a JSON file, returning None on any error."""
441
+ if not path.is_file():
442
+ return None
443
+ try:
444
+ content = path.read_text(encoding="utf-8")
445
+ data = json.loads(content)
446
+ return data if isinstance(data, dict) else None
447
+ except (OSError, UnicodeDecodeError, json.JSONDecodeError):
448
+ return None
449
+
450
+
451
+ def _deep_merge(base: dict, override: dict) -> dict:
452
+ """Recursively merge override into base. Override values win for scalars;
453
+ dicts are merged recursively; lists are replaced (not concatenated)."""
454
+ result = base.copy()
455
+ for key, value in override.items():
456
+ if key in result and isinstance(result[key], dict) and isinstance(value, dict):
457
+ result[key] = _deep_merge(result[key], value)
458
+ else:
459
+ result[key] = value
460
+ return result
461
+
462
+
463
+ def _apply_config_data(config: AgentConfig, data: dict, root: Path) -> None:
464
+ """Apply a merged config dict to an AgentConfig object."""
465
+ if "permission" in data:
466
+ config.permissions = data["permission"]
467
+ if "providers" in data:
468
+ from aru.providers import load_providers_from_config
469
+ load_providers_from_config(data)
470
+ if "default_model" in data:
471
+ config.default_model = data["default_model"]
472
+ if "model_aliases" in data and isinstance(data["model_aliases"], dict):
473
+ config.model_aliases = data["model_aliases"]
474
+ if "plan_reviewer" in data:
475
+ config.plan_reviewer = bool(data["plan_reviewer"])
476
+ if "tree_depth" in data:
477
+ td = data["tree_depth"]
478
+ if isinstance(td, int) and 0 <= td <= 5:
479
+ config.tree_depth = td
480
+ if "plugins" in data and isinstance(data["plugins"], list):
481
+ config.plugin_specs = data["plugins"]
482
+ if "tools" in data and isinstance(data["tools"], dict):
483
+ tools_cfg = data["tools"]
484
+ if "disabled" in tools_cfg and isinstance(tools_cfg["disabled"], list):
485
+ config.disabled_tools = [str(t) for t in tools_cfg["disabled"]]
486
+ if "instructions" in data and isinstance(data["instructions"], list):
487
+ entries = [str(e) for e in data["instructions"] if isinstance(e, str)]
488
+ config.rules_instructions = _resolve_instructions(entries, root)
489
+ if "agent" in data and isinstance(data["agent"], dict):
490
+ for agent_name, agent_data in data["agent"].items():
491
+ if agent_name in config.custom_agents and isinstance(agent_data, dict):
492
+ if "permission" in agent_data:
493
+ config.custom_agents[agent_name].permission = agent_data["permission"]
494
+
495
+
439
496
  def load_config(cwd: str | None = None) -> AgentConfig:
440
497
  """Load agent configuration from AGENTS.md and .agents/ directory.
441
498
 
@@ -492,52 +549,27 @@ def load_config(cwd: str | None = None) -> AgentConfig:
492
549
  config.skills = _discover_skills(skill_roots)
493
550
  config.custom_agents = _discover_agents(skill_roots)
494
551
 
495
- # Load opencode-style config (aru.json or .aru/config.json)
496
- config_paths = [root / "aru.json", root / ".aru" / "config.json"]
497
- for config_path in config_paths:
498
- if config_path.is_file():
499
- try:
500
- content = config_path.read_text(encoding="utf-8")
501
- data = json.loads(content)
502
- if isinstance(data, dict):
503
- if "permission" in data:
504
- config.permissions = data["permission"]
505
- # Load provider configuration
506
- if "providers" in data:
507
- from aru.providers import load_providers_from_config
508
- load_providers_from_config(data)
509
- # Store default model and aliases for CLI
510
- if "default_model" in data:
511
- config.default_model = data["default_model"]
512
- if "model_aliases" in data and isinstance(data["model_aliases"], dict):
513
- config.model_aliases = data["model_aliases"]
514
- if "plan_reviewer" in data:
515
- config.plan_reviewer = bool(data["plan_reviewer"])
516
- if "tree_depth" in data:
517
- td = data["tree_depth"]
518
- if isinstance(td, int) and 0 <= td <= 5:
519
- config.tree_depth = td
520
- # Plugin specs
521
- if "plugins" in data and isinstance(data["plugins"], list):
522
- config.plugin_specs = data["plugins"]
523
- # Custom tools config
524
- if "tools" in data and isinstance(data["tools"], dict):
525
- tools_cfg = data["tools"]
526
- if "disabled" in tools_cfg and isinstance(tools_cfg["disabled"], list):
527
- config.disabled_tools = [str(t) for t in tools_cfg["disabled"]]
528
- # Resolve instructions (local files, globs, URLs)
529
- if "instructions" in data and isinstance(data["instructions"], list):
530
- entries = [str(e) for e in data["instructions"] if isinstance(e, str)]
531
- config.rules_instructions = _resolve_instructions(entries, root)
532
- # Agent-level permission overrides from aru.json
533
- if "agent" in data and isinstance(data["agent"], dict):
534
- for agent_name, agent_data in data["agent"].items():
535
- if agent_name in config.custom_agents and isinstance(agent_data, dict):
536
- if "permission" in agent_data:
537
- config.custom_agents[agent_name].permission = agent_data["permission"]
538
- break
539
- except (OSError, UnicodeDecodeError, json.JSONDecodeError):
540
- pass
552
+ # Load config: global (~/.aru/config.json) first, then project-level on top.
553
+ # Project values override global values via deep merge.
554
+ home = Path.home()
555
+ global_config_paths = [home / ".aru" / "config.json"]
556
+ project_config_paths = [root / "aru.json", root / ".aru" / "config.json"]
557
+
558
+ merged_data: dict = {}
559
+ for config_path in global_config_paths:
560
+ data = _load_json_file(config_path)
561
+ if data is not None:
562
+ merged_data = data
563
+ break
564
+
565
+ for config_path in project_config_paths:
566
+ data = _load_json_file(config_path)
567
+ if data is not None:
568
+ merged_data = _deep_merge(merged_data, data)
569
+ break
570
+
571
+ if merged_data:
572
+ _apply_config_data(config, merged_data, root)
541
573
 
542
574
  return config
543
575
 
@@ -195,6 +195,7 @@ async def run_agent_capture(agent, message: str, session=None, lightweight: bool
195
195
  )
196
196
  pending_tool_uses[tool_id] = assistant_blocks[-1]
197
197
  if accumulated[display._flushed_len:]:
198
+ display.content = None
198
199
  live.stop()
199
200
  display.flush()
200
201
  live.start()
@@ -280,6 +281,10 @@ async def run_agent_capture(agent, message: str, session=None, lightweight: bool
280
281
  )
281
282
  break
282
283
 
284
+ # Clear live content before the Live context exits so its final
285
+ # render doesn't duplicate text that we print explicitly below.
286
+ display.content = None
287
+
283
288
  ctx.live = None
284
289
  ctx.display = None
285
290
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aru-code
3
- Version: 0.20.0
3
+ Version: 0.20.1
4
4
  Summary: A Claude Code clone built with Agno agents
5
5
  Author-email: Estevao <estevaofon@gmail.com>
6
6
  License-Expression: MIT
@@ -312,8 +312,47 @@ Without any `aru.json` config, aru applies safe defaults:
312
312
  - Bash → ~40 safe command prefixes auto-allowed (`ls`, `git status`, `grep`, etc.), rest → `ask`
313
313
  - Sensitive files (`*.env`, `*.env.*`) → `deny` for read/edit/write (except `*.env.example`)
314
314
 
315
- > `aru.json` can also be placed at `.aru/config.json`.
316
- >
315
+ #### Config file locations
316
+
317
+ Aru loads configuration from two levels, with project settings overriding global ones:
318
+
319
+ | Level | Path | Purpose |
320
+ |-------|------|---------|
321
+ | **Global (user)** | `~/.aru/config.json` | Defaults that apply to all projects (model, aliases, permissions, providers) |
322
+ | **Project** | `aru.json` or `.aru/config.json` | Project-specific overrides |
323
+
324
+ Global config is loaded first, then the project config is **deep-merged** on top — scalar values and lists are replaced, nested objects (like `permission`, `providers`, `model_aliases`) are merged recursively. This means you can set your preferred model and aliases globally and only override what's different per project.
325
+
326
+ **Example `~/.aru/config.json`:**
327
+
328
+ ```json
329
+ {
330
+ "default_model": "anthropic/claude-sonnet-4-6",
331
+ "model_aliases": {
332
+ "sonnet": "anthropic/claude-sonnet-4-6",
333
+ "opus": "anthropic/claude-opus-4-6"
334
+ },
335
+ "permission": {
336
+ "read": "allow",
337
+ "glob": "allow",
338
+ "grep": "allow"
339
+ }
340
+ }
341
+ ```
342
+
343
+ Then a project `aru.json` only needs project-specific settings:
344
+
345
+ ```json
346
+ {
347
+ "default_model": "ollama/codellama",
348
+ "permission": {
349
+ "bash": { "pytest *": "allow" }
350
+ }
351
+ }
352
+ ```
353
+
354
+ The result: `default_model` becomes `ollama/codellama`, `model_aliases` come from global, and `permission` merges both levels (`read`, `glob`, `grep` from global + `bash` from project).
355
+
317
356
  > A full `aru.json` config reference here: [`aru.json`](./aru.json)
318
357
 
319
358
  ### AGENTS.md
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "aru-code"
7
- version = "0.20.0"
7
+ version = "0.20.1"
8
8
  description = "A Claude Code clone built with Agno agents"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1 +0,0 @@
1
- __version__ = "0.20.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