aru-code 0.27.0__tar.gz → 0.28.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 (87) hide show
  1. {aru_code-0.27.0/aru_code.egg-info → aru_code-0.28.0}/PKG-INFO +1 -1
  2. aru_code-0.28.0/aru/__init__.py +1 -0
  3. {aru_code-0.27.0 → aru_code-0.28.0}/aru/cli.py +15 -0
  4. aru_code-0.28.0/aru/commands.py +244 -0
  5. {aru_code-0.27.0 → aru_code-0.28.0}/aru/config.py +7 -1
  6. aru_code-0.28.0/aru/plugin_cache.py +618 -0
  7. {aru_code-0.27.0 → aru_code-0.28.0}/aru/plugins/custom_tools.py +9 -1
  8. {aru_code-0.27.0 → aru_code-0.28.0}/aru/plugins/manager.py +9 -1
  9. {aru_code-0.27.0 → aru_code-0.28.0}/aru/runtime.py +3 -0
  10. {aru_code-0.27.0 → aru_code-0.28.0}/aru/tools/registry.py +7 -1
  11. aru_code-0.28.0/aru/tools/skill.py +153 -0
  12. {aru_code-0.27.0 → aru_code-0.28.0}/aru/tools/tasklist.py +13 -8
  13. {aru_code-0.27.0 → aru_code-0.28.0/aru_code.egg-info}/PKG-INFO +1 -1
  14. {aru_code-0.27.0 → aru_code-0.28.0}/aru_code.egg-info/SOURCES.txt +6 -1
  15. {aru_code-0.27.0 → aru_code-0.28.0}/pyproject.toml +1 -1
  16. aru_code-0.28.0/tests/test_invoke_skill.py +308 -0
  17. aru_code-0.28.0/tests/test_plugin_cache.py +354 -0
  18. aru_code-0.28.0/tests/test_tasklist.py +117 -0
  19. aru_code-0.27.0/aru/__init__.py +0 -1
  20. aru_code-0.27.0/aru/commands.py +0 -105
  21. {aru_code-0.27.0 → aru_code-0.28.0}/LICENSE +0 -0
  22. {aru_code-0.27.0 → aru_code-0.28.0}/README.md +0 -0
  23. {aru_code-0.27.0 → aru_code-0.28.0}/aru/agent_factory.py +0 -0
  24. {aru_code-0.27.0 → aru_code-0.28.0}/aru/agents/__init__.py +0 -0
  25. {aru_code-0.27.0 → aru_code-0.28.0}/aru/agents/base.py +0 -0
  26. {aru_code-0.27.0 → aru_code-0.28.0}/aru/agents/catalog.py +0 -0
  27. {aru_code-0.27.0 → aru_code-0.28.0}/aru/agents/planner.py +0 -0
  28. {aru_code-0.27.0 → aru_code-0.28.0}/aru/cache_patch.py +0 -0
  29. {aru_code-0.27.0 → aru_code-0.28.0}/aru/checkpoints.py +0 -0
  30. {aru_code-0.27.0 → aru_code-0.28.0}/aru/completers.py +0 -0
  31. {aru_code-0.27.0 → aru_code-0.28.0}/aru/context.py +0 -0
  32. {aru_code-0.27.0 → aru_code-0.28.0}/aru/display.py +0 -0
  33. {aru_code-0.27.0 → aru_code-0.28.0}/aru/history_blocks.py +0 -0
  34. {aru_code-0.27.0 → aru_code-0.28.0}/aru/permissions.py +0 -0
  35. {aru_code-0.27.0 → aru_code-0.28.0}/aru/plugins/__init__.py +0 -0
  36. {aru_code-0.27.0 → aru_code-0.28.0}/aru/plugins/hooks.py +0 -0
  37. {aru_code-0.27.0 → aru_code-0.28.0}/aru/plugins/tool_api.py +0 -0
  38. {aru_code-0.27.0 → aru_code-0.28.0}/aru/providers.py +0 -0
  39. {aru_code-0.27.0 → aru_code-0.28.0}/aru/runner.py +0 -0
  40. {aru_code-0.27.0 → aru_code-0.28.0}/aru/select.py +0 -0
  41. {aru_code-0.27.0 → aru_code-0.28.0}/aru/session.py +0 -0
  42. {aru_code-0.27.0 → aru_code-0.28.0}/aru/tools/__init__.py +0 -0
  43. {aru_code-0.27.0 → aru_code-0.28.0}/aru/tools/_diff.py +0 -0
  44. {aru_code-0.27.0 → aru_code-0.28.0}/aru/tools/_shared.py +0 -0
  45. {aru_code-0.27.0 → aru_code-0.28.0}/aru/tools/ast_tools.py +0 -0
  46. {aru_code-0.27.0 → aru_code-0.28.0}/aru/tools/codebase.py +0 -0
  47. {aru_code-0.27.0 → aru_code-0.28.0}/aru/tools/delegate.py +0 -0
  48. {aru_code-0.27.0 → aru_code-0.28.0}/aru/tools/file_ops.py +0 -0
  49. {aru_code-0.27.0 → aru_code-0.28.0}/aru/tools/gitignore.py +0 -0
  50. {aru_code-0.27.0 → aru_code-0.28.0}/aru/tools/mcp_client.py +0 -0
  51. {aru_code-0.27.0 → aru_code-0.28.0}/aru/tools/plan_mode.py +0 -0
  52. {aru_code-0.27.0 → aru_code-0.28.0}/aru/tools/ranker.py +0 -0
  53. {aru_code-0.27.0 → aru_code-0.28.0}/aru/tools/search.py +0 -0
  54. {aru_code-0.27.0 → aru_code-0.28.0}/aru/tools/shell.py +0 -0
  55. {aru_code-0.27.0 → aru_code-0.28.0}/aru/tools/web.py +0 -0
  56. {aru_code-0.27.0 → aru_code-0.28.0}/aru_code.egg-info/dependency_links.txt +0 -0
  57. {aru_code-0.27.0 → aru_code-0.28.0}/aru_code.egg-info/entry_points.txt +0 -0
  58. {aru_code-0.27.0 → aru_code-0.28.0}/aru_code.egg-info/requires.txt +0 -0
  59. {aru_code-0.27.0 → aru_code-0.28.0}/aru_code.egg-info/top_level.txt +0 -0
  60. {aru_code-0.27.0 → aru_code-0.28.0}/setup.cfg +0 -0
  61. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_agents_base.py +0 -0
  62. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_agents_md_coverage.py +0 -0
  63. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_cache_patch_metrics.py +0 -0
  64. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_catalog.py +0 -0
  65. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_checkpoints.py +0 -0
  66. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_cli.py +0 -0
  67. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_cli_advanced.py +0 -0
  68. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_cli_base.py +0 -0
  69. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_cli_completers.py +0 -0
  70. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_cli_new.py +0 -0
  71. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_cli_run_cli.py +0 -0
  72. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_cli_session.py +0 -0
  73. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_cli_shell.py +0 -0
  74. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_codebase.py +0 -0
  75. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_confabulation_regression.py +0 -0
  76. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_config.py +0 -0
  77. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_context.py +0 -0
  78. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_gitignore.py +0 -0
  79. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_guardrails_scenarios.py +0 -0
  80. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_main.py +0 -0
  81. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_mcp_client.py +0 -0
  82. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_permissions.py +0 -0
  83. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_plan_mode_refactor.py +0 -0
  84. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_plugins.py +0 -0
  85. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_providers.py +0 -0
  86. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_ranker.py +0 -0
  87. {aru_code-0.27.0 → aru_code-0.28.0}/tests/test_select.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aru-code
3
- Version: 0.27.0
3
+ Version: 0.28.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
@@ -0,0 +1 @@
1
+ __version__ = "0.28.0"
@@ -136,6 +136,11 @@ async def run_cli(skip_permissions: bool = False, resume_id: str | None = None):
136
136
 
137
137
  # Load project configuration
138
138
  config = load_config()
139
+ ctx.config = config
140
+ # Populate invoke_skill's dynamic docstring so the LLM-facing schema lists
141
+ # the skills actually available on this machine.
142
+ from aru.tools.skill import _update_invoke_skill_docstring
143
+ _update_invoke_skill_docstring(config.skills)
139
144
  if config.agents_md:
140
145
  console.print("[dim]Loaded AGENTS.md[/dim]")
141
146
  if config.commands:
@@ -546,6 +551,12 @@ async def run_cli(skip_permissions: bool = False, resume_id: str | None = None):
546
551
  console.print(f" [bold cyan]{entry.name}[/bold cyan] [dim]{entry.description}[/dim]")
547
552
  continue
548
553
 
554
+ if user_input.lower() == "/plugin" or user_input.lower().startswith("/plugin "):
555
+ from aru.commands import handle_plugin_command
556
+ rest = user_input[len("/plugin"):].strip()
557
+ handle_plugin_command(rest)
558
+ continue
559
+
549
560
  if user_input.lower() == "/help":
550
561
  _show_help(config)
551
562
  continue
@@ -746,6 +757,10 @@ async def run_oneshot(prompt: str, print_only: bool = False, skip_permissions: b
746
757
  ctx = init_ctx(console=console, skip_permissions=skip_permissions)
747
758
 
748
759
  config = load_config()
760
+ ctx.config = config
761
+ # Populate invoke_skill's dynamic docstring (same as interactive path)
762
+ from aru.tools.skill import _update_invoke_skill_docstring
763
+ _update_invoke_skill_docstring(config.skills)
749
764
  session = Session()
750
765
  if config.default_model:
751
766
  session.model_ref = config.default_model
@@ -0,0 +1,244 @@
1
+ """Slash command definitions, help display, shell execution, and user prompts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import subprocess
6
+ import os
7
+
8
+ from rich.console import Console
9
+ from rich.panel import Panel
10
+ from rich.syntax import Syntax
11
+ from rich.text import Text
12
+
13
+ from aru.display import console
14
+
15
+ SLASH_COMMANDS = [
16
+ ("/help", "Show help and available commands", "/help"),
17
+ ("/plan", "Create an implementation plan", "/plan <task>"),
18
+ ("/model", "Switch model/provider", "/model [provider/model]"),
19
+ ("/sessions", "List recent sessions", "/sessions"),
20
+ ("/commands", "List custom commands", "/commands"),
21
+ ("/skills", "List available skills", "/skills"),
22
+ ("/agents", "List custom agents", "/agents"),
23
+ ("/mcp", "List loaded MCP tools", "/mcp"),
24
+ ("/plugin", "Manage cached plugins (install/list/remove/update)", "/plugin <subcommand>"),
25
+ ("/undo", "Undo last turn — restore files and/or conversation", "/undo"),
26
+ ("/cost", "Show detailed token usage and cost", "/cost"),
27
+ ("/quit", "Exit aru", "/quit"),
28
+ ]
29
+
30
+
31
+ def run_shell(command: str):
32
+ """Run a shell command directly, streaming output to the terminal."""
33
+ console.print()
34
+ console.print(Panel(
35
+ Syntax(command, "bash", theme="monokai"),
36
+ title="[bold]Shell[/bold]",
37
+ border_style="dim",
38
+ expand=False,
39
+ ))
40
+ try:
41
+ process = subprocess.Popen(
42
+ command,
43
+ shell=True,
44
+ stdout=subprocess.PIPE,
45
+ stderr=subprocess.STDOUT,
46
+ text=True,
47
+ cwd=os.getcwd(),
48
+ bufsize=1,
49
+ )
50
+ for line in process.stdout:
51
+ console.print(Text(line.rstrip()))
52
+ process.wait()
53
+ if process.returncode != 0:
54
+ console.print(f"[red]Exit code: {process.returncode}[/red]")
55
+ except KeyboardInterrupt:
56
+ process.kill()
57
+ console.print("\n[yellow]Interrupted.[/yellow]")
58
+ except Exception as e:
59
+ from rich.markup import escape
60
+ console.print(f"[red]Error: {escape(str(e))}[/red]")
61
+ console.print()
62
+
63
+
64
+ def ask_yes_no(prompt: str) -> bool:
65
+ """Ask the user a yes/no question."""
66
+ try:
67
+ answer = console.input(f"[bold yellow]{prompt} (y/n):[/bold yellow] ").strip().lower()
68
+ return answer in ("y", "yes", "s", "sim")
69
+ except (EOFError, KeyboardInterrupt):
70
+ return False
71
+
72
+
73
+ def handle_plugin_command(args: str) -> None:
74
+ """Handle /plugin <subcommand> [args] — install/list/remove/update/info."""
75
+ from rich.table import Table
76
+ from rich.markup import escape
77
+
78
+ parts = args.strip().split(None, 2)
79
+ if not parts:
80
+ _show_plugin_help()
81
+ return
82
+
83
+ subcmd = parts[0].lower()
84
+
85
+ if subcmd == "list":
86
+ from aru.plugin_cache import list_installed
87
+ entries = list_installed()
88
+ if not entries:
89
+ console.print("[dim]No plugins installed. Use /plugin install <spec> to add one.[/dim]")
90
+ return
91
+ table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
92
+ table.add_column("Name", style="cyan")
93
+ table.add_column("Version", style="green")
94
+ table.add_column("Source")
95
+ table.add_column("Spec", style="dim")
96
+ for e in entries:
97
+ table.add_row(e.id, e.version or "-", e.source, e.spec)
98
+ console.print(table)
99
+ return
100
+
101
+ if subcmd == "install":
102
+ if len(parts) < 2:
103
+ console.print("[yellow]Usage: /plugin install <spec> [name][/yellow]")
104
+ return
105
+ spec = parts[1]
106
+ name = parts[2] if len(parts) >= 3 else None
107
+ from aru.plugin_cache import install
108
+ console.print(f"[dim]Installing {escape(spec)}...[/dim]")
109
+ result = install(spec, name=name)
110
+ if not result.ok:
111
+ console.print(f"[red]Install failed: {escape(result.error or 'unknown error')}[/red]")
112
+ return
113
+ provides = result.provides
114
+ provides_str = ", ".join(f"{c} {k}" for k, c in provides.items()) or "no resources"
115
+ console.print(
116
+ f"[green]Installed {escape(result.name or '')}"
117
+ f"{f'@{result.version}' if result.version else ''}[/green] "
118
+ f"([dim]{result.state}[/dim]) -> {escape(str(result.target))}"
119
+ )
120
+ console.print(f"[dim]Provides: {provides_str}[/dim]")
121
+ console.print(
122
+ "[dim]Discovery refreshes on next aru restart. "
123
+ "Skills/agents/tools from the plugin will then be available.[/dim]"
124
+ )
125
+ return
126
+
127
+ if subcmd == "remove":
128
+ if len(parts) < 2:
129
+ console.print("[yellow]Usage: /plugin remove <name>[/yellow]")
130
+ return
131
+ name = parts[1]
132
+ from aru.plugin_cache import remove
133
+ if remove(name):
134
+ console.print(f"[green]Removed plugin: {escape(name)}[/green]")
135
+ else:
136
+ console.print(f"[yellow]Plugin not found: {escape(name)}[/yellow]")
137
+ return
138
+
139
+ if subcmd == "update":
140
+ if len(parts) < 2:
141
+ console.print("[yellow]Usage: /plugin update <name>[/yellow]")
142
+ return
143
+ name = parts[1]
144
+ from aru.plugin_cache import update
145
+ console.print(f"[dim]Updating {escape(name)}...[/dim]")
146
+ result = update(name)
147
+ if not result.ok:
148
+ console.print(f"[red]Update failed: {escape(result.error or 'unknown error')}[/red]")
149
+ return
150
+ console.print(
151
+ f"[green]Updated {escape(result.name or '')}"
152
+ f"{f'@{result.version}' if result.version else ''}[/green] "
153
+ f"([dim]{result.state}[/dim])"
154
+ )
155
+ return
156
+
157
+ if subcmd == "info":
158
+ if len(parts) < 2:
159
+ console.print("[yellow]Usage: /plugin info <name>[/yellow]")
160
+ return
161
+ name = parts[1]
162
+ from aru.plugin_cache import list_installed, read_manifest
163
+ from pathlib import Path
164
+ entries = {e.id: e for e in list_installed()}
165
+ entry = entries.get(name)
166
+ if entry is None:
167
+ console.print(f"[yellow]Plugin not found: {escape(name)}[/yellow]")
168
+ return
169
+ manifest = read_manifest(Path(entry.target))
170
+ console.print(f"[bold cyan]{escape(entry.id)}[/bold cyan]")
171
+ console.print(f" [dim]version:[/dim] {entry.version or '-'}")
172
+ console.print(f" [dim]source:[/dim] {entry.source}")
173
+ console.print(f" [dim]spec:[/dim] {escape(entry.spec)}")
174
+ console.print(f" [dim]target:[/dim] {escape(entry.target)}")
175
+ console.print(f" [dim]fingerprint:[/dim] {entry.fingerprint}")
176
+ console.print(f" [dim]first_time:[/dim] {entry.first_time}")
177
+ console.print(f" [dim]last_time:[/dim] {entry.last_time}")
178
+ if manifest:
179
+ desc = manifest.get("description")
180
+ if desc:
181
+ console.print(f" [dim]description:[/dim] {escape(str(desc))}")
182
+ engines = manifest.get("engines") or {}
183
+ if isinstance(engines, dict) and engines.get("aru"):
184
+ console.print(f" [dim]engines.aru:[/dim] {escape(str(engines['aru']))}")
185
+ return
186
+
187
+ _show_plugin_help()
188
+
189
+
190
+ def _show_plugin_help() -> None:
191
+ """Print /plugin command usage."""
192
+ from rich.table import Table
193
+ table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
194
+ table.add_column("Subcommand", style="cyan")
195
+ table.add_column("Description", style="dim")
196
+ table.add_row("/plugin install <spec> [name]", "Install a plugin from git or local path")
197
+ table.add_row("/plugin list", "List installed plugins")
198
+ table.add_row("/plugin remove <name>", "Uninstall a plugin")
199
+ table.add_row("/plugin update <name>", "Update a plugin (git pull)")
200
+ table.add_row("/plugin info <name>", "Show plugin metadata")
201
+ console.print(table)
202
+ console.print()
203
+ console.print("[dim]Spec formats:[/dim]")
204
+ console.print("[dim] github:user/repo — shorthand for GitHub[/dim]")
205
+ console.print("[dim] github:user/repo@v1.0.0 — pin to tag/branch[/dim]")
206
+ console.print("[dim] git+https://host/path.git — any git URL[/dim]")
207
+ console.print("[dim] file:///abs/path or ./rel — local directory[/dim]")
208
+
209
+
210
+ def _show_help(config) -> None:
211
+ """Display help with available commands."""
212
+ from rich.table import Table
213
+
214
+ table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
215
+ table.add_column("Command", style="cyan")
216
+ table.add_column("Description", style="dim")
217
+
218
+ table.add_row("/plan <task>", "Create detailed implementation plan")
219
+ table.add_row("/model [provider/model]", "Switch models (e.g., ollama/llama3.1, openai/gpt-4o)")
220
+ table.add_row("/sessions", "List recent sessions")
221
+ table.add_row("/commands", "List custom commands")
222
+ table.add_row("/skills", "List available skills")
223
+ table.add_row("/agents", "List custom agents")
224
+ table.add_row("/mcp", "List loaded MCP tools")
225
+ table.add_row("/plugin <subcmd>", "Manage plugins (install/list/remove/update/info)")
226
+ table.add_row("/undo", "Undo last turn (restore files and/or conversation)")
227
+ table.add_row("/help", "Show this help")
228
+ table.add_row("/quit", "Exit aru")
229
+ table.add_row("! <cmd>", "Run shell command")
230
+
231
+ if config and config.commands:
232
+ table.add_row("", "")
233
+ for name, cmd_def in config.commands.items():
234
+ table.add_row(f"/{name}", cmd_def.description)
235
+
236
+ if config and config.custom_agents:
237
+ primary = {k: v for k, v in config.custom_agents.items() if v.mode == "primary"}
238
+ if primary:
239
+ table.add_row("", "")
240
+ for name, agent_def in primary.items():
241
+ table.add_row(f"/{name}", f"[agent] {agent_def.description}")
242
+
243
+ console.print(table)
244
+ console.print()
@@ -535,9 +535,15 @@ def load_config(cwd: str | None = None) -> AgentConfig:
535
535
  config.commands = _load_commands(agents_dir)
536
536
 
537
537
  # Discover skills from multiple roots (agentskills.io convention)
538
- # Order: global paths first, project-local last (local overrides global)
538
+ # Order: cached plugins first (lowest priority), then global, then project-local
539
+ # (local overrides global overrides cache — lets users shadow plugin skills).
539
540
  home = Path.home()
540
541
  skill_roots: list[Path] = []
542
+ try:
543
+ from aru.plugin_cache import get_cached_plugin_roots
544
+ skill_roots.extend(get_cached_plugin_roots())
545
+ except Exception as exc: # defensive: never fail config load over cache
546
+ logger.warning("Failed to load cached plugin roots: %s", exc)
541
547
  for dirname in (".agents", ".claude"):
542
548
  global_dir = home / dirname
543
549
  if global_dir.is_dir():