diff --git a/agentscore_commerce/challenge/how_to_pay.py b/agentscore_commerce/challenge/how_to_pay.py index 83744de..90cfde1 100644 --- a/agentscore_commerce/challenge/how_to_pay.py +++ b/agentscore_commerce/challenge/how_to_pay.py @@ -104,7 +104,7 @@ async def build_how_to_pay( "what_it_does": ( f"Hits this endpoint, receives this same 402, signs the MPP challenge on {network_name}, and " "submits the credential back via Authorization: Payment. Either client (tempo request or " - "agentscore-pay pay --chain tempo) works — both run the full MPP handshake." + "agentscore-pay pay --chain tempo) works; both run the full MPP handshake." ), } if recommend == "both": @@ -203,7 +203,7 @@ async def build_how_to_pay( ) elif link_cli_blocked: stripe_block["note"] = ( - "link-cli SPT path not available for this purchase — Stripe link-cli caps spend requests at $500.00 " + "link-cli SPT path not available for this purchase: Stripe link-cli caps spend requests at $500.00 " f"($50000 cents); your total is ${total_num}. Use your own Stripe account with the SharedPaymentToken " "API instead." ) diff --git a/agentscore_commerce/checkout.py b/agentscore_commerce/checkout.py index 4ab8a64..b131cd1 100644 --- a/agentscore_commerce/checkout.py +++ b/agentscore_commerce/checkout.py @@ -1,6 +1,6 @@ """High-level Checkout orchestrator; composes 402-emit + verify+settle. -The Checkout primitive collapses the agent-commerce dance (emit 402 → +The Checkout primitive collapses the agentic-commerce dance (emit 402 → verify+settle on retry → respond) into a single ``await checkout.handle(request)`` call. It services every merchant shape: @@ -30,7 +30,7 @@ ``reference_id`` (a UUID minted on first contact). Goods merchants persist this as their order id; API merchants treat it as a per-call request id. -Usage (goods seller, full agent-commerce flow):: +Usage (goods seller, full agentic-commerce flow):: checkout = Checkout( rails={ @@ -811,7 +811,7 @@ def _strip_payment_headers_from_raw(raw: Any) -> Any: class Checkout: - """High-level agent-commerce orchestrator. + """High-level agentic-commerce orchestrator. Composes :func:`build_accepted_methods`, :func:`build_how_to_pay`, :func:`respond_402`, :func:`verify_x402_request`, and @@ -1116,7 +1116,7 @@ def _x402_base_network(self) -> str | None: return None async def handle(self, request: CheckoutRequest) -> CheckoutResult: - """One-call agent-commerce flow. + """One-call agentic-commerce flow. * x402 ``X-Payment`` header present → verify, settle via ``x402_server``, run ``on_settled`` hook, return 200 with the hook's body (or diff --git a/agentscore_commerce/discovery/agentscore_content.py b/agentscore_commerce/discovery/agentscore_content.py index d734ab3..68f74a8 100644 --- a/agentscore_commerce/discovery/agentscore_content.py +++ b/agentscore_commerce/discovery/agentscore_content.py @@ -101,7 +101,7 @@ def build_agentscore_onboarding_steps( compatible_fragment = ", ".join(hint for rail, hint in compatible_hints if rail in accepted_rails) compatible_clients_clause = ( - f"the rails table also lists per-rail `compatible_clients` — {compatible_fragment}. " + f"the rails table also lists per-rail `compatible_clients`: {compatible_fragment}. " if compatible_fragment else "" ) @@ -114,7 +114,7 @@ def build_agentscore_onboarding_steps( ) bootstrap_step = ( "First-run only: bootstrap wallet + Passport. Run `agentscore-pay agent-guide --json` " - "for the canonical cold-start path — it walks `agentscore-pay init` " + "for the canonical cold-start path; it walks `agentscore-pay init` " "(creates keystore + per-chain wallet), `agentscore-pay passport login` " f"(one-time KYC{'; required for this merchant' if requires_kyc else ''}; " "the human completes a verify URL once and pay caches the operator_token), " @@ -123,7 +123,7 @@ def build_agentscore_onboarding_steps( ) stripe_fallback_step = ( "If your only payment method is a Stripe / Link card (no crypto), install `@stripe/link-cli` " - "instead of agentscore-pay and use it on the SPT rail. Identity gating still applies — the " + "instead of agentscore-pay and use it on the SPT rail. Identity gating still applies: the " "merchant's 403 with `verify_url` lets you bootstrap a Passport even with no crypto wallet involved." ) returning_user_step = ( diff --git a/agentscore_commerce/discovery/llms_txt.py b/agentscore_commerce/discovery/llms_txt.py index 7ad0153..f586265 100644 --- a/agentscore_commerce/discovery/llms_txt.py +++ b/agentscore_commerce/discovery/llms_txt.py @@ -28,7 +28,7 @@ def llms_txt_identity_section( aip_bullet = "" if aip: aip_bullet = ( - "\n- **`Agent-Identity: ` + RFC 9421 signature** — present an Agent Identity Token (AIP) " + "\n- **`Agent-Identity: ` + RFC 9421 signature**: present an Agent Identity Token (AIP) " "from a trusted issuer (AgentScore is always trusted). Short-lived and bound to your key; sign " "the request (`Signature-Input` + `Signature` over `@method @authority @path agent-identity`, " "tag `agent-identity`) to prove possession. No long-lived token on the wire. Mint one with " @@ -50,13 +50,13 @@ def llms_txt_identity_section( compliance_note = f"\n\nCompliance: {', '.join(parts)}." return ( "## Identity\n\n" - "AgentScore identity is reusable across every AgentScore-gated merchant — one KYC, " + "AgentScore identity is reusable across every AgentScore-gated merchant: one KYC, " "no re-verification per site. Pick a header:\n\n" - "- **`X-Wallet-Address: 0x...` or base58** — works on signing rails (Tempo, x402, " + "- **`X-Wallet-Address: 0x...` or base58**: works on signing rails (Tempo, x402, " "Solana MPP). The wallet you claim must sign the payment.\n" - "- **`X-Operator-Token: opc_...`** — works on every rail, including Stripe SPT. " + "- **`X-Operator-Token: opc_...`**: works on every rail, including Stripe SPT. " f"Reusable across AgentScore merchants until expiry.{aip_bullet}\n" - "- **Neither** — you get a 403 with `verify_url`. Complete the session flow once and " + "- **Neither**: you get a 403 with `verify_url`. Complete the session flow once and " f"reuse the resulting `opc_...` everywhere.{compliance_note}" ) @@ -103,22 +103,22 @@ def _llms_txt_payment_section_compact(*, rails: list[str], app_url: str) -> str: rails_list = list(rails) if _has_rail_family(rails_list, "tempo-"): lines.append( - "- **Tempo USDC via MPP** — " + "- **Tempo USDC via MPP**: " f"`tempo request -X POST -H \"X-Operator-Token: opc_...\" --json '{{...}}' --max-spend N {app_url}`" ) if _has_rail_family(rails_list, "x402-base-"): lines.append( - f"- **x402 USDC on Base** (EIP-3009) — `agentscore-pay pay POST {app_url} --chain base " + f"- **x402 USDC on Base** (EIP-3009): `agentscore-pay pay POST {app_url} --chain base " "-H \"X-Operator-Token: opc_...\" -d '{...}'`" ) if _has_rail_family(rails_list, "mpp-solana-"): lines.append( - f"- **USDC on Solana** — `agentscore-pay pay POST {app_url} --chain solana " + f"- **USDC on Solana**: `agentscore-pay pay POST {app_url} --chain solana " "-H \"X-Operator-Token: opc_...\" -d '{...}'`" ) if "stripe-spt" in rails_list: lines.append( - "- **Stripe Shared Payment Token** — agent mints SPT (own Stripe account scoped to networkId, " + "- **Stripe Shared Payment Token**: agent mints SPT (own Stripe account scoped to networkId, " "OR `link-cli spend-request create --credential-type shared_payment_token --network-id " " ...`)" ) diff --git a/agentscore_commerce/discovery/openapi.py b/agentscore_commerce/discovery/openapi.py index 02e0e8e..f498cd9 100644 --- a/agentscore_commerce/discovery/openapi.py +++ b/agentscore_commerce/discovery/openapi.py @@ -233,7 +233,7 @@ def agentscore_denial_schemas() -> dict[str, Any]: "token_expired → deliver_verify_url_and_poll, invalid_credential → " "switch_token_or_restart_session, wallet_signer_mismatch → resign_or_switch_to_operator_token, " "wallet_auth_requires_wallet_signing → switch_to_operator_token, wallet_not_trusted → " - "contact_support — UNFIXABLE compliance only (sanctions/age/jurisdiction_restricted); " + "contact_support, for UNFIXABLE compliance only (sanctions/age/jurisdiction_restricted); " "fixable reasons re-route to identity_verification_required, payment_required → contact_merchant)." ), }, @@ -255,7 +255,7 @@ def agentscore_denial_schemas() -> dict[str, Any]: "description": ( "Present for missing_identity / identity_verification_required / token_expired " "denials. Agent shares this with the user to complete KYC or claim a wallet. " - "Not present on wallet_not_trusted (UNFIXABLE compliance — re-verification " + "Not present on wallet_not_trusted (UNFIXABLE compliance, so re-verification " "won't change the outcome)." ), }, diff --git a/agentscore_commerce/discovery/skill_md.py b/agentscore_commerce/discovery/skill_md.py index d8bc7cc..82f85aa 100644 --- a/agentscore_commerce/discovery/skill_md.py +++ b/agentscore_commerce/discovery/skill_md.py @@ -124,7 +124,7 @@ class _SkillCtx: "USDC (SPL). Use `agentscore-pay pay --chain solana`; MPP credential goes in `Authorization: Payment`." ), "stripe": ( - "Card via Link wallet. Use `@stripe/link-cli` — `agentscore-pay` emits the " + "Card via Link wallet. Use `@stripe/link-cli`; `agentscore-pay` emits the " "handoff hint when this rail is picked." ), } @@ -141,7 +141,7 @@ def _validate(input: _SkillCtx) -> None: raise ValueError(f"build_skill_md: name must be 1-{_NAME_MAX} characters (got {len(n) if n else 0})") if not _NAME_RE.match(n): raise ValueError( - f'build_skill_md: name "{n}" is invalid — must be lowercase alphanumeric and hyphens, ' + f'build_skill_md: name "{n}" is invalid: must be lowercase alphanumeric and hyphens, ' "no leading/trailing/consecutive hyphens (agentskills.io spec)" ) if not input.description: @@ -232,7 +232,7 @@ def _payment_section(input: _SkillCtx) -> str: clients[r] = defaults.get(r, []) rows = ["| Rail | Notes | Compatible clients |", "|---|---|---|"] for r in input.accepted_rails: - client_list = ", ".join(clients.get(r, [])) or "—" + client_list = ", ".join(clients.get(r, [])) or "none" rows.append(f"| **{_RAIL_LABELS[r]}** | {_RAIL_NOTES[r]} | {client_list} |") intro = ( "Each gated route returns a 402 with `WWW-Authenticate` + `PAYMENT-REQUIRED` " @@ -276,12 +276,12 @@ def _identity_section(input: _SkillCtx) -> str: "\n\nThis merchant accepts AIP Agent Identity Tokens. If you hold an AIT from a trusted issuer " "(AgentScore is always trusted), present the JWT in an `Agent-Identity` header plus an RFC 9421 " "HTTP Message Signature (`Signature-Input` + `Signature` over `@method @authority @path agent-identity`, " - "tag `agent-identity`) signed with the token-bound cnf key — it satisfies identity in one round trip, " + "tag `agent-identity`) signed with the token-bound cnf key, satisfying identity in one round trip, " "no separate AgentScore credential needed. `agentscore-pay pay --identity aip` does this automatically." ) denial_note = ( - "Denial bodies carry an `agent_instructions` block describing the recovery action " - "— read the `action` field and follow it. See the identity-bootstrap skill for the " + "Denial bodies carry an `agent_instructions` block describing the recovery action. " + "Read the `action` field and follow it. See the identity-bootstrap skill for the " "canonical denial-code → action table." ) return "\n".join( @@ -375,7 +375,7 @@ def build_skill_md( support_links: list[SkillMdLink] | None = None, refresh_footer: bool = True, ) -> str: - """Render an agentskills.io-compatible ``skill.md`` for an agent-commerce merchant. + """Render an agentskills.io-compatible ``skill.md`` for an agentic-commerce merchant. Output is YAML frontmatter (``name`` / ``description`` / optional ``license`` / ``compatibility`` / ``allowed-tools`` / ``metadata``) followed by markdown sections diff --git a/pyproject.toml b/pyproject.toml index a0f121b..72847dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "agentscore-commerce" -version = "2.7.1" +version = "2.7.2" description = "Agentic commerce SDK for Python: identity middleware (FastAPI, Flask, Django, AIOHTTP, Sanic, ASGI) + payment helpers + 402 builders + discovery + Stripe multichain. The full merchant-side toolkit for AgentScore-powered agentic commerce." readme = "README.md" license = "MIT" diff --git a/tests/test_skill_md.py b/tests/test_skill_md.py index a95b0e5..c0f25a0 100644 --- a/tests/test_skill_md.py +++ b/tests/test_skill_md.py @@ -253,12 +253,16 @@ def test_drops_overrides_for_rails_not_in_accepted(self) -> None: assert "rogue-cli" not in out assert "Stripe Shared Payment Token" not in out - def test_renders_em_dash_when_clients_empty(self) -> None: + def test_renders_none_when_clients_empty(self) -> None: + # "none" rather than an em-dash: this table is served on every store's + # skill.md, and the org rule bars em-dashes on external surfaces. Kept + # identical to the Node SDK, which renders the same table. cfg = _base() cfg["accepted_rails"] = ["x402_base"] cfg["compatible_clients"] = {"x402_base": []} out = build_skill_md(**cfg) - assert re.search("x402 on Base.+\\| —", out) + assert re.search(r"x402 on Base.+\| none", out) + assert "—" not in out class TestIdentitySection: diff --git a/uv.lock b/uv.lock index c2294b1..987938c 100644 --- a/uv.lock +++ b/uv.lock @@ -13,7 +13,7 @@ prerelease-mode = "disallow" [[package]] name = "agentscore-commerce" -version = "2.7.1" +version = "2.7.2" source = { editable = "." } dependencies = [ { name = "agentscore-py" },