From 51f3810e52ff344312e2ef1aa465982b26146008 Mon Sep 17 00:00:00 2001 From: kanywst Date: Sat, 27 Jun 2026 17:57:59 +0900 Subject: [PATCH] chore!: rename package a2claude -> a2acode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The project is no longer Claude-specific: it fronts any ACP coding agent over A2A. The a2claude name (A2A + Claude) misrepresents that, so rename the package, module, CLI command, OpenTelemetry span/attribute names, and repository URL to a2acode (A2A + code) — agent-neutral while keeping the a2* lineage. BREAKING CHANGE: the distribution and import name is now `a2acode` and the CLI command is `a2acode`. Update installs, imports, and any `a2claude ...` scripts. The GitHub repository and PyPI project must be renamed to match. --- CLAUDE.md | 10 ++-- README.md | 46 +++++++++---------- assets/demo.tape | 6 +-- pyproject.toml | 10 ++-- src/{a2claude => a2acode}/__init__.py | 0 src/{a2claude => a2acode}/auth.py | 0 .../backends/__init__.py | 0 src/{a2claude => a2acode}/backends/acp.py | 0 src/{a2claude => a2acode}/backends/base.py | 0 src/{a2claude => a2acode}/backends/claude.py | 0 src/{a2claude => a2acode}/backends/diff.py | 0 src/{a2claude => a2acode}/backends/echo.py | 0 src/{a2claude => a2acode}/backends/session.py | 0 src/{a2claude => a2acode}/card.py | 2 +- src/{a2claude => a2acode}/cli.py | 10 ++-- src/{a2claude => a2acode}/executor.py | 6 +-- src/{a2claude => a2acode}/py.typed | 0 src/{a2claude => a2acode}/server.py | 0 src/{a2claude => a2acode}/tracing.py | 6 +-- tests/test_acp.py | 4 +- tests/test_auth.py | 8 ++-- tests/test_claude_backend.py | 4 +- tests/test_cli.py | 10 ++-- tests/test_executor.py | 6 +-- tests/test_signing.py | 6 +-- tests/test_smoke.py | 12 ++--- tests/test_tracing.py | 8 ++-- uv.lock | 2 +- 28 files changed, 78 insertions(+), 78 deletions(-) rename src/{a2claude => a2acode}/__init__.py (100%) rename src/{a2claude => a2acode}/auth.py (100%) rename src/{a2claude => a2acode}/backends/__init__.py (100%) rename src/{a2claude => a2acode}/backends/acp.py (100%) rename src/{a2claude => a2acode}/backends/base.py (100%) rename src/{a2claude => a2acode}/backends/claude.py (100%) rename src/{a2claude => a2acode}/backends/diff.py (100%) rename src/{a2claude => a2acode}/backends/echo.py (100%) rename src/{a2claude => a2acode}/backends/session.py (100%) rename src/{a2claude => a2acode}/card.py (99%) rename src/{a2claude => a2acode}/cli.py (97%) rename src/{a2claude => a2acode}/executor.py (99%) rename src/{a2claude => a2acode}/py.typed (100%) rename src/{a2claude => a2acode}/server.py (100%) rename src/{a2claude => a2acode}/tracing.py (84%) diff --git a/CLAUDE.md b/CLAUDE.md index f851313..6f47ed7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## What this is -a2claude serves a coding agent over the [A2A](https://a2aprotocol.ai/) protocol. Other agents discover it through its agent card and delegate coding work; it drives a real coding-agent session and streams the structured work (tool calls, file diffs, permission requests, cost, session continuity) back — not just flattened text in / text out. +a2acode serves a coding agent over the [A2A](https://a2aprotocol.ai/) protocol. Other agents discover it through its agent card and delegate coding work; it drives a real coding-agent session and streams the structured work (tool calls, file diffs, permission requests, cost, session continuity) back — not just flattened text in / text out. It is a **bridge between two interop standards**: it speaks Zed's [Agent Client Protocol](https://agentclientprotocol.com) (ACP) to the coding agent (Claude Code, Gemini CLI, Codex, OpenHands, ... — a launch-command choice) and A2A to the caller. The default `acp` backend makes the agent vendor-neutral; a `claude` backend (Claude Agent SDK, no subprocess) and an `echo` backend also ship. @@ -26,8 +26,8 @@ CI (`.github/workflows/ci.yml`) runs lint, format-check, mypy, pytest, and build Run the server end to end without an API key using the `echo` backend: ```bash -uv run a2claude serve --backend echo & -uv run a2claude call "fix the failing test" +uv run a2acode serve --backend echo & +uv run a2acode call "fix the failing test" ``` ## Architecture @@ -46,7 +46,7 @@ CLI / A2A caller -> echo.py dependency-free mirror, for tests and offline wiring checks ``` -The `acp` backend is the headline: ACP's `session/update` stream, diff content, and `session/request_permission` map almost one-to-one onto the event vocabulary below, so vendor-neutrality is a launch-command choice rather than a backend per agent. ACP itself targets human-driven editors; a2claude's value is exposing an ACP agent to *remote A2A callers* with the permission round-trip and cost preserved. +The `acp` backend is the headline: ACP's `session/update` stream, diff content, and `session/request_permission` map almost one-to-one onto the event vocabulary below, so vendor-neutrality is a launch-command choice rather than a backend per agent. ACP itself targets human-driven editors; a2acode's value is exposing an ACP agent to *remote A2A callers* with the permission round-trip and cost preserved. ### The event vocabulary (`backends/base.py`) @@ -82,7 +82,7 @@ The server does **not** load the developer's personal Claude settings (`setting_ - **Keep the layering intact.** If you reach for `acp`/`claude_agent_sdk` outside their own backend module, or for `a2a.*` inside a backend, that's the wrong layer. - **The translation functions are pure and side-effect free** — `events_from_message` (claude), `events_from_update` + `select_option` (acp), and `diff.py` — so the protocol mapping is unit-testable without a live agent. Keep them that way; stateful concerns (cost capture, permission parking) live in the backend's `Client`/`drive`, not the translator. - Python 3.13+, full type hints, `from __future__ import annotations`. Ruff enforces `E, F, I, UP, B, SIM` at line length 88. -- The `acp` and `claude` backends are imported lazily (`make_backend`) so `echo` works without their runtime deps. The Claude SDK is an optional extra (`a2claude[claude]`). New optional backends should follow the same lazy pattern. +- The `acp` and `claude` backends are imported lazily (`make_backend`) so `echo` works without their runtime deps. The Claude SDK is an optional extra (`a2acode[claude]`). New optional backends should follow the same lazy pattern. ## Reference material diff --git a/README.md b/README.md index 36e4932..222db40 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ -a2claude +a2acode -# a2claude +# a2acode Serve a coding agent over the [A2A](https://a2aprotocol.ai/) protocol. Other agents call it over A2A; it drives a real coding-agent session in your project — Claude Code, or any agent that speaks Zed's [Agent Client Protocol](https://agentclientprotocol.com) (ACP): Gemini CLI, Codex, OpenHands, and more — and streams the work back as it happens. -[![CI](https://github.com/kanywst/a2claude/actions/workflows/ci.yml/badge.svg)](https://github.com/kanywst/a2claude/actions/workflows/ci.yml) +[![CI](https://github.com/kanywst/a2acode/actions/workflows/ci.yml/badge.svg)](https://github.com/kanywst/a2acode/actions/workflows/ci.yml) [![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE) [![Python 3.13+](https://img.shields.io/badge/python-3.13+-blue.svg)](https://www.python.org/) [![Protocol: A2A 1.0](https://img.shields.io/badge/protocol-A2A%201.0-D97757.svg)](https://a2aprotocol.ai/) -![a2claude streaming a task, then pausing on a permission prompt](assets/demo.gif) +![a2acode streaming a task, then pausing on a permission prompt](assets/demo.gif) -Most adapters that put a coding agent behind A2A flatten everything to text in, text out. a2claude keeps the structure the agent produces: the tools it runs, the files it changes, what it costs, the approvals it needs, and how to continue on the next turn. It bridges two Linux Foundation interop standards — **ACP** (how editors and clients talk to coding agents) on the agent side, **A2A** (how agents delegate to each other) on the caller side — so any ACP agent becomes a peer any A2A orchestrator can call. +Most adapters that put a coding agent behind A2A flatten everything to text in, text out. a2acode keeps the structure the agent produces: the tools it runs, the files it changes, what it costs, the approvals it needs, and how to continue on the next turn. It bridges two Linux Foundation interop standards — **ACP** (how editors and clients talk to coding agents) on the agent side, **A2A** (how agents delegate to each other) on the caller side — so any ACP agent becomes a peer any A2A orchestrator can call. ## How it maps to A2A @@ -30,13 +30,13 @@ The mapping is all in `executor.py`. Backends only emit normalized events; they Anthropic now ships its own ways to run Claude Code beyond the terminal: Claude Code on the web, background agents, cloud-hosted Routines, and the Managed Agents API. These are the right choices when you want Anthropic to host the run and you live in their ecosystem, and they are typically tied to Anthropic infrastructure and a GitHub-centric flow. -a2claude solves a different problem: making any coding agent a first-class peer on a vendor-neutral [A2A](https://a2aprotocol.ai/) mesh. An orchestrator built on any framework discovers it through its agent card and delegates coding work the same way it would to any other A2A agent. The run happens on infrastructure you control, in a workspace you point it at. Reach for a2claude when: +a2acode solves a different problem: making any coding agent a first-class peer on a vendor-neutral [A2A](https://a2aprotocol.ai/) mesh. An orchestrator built on any framework discovers it through its agent card and delegates coding work the same way it would to any other A2A agent. The run happens on infrastructure you control, in a workspace you point it at. Reach for a2acode when: - another agent (not a human at a prompt) is the caller, and it speaks A2A; - you want the run on your own infrastructure and data boundary, not a vendor VM; - you do not want to bet on one vendor's coding agent: ACP makes the backend a launch-command choice, so swapping Claude Code for Codex, Gemini CLI, or OpenHands does not touch the protocol surface your callers depend on. -ACP already standardizes the editor↔agent side and a dozen agents speak it; a2claude is the piece that exposes an ACP agent to *remote autonomous callers* over A2A, with permission round-trips and cost preserved as first-class protocol citizens — the part ACP leaves out because it assumes a human in an editor. The practical user is the platform team building that mesh, not the individual developer. +ACP already standardizes the editor↔agent side and a dozen agents speak it; a2acode is the piece that exposes an ACP agent to *remote autonomous callers* over A2A, with permission round-trips and cost preserved as first-class protocol citizens — the part ACP leaves out because it assumes a human in an editor. The practical user is the platform team building that mesh, not the individual developer. ## Requirements @@ -55,9 +55,9 @@ uv sync The `echo` backend needs no API key and no Claude install, so you can exercise the whole path offline first: ```bash -uv run a2claude serve --backend echo & +uv run a2acode serve --backend echo & # once the "Uvicorn running" line appears: -uv run a2claude call "fix the failing test" +uv run a2acode call "fix the failing test" ``` ```text @@ -72,30 +72,30 @@ fix the failing test Then point it at a real project. The default backend is `acp`, fronting Claude Code through its ACP adapter: ```bash -uv run a2claude serve --cwd /path/to/project # acp + claude by default -uv run a2claude call "add a /health endpoint" --url http://localhost:9100/ +uv run a2acode serve --cwd /path/to/project # acp + claude by default +uv run a2acode call "add a /health endpoint" --url http://localhost:9100/ ``` Swap the agent without touching anything else: ```bash -uv run a2claude serve --agent gemini --cwd /path/to/project -uv run a2claude serve --agent-command "npx -y some-other-acp-agent" +uv run a2acode serve --agent gemini --cwd /path/to/project +uv run a2acode serve --agent-command "npx -y some-other-acp-agent" ``` Continue the same conversation by passing the `context` from a previous turn: ```bash -uv run a2claude call "now add a test for it" --context +uv run a2acode call "now add a test for it" --context ``` ## Commands | Command | Description | | -------------------- | -------------------------------------------- | -| `a2claude serve` | Start the A2A server | -| `a2claude call TEXT` | Send a message and print the streamed events | -| `a2claude card` | Fetch and print the agent card | +| `a2acode serve` | Start the A2A server | +| `a2acode call TEXT` | Send a message and print the streamed events | +| `a2acode card` | Fetch and print the agent card | The agent card is served at `/.well-known/agent-card.json` and advertises Claude Code's abilities as discrete skills (generation, refactor, debug, review, test, explain). @@ -118,7 +118,7 @@ Each agent authenticates the way its own tooling does, inherited from the server A caller that discovers this server only has the agent card to go on. Sign it so the caller can confirm the card came from you and was not swapped in transit: ```bash -uv run a2claude serve --sign-key card-signing.pem --sign-kid my-key-1 --sign-alg ES256 +uv run a2acode serve --sign-key card-signing.pem --sign-kid my-key-1 --sign-alg ES256 ``` The card is then served with a JWS signature over its canonical form. `--sign-key` is a path to a file holding the key: a PEM private key for asymmetric algorithms (`ES256`, `RS256`), or a shared secret for `HS256`. `--sign-kid` is the key id a verifier uses to look up the matching public key. Unsigned is still the default. @@ -128,7 +128,7 @@ The card is then served with a JWS signature over its canonical form. `--sign-ke A signed card proves who the server is; this proves the caller is allowed in. Require a bearer token and the server rejects any task request that does not carry it: ```bash -uv run a2claude serve --auth-token-file caller-token.txt +uv run a2acode serve --auth-token-file caller-token.txt ``` When `--auth-token-file` is set, callers must send `Authorization: Bearer `; a request without a valid token gets `401 Unauthorized`. The agent card stays public so a caller can still fetch it to discover the requirement, and the card advertises the bearer scheme in `securitySchemes`. Without the flag the server stays open, as before. @@ -140,10 +140,10 @@ A2A keeps the credential at the HTTP layer, so this composes with whatever your A tool that needs approval pauses the task in the A2A `input-required` state instead of being skipped. The caller answers with a follow-up message on the same task: ```bash -uv run a2claude call "sudo reboot" +uv run a2acode call "sudo reboot" # ... [input-required] Permission requested for Bash: $ sudo reboot -# reply: a2claude call "allow" --task --context -uv run a2claude call "allow" --task --context +# reply: a2acode call "allow" --task --context +uv run a2acode call "allow" --task --context ``` `allow` (or `yes`, `approve`, `ok`) approves; anything else denies. The agent session stays alive across the pause, so it resumes exactly where it stopped. Over ACP this is the agent's `session/request_permission` call answered from the A2A caller's reply; with the `claude` backend it routes through the Claude SDK's `can_use_tool`. @@ -156,7 +156,7 @@ The agent card advertises push notifications. A caller can register a webhook fo ## Observability -Debugging one agent is hard; debugging a chain of them without traces is worse. Because A2A runs over HTTP, it drops straight into OpenTelemetry: install the extra and the A2A SDK's instrumentation plus a per-task `a2claude.execute` span light up, with W3C trace context propagating across the call so client and server spans share one trace. +Debugging one agent is hard; debugging a chain of them without traces is worse. Because A2A runs over HTTP, it drops straight into OpenTelemetry: install the extra and the A2A SDK's instrumentation plus a per-task `a2acode.execute` span light up, with W3C trace context propagating across the call so client and server spans share one trace. ```bash uv sync --extra telemetry diff --git a/assets/demo.tape b/assets/demo.tape index 18d72ef..b203dce 100644 --- a/assets/demo.tape +++ b/assets/demo.tape @@ -14,20 +14,20 @@ Hide Type 'cd "$(git rev-parse --show-toplevel)" && source .venv/bin/activate && export PS1="$ " _ZO_DOCTOR=0 && clear' Enter Sleep 1.5s -Type "a2claude serve --backend echo > /tmp/demo.log 2>&1 &" +Type "a2acode serve --backend echo > /tmp/demo.log 2>&1 &" Enter Sleep 2.5s Type "clear" Enter Show -Type "a2claude call 'refactor the parser'" +Type "a2acode call 'refactor the parser'" Enter Sleep 3.5s Type "# a tool that needs approval pauses the task:" Enter -Type "a2claude call 'sudo reboot'" +Type "a2acode call 'sudo reboot'" Enter Sleep 4s Sleep 1.5s diff --git a/pyproject.toml b/pyproject.toml index 58a8cdc..002aec4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "a2claude" +name = "a2acode" version = "0.3.0" description = "Serve Claude Code and other ACP coding agents over the A2A protocol." readme = "README.md" @@ -21,15 +21,15 @@ dependencies = [ # Claude Code too, so this is only needed for the SDK-native path. claude = ["claude-agent-sdk>=0.2.101"] # Distributed tracing. Pulls the A2A SDK's OpenTelemetry instrumentation plus -# the API a2claude's own spans import directly (declared rather than relied on -# transitively through the SDK). Install with `a2claude[telemetry]`. +# the API a2acode's own spans import directly (declared rather than relied on +# transitively through the SDK). Install with `a2acode[telemetry]`. telemetry = ["a2a-sdk[telemetry]>=1.1,<2", "opentelemetry-api>=1.33"] [project.scripts] -a2claude = "a2claude.cli:app" +a2acode = "a2acode.cli:app" [project.urls] -Repository = "https://github.com/kanywst/a2claude" +Repository = "https://github.com/kanywst/a2acode" [dependency-groups] dev = [ diff --git a/src/a2claude/__init__.py b/src/a2acode/__init__.py similarity index 100% rename from src/a2claude/__init__.py rename to src/a2acode/__init__.py diff --git a/src/a2claude/auth.py b/src/a2acode/auth.py similarity index 100% rename from src/a2claude/auth.py rename to src/a2acode/auth.py diff --git a/src/a2claude/backends/__init__.py b/src/a2acode/backends/__init__.py similarity index 100% rename from src/a2claude/backends/__init__.py rename to src/a2acode/backends/__init__.py diff --git a/src/a2claude/backends/acp.py b/src/a2acode/backends/acp.py similarity index 100% rename from src/a2claude/backends/acp.py rename to src/a2acode/backends/acp.py diff --git a/src/a2claude/backends/base.py b/src/a2acode/backends/base.py similarity index 100% rename from src/a2claude/backends/base.py rename to src/a2acode/backends/base.py diff --git a/src/a2claude/backends/claude.py b/src/a2acode/backends/claude.py similarity index 100% rename from src/a2claude/backends/claude.py rename to src/a2acode/backends/claude.py diff --git a/src/a2claude/backends/diff.py b/src/a2acode/backends/diff.py similarity index 100% rename from src/a2claude/backends/diff.py rename to src/a2acode/backends/diff.py diff --git a/src/a2claude/backends/echo.py b/src/a2acode/backends/echo.py similarity index 100% rename from src/a2claude/backends/echo.py rename to src/a2acode/backends/echo.py diff --git a/src/a2claude/backends/session.py b/src/a2acode/backends/session.py similarity index 100% rename from src/a2claude/backends/session.py rename to src/a2acode/backends/session.py diff --git a/src/a2claude/card.py b/src/a2acode/card.py similarity index 99% rename from src/a2claude/card.py rename to src/a2acode/card.py index adb0bbb..a3debf1 100644 --- a/src/a2claude/card.py +++ b/src/a2acode/card.py @@ -29,7 +29,7 @@ try: # The agent card version tracks the package version, read from installed # metadata so there is one source of truth (pyproject) and it cannot drift. - VERSION = _package_version("a2claude") + VERSION = _package_version("a2acode") except PackageNotFoundError: # running from a source tree without an install VERSION = "0.0.0" diff --git a/src/a2claude/cli.py b/src/a2acode/cli.py similarity index 97% rename from src/a2claude/cli.py rename to src/a2acode/cli.py index 8cd4985..bdfeff1 100644 --- a/src/a2claude/cli.py +++ b/src/a2acode/cli.py @@ -2,9 +2,9 @@ Three commands, enough to run the server and exercise it by hand: - a2claude serve start the A2A server - a2claude call TEXT send a message and print the streamed events - a2claude card fetch and print the agent card + a2acode serve start the A2A server + a2acode call TEXT send a message and print the streamed events + a2acode card fetch and print the agent card """ from __future__ import annotations @@ -174,7 +174,7 @@ def serve( auth_token=auth_token, ) label = f"{backend}:{agent}" if backend == "acp" else backend - typer.echo(f"a2claude: backend={label} card={_local_url(host, port)}") + typer.echo(f"a2acode: backend={label} card={_local_url(host, port)}") uvicorn.run(asgi_app, host=host, port=port, log_level="info") @@ -270,7 +270,7 @@ async def _call(text: str, url: str, context: str | None, task: str | None) -> N def _render_input_required(line: str, ids: dict[str, str], url: str) -> None: typer.echo(f"[input-required] {line}") follow = ( - f'a2claude call "allow" --task {ids["task"]} ' + f'a2acode call "allow" --task {ids["task"]} ' f"--context {ids['context']} --url {url}" ) typer.echo(f" reply: {follow}") diff --git a/src/a2claude/executor.py b/src/a2acode/executor.py similarity index 99% rename from src/a2claude/executor.py rename to src/a2acode/executor.py index 980b579..48be2fe 100644 --- a/src/a2claude/executor.py +++ b/src/a2acode/executor.py @@ -128,11 +128,11 @@ async def execute(self, context: RequestContext, event_queue: EventQueue) -> Non # span() drops None-valued attributes, so no fallbacks are needed; the # ids are populated by the SDK before execute is called. with span( - "a2claude.execute", + "a2acode.execute", **{ "a2a.task_id": context.task_id, "a2a.context_id": context.context_id, - "a2claude.backend": self._backend.name, + "a2acode.backend": self._backend.name, }, ): await self._execute(context, event_queue) @@ -293,7 +293,7 @@ async def _request_input(updater: TaskUpdater, event: PermissionRequest) -> None message=updater.new_agent_message( [Part(text=f"Permission requested for {event.tool_name}: {line}")], metadata={ - "a2claude_permission": { + "a2acode_permission": { "request_id": event.request_id, "tool": event.tool_name, "input": event.tool_input, diff --git a/src/a2claude/py.typed b/src/a2acode/py.typed similarity index 100% rename from src/a2claude/py.typed rename to src/a2acode/py.typed diff --git a/src/a2claude/server.py b/src/a2acode/server.py similarity index 100% rename from src/a2claude/server.py rename to src/a2acode/server.py diff --git a/src/a2claude/tracing.py b/src/a2acode/tracing.py similarity index 84% rename from src/a2claude/tracing.py rename to src/a2acode/tracing.py index 80d4ecd..aeaeb12 100644 --- a/src/a2claude/tracing.py +++ b/src/a2acode/tracing.py @@ -2,10 +2,10 @@ A2A runs over HTTP, so it slots into standard OpenTelemetry tracing: the SDK already instruments its own client/server/task paths, and this adds a span for -a2claude's own protocol-mapping layer so a trace shows where time went inside +a2acode's own protocol-mapping layer so a trace shows where time went inside the executor, not just in the SDK. -OpenTelemetry is an optional dependency (install ``a2claude[telemetry]``). When +OpenTelemetry is an optional dependency (install ``a2acode[telemetry]``). When it is absent, ``span`` is a no-op context manager, so the core install and the hot path stay free of the dependency. Trace context propagation over HTTP headers is handled by the standard OTel instrumentation, not here. @@ -20,7 +20,7 @@ try: from opentelemetry import trace - _tracer: Any | None = trace.get_tracer("a2claude") + _tracer: Any | None = trace.get_tracer("a2acode") except ImportError: # opentelemetry not installed: tracing is a no-op _tracer = None diff --git a/tests/test_acp.py b/tests/test_acp.py index 0b49a82..163b9d8 100644 --- a/tests/test_acp.py +++ b/tests/test_acp.py @@ -11,12 +11,12 @@ from acp import schema as s from acp import text_block, tool_diff_content -from a2claude.backends.acp import ( +from a2acode.backends.acp import ( _BridgeClient, events_from_update, select_option, ) -from a2claude.backends.base import ( +from a2acode.backends.base import ( FileChange, PermissionDecision, TextDelta, diff --git a/tests/test_auth.py b/tests/test_auth.py index e19e23c..e2135e5 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -8,9 +8,9 @@ from starlette.testclient import TestClient -from a2claude.backends import make_backend -from a2claude.card import build_card -from a2claude.server import build_app +from a2acode.backends import make_backend +from a2acode.card import build_card +from a2acode.server import build_app TOKEN = "s3cret-bearer-token" @@ -74,7 +74,7 @@ def test_no_auth_by_default(): def test_middleware_rejects_empty_token(): import pytest - from a2claude.auth import BearerAuthMiddleware + from a2acode.auth import BearerAuthMiddleware with pytest.raises(ValueError, match="empty"): BearerAuthMiddleware(lambda *a: None, token=" ") diff --git a/tests/test_claude_backend.py b/tests/test_claude_backend.py index 752fb83..7178f2e 100644 --- a/tests/test_claude_backend.py +++ b/tests/test_claude_backend.py @@ -9,8 +9,8 @@ ToolUseBlock, ) -from a2claude.backends.base import FileChange, Result, RunRequest, TextDelta, ToolUse -from a2claude.backends.claude import ClaudeBackend, events_from_message +from a2acode.backends.base import FileChange, Result, RunRequest, TextDelta, ToolUse +from a2acode.backends.claude import ClaudeBackend, events_from_message def test_events_from_assistant_message_with_write(): diff --git a/tests/test_cli.py b/tests/test_cli.py index 81d23b9..1968404 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -7,7 +7,7 @@ from typer.testing import CliRunner -from a2claude.cli import app +from a2acode.cli import app runner = CliRunner() @@ -25,7 +25,7 @@ def _plain(output: str) -> str: return re.sub(r"[^a-zA-Z0-9-]", "", no_ansi) -@patch("a2claude.cli.uvicorn.run") +@patch("a2acode.cli.uvicorn.run") def test_serve_rejects_invalid_permission_mode(mock_run: MagicMock) -> None: # --permission-mode is a Claude-only flag, so it is validated on the claude # path. A bad value fails before uvicorn.run, so the server never starts. @@ -39,7 +39,7 @@ def test_serve_rejects_invalid_permission_mode(mock_run: MagicMock) -> None: assert "bogus" in plain -@patch("a2claude.cli.uvicorn.run") +@patch("a2acode.cli.uvicorn.run") def test_serve_accepts_a_valid_permission_mode(mock_run: MagicMock) -> None: # 'plan' is valid, so validation passes and execution proceeds to uvicorn.run # (mocked here so no socket is bound). @@ -50,7 +50,7 @@ def test_serve_accepts_a_valid_permission_mode(mock_run: MagicMock) -> None: mock_run.assert_called_once() -@patch("a2claude.cli.uvicorn.run") +@patch("a2acode.cli.uvicorn.run") def test_serve_ignores_permission_mode_off_the_claude_path( mock_run: MagicMock, ) -> None: @@ -63,7 +63,7 @@ def test_serve_ignores_permission_mode_off_the_claude_path( mock_run.assert_called_once() -@patch("a2claude.cli.uvicorn.run") +@patch("a2acode.cli.uvicorn.run") def test_serve_rejects_malformed_agent_command(mock_run: MagicMock) -> None: # An unmatched quote makes shlex.split raise; it must surface as a clean # BadParameter, not a traceback, and the server must not start. diff --git a/tests/test_executor.py b/tests/test_executor.py index f4828b5..e8b7cdf 100644 --- a/tests/test_executor.py +++ b/tests/test_executor.py @@ -2,9 +2,9 @@ from __future__ import annotations -from a2claude import executor as executor_mod -from a2claude.backends import BackendSession, RunRequest, make_backend -from a2claude.executor import ClaudeCodeExecutor +from a2acode import executor as executor_mod +from a2acode.backends import BackendSession, RunRequest, make_backend +from a2acode.executor import ClaudeCodeExecutor def test_remember_session_moves_reused_context_to_most_recent(): diff --git a/tests/test_signing.py b/tests/test_signing.py index 58054ad..3229f3d 100644 --- a/tests/test_signing.py +++ b/tests/test_signing.py @@ -12,8 +12,8 @@ create_signature_verifier, ) -from a2claude.card import build_card, sign_card, signer_from_key_file -from a2claude.server import build_app +from a2acode.card import build_card, sign_card, signer_from_key_file +from a2acode.server import build_app SECRET = "a-shared-secret-at-least-32-bytes-long!!" @@ -51,7 +51,7 @@ def test_signer_from_key_file(tmp_path): def test_build_app_signs_the_served_card(tmp_path): from starlette.testclient import TestClient - from a2claude.backends import make_backend + from a2acode.backends import make_backend key_file = tmp_path / "card.key" key_file.write_text(SECRET) diff --git a/tests/test_smoke.py b/tests/test_smoke.py index fedb379..1a8482d 100644 --- a/tests/test_smoke.py +++ b/tests/test_smoke.py @@ -4,7 +4,7 @@ import pytest -from a2claude.backends import ( +from a2acode.backends import ( BackendSession, PermissionDecision, PermissionRequest, @@ -14,8 +14,8 @@ ToolUse, make_backend, ) -from a2claude.backends.diff import file_changes -from a2claude.server import build_app +from a2acode.backends.diff import file_changes +from a2acode.server import build_app async def _drive(backend, request): @@ -143,7 +143,7 @@ def test_build_app_returns_asgi_app(): def test_card_advertises_jsonrpc_and_rest(): from a2a.utils.constants import TransportProtocol - from a2claude.card import build_card + from a2acode.card import build_card card = build_card("http://localhost:9100/") bindings = {iface.protocol_binding for iface in card.supported_interfaces} @@ -154,10 +154,10 @@ def test_card_advertises_jsonrpc_and_rest(): def test_card_version_tracks_package_version(): from importlib.metadata import PackageNotFoundError, version - from a2claude.card import build_card + from a2acode.card import build_card try: - expected = version("a2claude") + expected = version("a2acode") except PackageNotFoundError: # source tree without an install: same fallback expected = "0.0.0" assert build_card("http://localhost:9100/").version == expected diff --git a/tests/test_tracing.py b/tests/test_tracing.py index 1627c6e..0bd42a6 100644 --- a/tests/test_tracing.py +++ b/tests/test_tracing.py @@ -19,8 +19,8 @@ InMemorySpanExporter, ) -from a2claude.backends import make_backend -from a2claude.executor import ClaudeCodeExecutor +from a2acode.backends import make_backend +from a2acode.executor import ClaudeCodeExecutor @pytest.fixture @@ -59,9 +59,9 @@ async def test_execute_emits_a_span_with_attributes(exporter): await executor.execute(context, EventQueue()) spans = exporter.get_finished_spans() - execute_spans = [s for s in spans if s.name == "a2claude.execute"] + execute_spans = [s for s in spans if s.name == "a2acode.execute"] assert len(execute_spans) == 1 attrs = execute_spans[0].attributes assert attrs["a2a.task_id"] == "task-1" assert attrs["a2a.context_id"] == "ctx-1" - assert attrs["a2claude.backend"] == "echo" + assert attrs["a2acode.backend"] == "echo" diff --git a/uv.lock b/uv.lock index 91b51a1..923cce4 100644 --- a/uv.lock +++ b/uv.lock @@ -39,7 +39,7 @@ telemetry = [ ] [[package]] -name = "a2claude" +name = "a2acode" version = "0.3.0" source = { editable = "." } dependencies = [