Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 2 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ app = App(
),
)

@app.tool(id="send_email", description="Send one email.")
@app.tool(id="send_email")
def send_email(to: str, subject: str, body: str) -> dict:
"""Send one email."""
return {"ok": True, "to": to, "subject": subject}

DAILY_FOLLOWUPS = schedule.cron(
Expand Down Expand Up @@ -88,15 +89,6 @@ ara runtime control call --session sess-123 --action list_windows
ara runtime control call --session sess-123 --action launch_app --arg id=browser --arg url=https://mail.google.com
```

If you prefer embedded script commands (`python app.py deploy`), add:

```python
from ara_sdk import run_cli

if __name__ == "__main__":
run_cli(app)
```

`ara logs app.py` streams live runtime events for the app across all active runs.
Each line includes timestamp + run id + event type. To persist output, use shell piping:

Expand Down
2 changes: 1 addition & 1 deletion examples/01-c-agent-skills-loading.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@

@app.tool(
id="title_case_decorator",
description="Convert input text by dispatching to the decorator-registered title-case handler.",
)
def title_case_decorator(text: str) -> dict:
"""Convert input text by dispatching to the decorator-registered title-case handler."""
# Keep registry + decorator local to the tool function because ara_sdk stores
# and executes function source for runtime tools; module globals are not guaranteed.
handlers: dict[str, callable] = {}
Expand Down
3 changes: 2 additions & 1 deletion examples/02-canonical-email-chat-cron.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ def _looks_like_email(value: str) -> bool:
return re.fullmatch(r"[^@\s]+@[^@\s]+\.[^@\s]+", candidate) is not None


@app.tool(id="send_email", description="Send one email via Resend API.")
@app.tool(id="send_email")
def send_email(to: str, subject: str, body: str) -> dict:
"""Send one email via Resend API."""
api_key = (os.getenv("RESEND_API_KEY") or "").strip()
sender = (os.getenv("CRON_EMAIL_FROM") or "").strip()
recipient = (to or "").strip()
Expand Down
2 changes: 0 additions & 2 deletions src/ara_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
local_file,
runtime,
run_auth_cli,
run_cli,
run_runtime_cli,
sandbox,
schedule,
Expand All @@ -42,7 +41,6 @@
"local_file",
"runtime",
"run_auth_cli",
"run_cli",
"run_runtime_cli",
"sandbox",
"schedule",
Expand Down
4 changes: 2 additions & 2 deletions src/ara_sdk/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import sys
from types import ModuleType

from .core import App, run_auth_cli, run_cli, run_runtime_cli
from .core import _run_app_cli, App, run_auth_cli, run_runtime_cli


def _print_help(bin_name: str) -> None:
Expand Down Expand Up @@ -77,7 +77,7 @@ def main() -> None:
raise SystemExit(f"Script not found: {script}")
module = _load_module(script)
app = _discover_app(module)
run_cli(app, argv=[command, *sys.argv[3:]], default_command=command)
_run_app_cli(app, argv=[command, *sys.argv[3:]], default_command=command)


if __name__ == "__main__":
Expand Down
5 changes: 2 additions & 3 deletions src/ara_sdk/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1009,7 +1009,6 @@ def tool(
self,
*,
id: Optional[str] = None,
description: str = "",
parameters: Optional[dict[str, Any]] = None,
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
def decorator(fn: Callable[..., Any]) -> Callable[..., Any]:
Expand All @@ -1024,7 +1023,7 @@ def decorator(fn: Callable[..., Any]) -> Callable[..., Any]:
if not source.startswith("def "):
raise ValueError("@app.tool only supports standard def functions")
params_schema = dict(parameters) if isinstance(parameters, dict) else _callable_parameters_schema(fn)
tool_description = str(description or fn.__doc__ or "").strip()
tool_description = str(fn.__doc__ or "").strip()
item = {
"type": "function",
"function": {
Expand Down Expand Up @@ -2791,7 +2790,7 @@ def run_auth_cli(argv: Optional[list[str]] = None) -> None:
)


def run_cli(app: App | dict[str, Any], argv: Optional[list[str]] = None, *, default_command: str = "deploy") -> None:
def _run_app_cli(app: App | dict[str, Any], argv: Optional[list[str]] = None, *, default_command: str = "deploy") -> None:
app_obj = app if isinstance(app, App) else None
manifest = app_obj.manifest if app_obj is not None else dict(app)

Expand Down
14 changes: 7 additions & 7 deletions tests/test_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,14 @@ def send_email(to: str, subject: str, body: str) -> dict:
"""Send an email payload."""
return {"ok": True, "to": to, "subject": subject, "body": body}

app.tool(id="send_email", description="Send email via tool.")(send_email)
app.tool(id="send_email")(send_email)

manifest = app.manifest
tools = manifest["agent"]["tools"]

assert tools[0]["type"] == "function"
assert tools[0]["function"]["name"] == "send_email"
assert tools[0]["function"]["description"] == "Send email via tool."
assert tools[0]["function"]["description"] == "Send an email payload."
assert tools[0]["function"]["parameters"]["properties"]["subject"]["type"] == "string"
assert tools[0]["function_name"] == "send_email"
assert tools[0]["source"].startswith("def send_email")
Expand All @@ -136,9 +136,9 @@ def test_tool_supports_multiline_decorator_arguments():

@app.tool(
id="send_email",
description="Send an email payload.",
)
def send_email(to: str):
"""Send an email payload."""
return {"ok": True, "to": to}

tools = app.manifest["agent"]["tools"]
Expand Down Expand Up @@ -818,7 +818,7 @@ def deploy(self, **kwargs):
classmethod(lambda cls, *, manifest, cwd=None: stub),
)

core.run_cli(
core._run_app_cli(
_manifest_with_runtime(runtime_profile={}),
argv=["up", "--warm", "true"],
)
Expand Down Expand Up @@ -850,7 +850,7 @@ def setup_auth(self, **kwargs):
"from_env",
classmethod(lambda cls, *, manifest, cwd=None: stub),
)
core.run_cli(
core._run_app_cli(
_manifest_with_runtime(runtime_profile={}),
argv=["setup-auth", "--x-key-name", "demo-x", "--x-key-rpm", "55", "--ensure-runtime-key", "true"],
)
Expand All @@ -861,7 +861,7 @@ def setup_auth(self, **kwargs):

def test_cli_rejects_unknown_subcommand(capsys):
with pytest.raises(SystemExit) as exc:
core.run_cli(_manifest_with_runtime(runtime_profile={}), argv=["not-a-command"])
core._run_app_cli(_manifest_with_runtime(runtime_profile={}), argv=["not-a-command"])
assert exc.value.code == 2
err = capsys.readouterr().err
assert "invalid choice" in err
Expand Down Expand Up @@ -893,7 +893,7 @@ def logs(self, runtime_key=None, app_header_key=None):
classmethod(lambda cls, *, manifest, cwd=None: stub),
)

core.run_cli(
core._run_app_cli(
_manifest_with_runtime(runtime_profile={}),
argv=["logs"],
)
Expand Down
4 changes: 2 additions & 2 deletions tests/test_standalone_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from ara_sdk import __main__ as sdk_main


def test_standalone_cli_dispatches_command_to_run_cli(tmp_path, monkeypatch):
def test_standalone_cli_dispatches_command_to_app_cli(tmp_path, monkeypatch):
script = tmp_path / "app.py"
script.write_text(
"\n".join(
Expand All @@ -25,7 +25,7 @@ def _run_cli(app, argv=None, *, default_command="deploy"):
captured["argv"] = list(argv or [])
captured["default_command"] = default_command

monkeypatch.setattr(sdk_main, "run_cli", _run_cli)
monkeypatch.setattr(sdk_main, "_run_app_cli", _run_cli)
monkeypatch.setattr(
sdk_main.sys,
"argv",
Expand Down
Loading