angel-agent-guard 0.2.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.
@@ -0,0 +1,3 @@
1
+ node_modules/
2
+ *.tgz
3
+ .env
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Oroboros Design (Daouda Ndiaye)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,155 @@
1
+ Metadata-Version: 2.5
2
+ Name: angel-agent-guard
3
+ Version: 0.2.0
4
+ Summary: Guard an AI agent before it acts: pre-purchase check for any shop, checkout or payment link, paid API, x402 endpoint or token, and a prompt-injection check for untrusted content. Zero dependencies; browser-use, LangChain/LangGraph and CrewAI tools included.
5
+ Project-URL: Homepage, https://github.com/daoudandiaye98-alt/angel-agent-guard/tree/main/python
6
+ Project-URL: Repository, https://github.com/daoudandiaye98-alt/angel-agent-guard
7
+ Project-URL: Documentation, https://angel.oroboros-design.com/docs/prompt-injection-defense/
8
+ Project-URL: Issues, https://github.com/daoudandiaye98-alt/angel-agent-guard/issues
9
+ Author: Oroboros Design
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: agent-security,agentic-commerce,ai-agent,base,browser-use,checkout,crewai,langchain,langgraph,mcp,phishing,prompt-injection,shopping-agent,usdc,x402
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Classifier: Topic :: Security
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.9
22
+ Provides-Extra: browser-use
23
+ Requires-Dist: browser-use>=1.0; extra == 'browser-use'
24
+ Provides-Extra: crewai
25
+ Requires-Dist: crewai>=0.80; extra == 'crewai'
26
+ Provides-Extra: langchain
27
+ Requires-Dist: langchain-core>=0.3; extra == 'langchain'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # angel-agent-guard (Python)
31
+
32
+ Two checks an AI agent should run before it acts:
33
+
34
+ - **`verify_before_pay(url_or_token)`** before **any purchase or paid call**. That covers a shop or product page, a checkout or payment link (Stripe, Shopify, PayPal, SaaS checkout), an unknown paid API, an x402 endpoint, and a token swap or approval. It checks:
35
+ - TLS and domain age
36
+ - look-alike domains, and redirects to payment pages on other domains
37
+ - which payment provider takes the money
38
+ - card fields and plain-http forms in the page
39
+ - text in the page aimed at shopping agents
40
+ - for x402: the payment offer itself
41
+ - for an ERC-20: the contract's owner powers
42
+ - **`guard_untrusted(text_or_url)`** before the model acts on a web page, email, document, RAG chunk or tool result written by someone else. A local pass runs first (invisible Unicode, tag smuggling, exfiltration images), then a hosted scan in 17 languages for text aimed at the model.
43
+ - **`quick_scan(text)`** is the local pass on its own. It is fully offline.
44
+
45
+ There are no dependencies (standard library only), and it works on Python 3.9+. It ships ready-made tools for **browser-use**, **LangChain / LangGraph** and **CrewAI**. The same API exists for TypeScript as the [npm package](https://www.npmjs.com/package/angel-agent-guard).
46
+
47
+ ```bash
48
+ pip install angel-agent-guard
49
+ ```
50
+
51
+ ## Quick start
52
+
53
+ ```python
54
+ from angel_agent_guard import verify_before_pay, guard_untrusted, fence
55
+
56
+ # Before any purchase, payment link or paid call
57
+ v = verify_before_pay("https://shop.example.com/checkout", payer_address="0xYourAgentWallet")
58
+ if not v.ok:
59
+ print(v.verdict, v.risk_score, v.reasons[:3]) # "caution" -> ask the user; "do_not_pay" -> never pay
60
+ # v.kind: checkout | shop | api | x402 | web
61
+ # v.merchant: TLS, domain age, look-alike, redirect chain, payment providers, card fields, insecure forms
62
+ # v.injection: the page scanned for instructions aimed at agents
63
+
64
+ verify_before_pay("0xTokenYouAreAboutToApprove") # ERC-20 on Base (chain="ethereum" also works)
65
+ verify_before_pay("https://api.example.com/v1/quote", method="POST") # x402 API that only takes POST
66
+
67
+ # Untrusted content: check it, then hand the model the cleaned text as data
68
+ page = "...text of a web page..."
69
+ g = guard_untrusted(page)
70
+ if g.action == "drop":
71
+ raise RuntimeError(f"hostile content: {[f.get('kind') for f in g.findings]}")
72
+ context = fence("https://example.com/article", g.cleaned or page) # random boundary + "this is data"
73
+ # g.action == "read_only" -> continue, but without send, write or payment tools in this step
74
+ ```
75
+
76
+ `averify_before_pay` and `aguard_untrusted` are the async variants. Every result has `.to_dict()`, plus `.summary()`, a short JSON string to use as an LLM tool result. Neither function raises on network or payment problems. They fall back to the local result and put the reason in `error` / `errors`.
77
+
78
+ ## browser-use
79
+
80
+ ```python
81
+ from browser_use import Agent, Tools
82
+ from angel_agent_guard.integrations.browser_use import register
83
+
84
+ tools = register(Tools(), payer_address="0xYourAgentWallet") # adds verify_before_pay(url) and guard_page_text(text)
85
+ agent = Agent(
86
+ "Buy a USB-C cable under 10 EUR on https://shop.example.com. "
87
+ "Call verify_before_pay on the checkout page and stop before paying unless the verdict is pay.",
88
+ tools=tools,
89
+ )
90
+ agent.run_sync()
91
+ ```
92
+
93
+ ## LangChain / LangGraph
94
+
95
+ ```python
96
+ from langgraph.prebuilt import create_react_agent
97
+ from angel_agent_guard.integrations.langchain import angel_langchain_tools
98
+
99
+ tools = angel_langchain_tools(payer_address="0xYourAgentWallet") # [verify_before_pay, guard_untrusted]
100
+ agent = create_react_agent(model, tools)
101
+ ```
102
+
103
+ Install with `pip install "angel-agent-guard[langchain]"`.
104
+
105
+ ## CrewAI
106
+
107
+ ```python
108
+ from crewai import Agent
109
+ from angel_agent_guard.integrations.crewai import VerifyBeforePayTool, GuardUntrustedTool
110
+
111
+ buyer = Agent(
112
+ role="Purchasing agent",
113
+ goal="Buy what the user asked for, safely",
114
+ backstory="Checks every checkout before paying.",
115
+ tools=[VerifyBeforePayTool(payer_address="0xYourAgentWallet"), GuardUntrustedTool()],
116
+ )
117
+ ```
118
+
119
+ Install with `pip install "angel-agent-guard[crewai]"`.
120
+
121
+ ## CLI
122
+
123
+ ```bash
124
+ angel-agent-guard-py verify https://shop.example.com/checkout
125
+ angel-agent-guard-py verify --token 0x... --chain base
126
+ angel-agent-guard-py scan "text to check" # or a https URL, or - for stdin
127
+ angel-agent-guard-py quick "text" # offline
128
+ ```
129
+
130
+ Exit codes: `0` clean / pay, `1` caution / unchecked, `2` hostile / do_not_pay, `3` usage or error.
131
+
132
+ ## Pricing and paying
133
+
134
+ The local checks are free. The hosted checks run on [Angel](https://angel.oroboros-design.com/docs/prompt-injection-defense/). Each agent gets 3 free calls, then pays per call in USDC on Base via x402:
135
+
136
+ | Check | Price per call |
137
+ |---|---|
138
+ | pre-purchase check | 0.25 USDC |
139
+ | input guard | 0.99 USDC |
140
+ | token risk | 0.99 USDC |
141
+
142
+ Pass `payer_address` (or set `ANGEL_PAYER_ADDRESS`) so that your wallet gets its own 3 free calls. Without it, the free calls are shared by IP. Once the free calls are used up, the result carries `payment`, which holds the x402 offer, the price and a card checkout link a human can open.
143
+
144
+ To pay automatically, pass a paying HTTP client as `client=`. This can be any object with `.request()`, for example a paying client from the [x402 Python SDK](https://pypi.org/project/x402/) built on httpx or requests. The library checks an x402 offer locally (`check_offer`: official USDC, network, amount cap, same host) but never signs anything itself.
145
+
146
+ ## Limits and data
147
+
148
+ - The local pass removes invisible characters and flags the riskiest markers. It does not judge meaning. If it finds nothing, it reports `unchecked`, never `clean`.
149
+ - The hosted checks are deterministic: patterns, TLS, DNS, RDAP and on-chain reads, with no model. That makes them fast and repeatable, but a new attack, or a well-made fraud shop on an old domain, can pass. Keep the other layers:
150
+ - read external content as data
151
+ - give the step that reads content no dangerous tools
152
+ - require an approval the model cannot give itself before money moves
153
+ - For a hosted check, the text or URL is sent to `angel.oroboros-design.com`. Use `remote=False` for content that must not leave your machine.
154
+
155
+ Disclosure: the hosted checks are a paid service run by the author (Oroboros Design). The library is MIT-licensed.
@@ -0,0 +1,126 @@
1
+ # angel-agent-guard (Python)
2
+
3
+ Two checks an AI agent should run before it acts:
4
+
5
+ - **`verify_before_pay(url_or_token)`** before **any purchase or paid call**. That covers a shop or product page, a checkout or payment link (Stripe, Shopify, PayPal, SaaS checkout), an unknown paid API, an x402 endpoint, and a token swap or approval. It checks:
6
+ - TLS and domain age
7
+ - look-alike domains, and redirects to payment pages on other domains
8
+ - which payment provider takes the money
9
+ - card fields and plain-http forms in the page
10
+ - text in the page aimed at shopping agents
11
+ - for x402: the payment offer itself
12
+ - for an ERC-20: the contract's owner powers
13
+ - **`guard_untrusted(text_or_url)`** before the model acts on a web page, email, document, RAG chunk or tool result written by someone else. A local pass runs first (invisible Unicode, tag smuggling, exfiltration images), then a hosted scan in 17 languages for text aimed at the model.
14
+ - **`quick_scan(text)`** is the local pass on its own. It is fully offline.
15
+
16
+ There are no dependencies (standard library only), and it works on Python 3.9+. It ships ready-made tools for **browser-use**, **LangChain / LangGraph** and **CrewAI**. The same API exists for TypeScript as the [npm package](https://www.npmjs.com/package/angel-agent-guard).
17
+
18
+ ```bash
19
+ pip install angel-agent-guard
20
+ ```
21
+
22
+ ## Quick start
23
+
24
+ ```python
25
+ from angel_agent_guard import verify_before_pay, guard_untrusted, fence
26
+
27
+ # Before any purchase, payment link or paid call
28
+ v = verify_before_pay("https://shop.example.com/checkout", payer_address="0xYourAgentWallet")
29
+ if not v.ok:
30
+ print(v.verdict, v.risk_score, v.reasons[:3]) # "caution" -> ask the user; "do_not_pay" -> never pay
31
+ # v.kind: checkout | shop | api | x402 | web
32
+ # v.merchant: TLS, domain age, look-alike, redirect chain, payment providers, card fields, insecure forms
33
+ # v.injection: the page scanned for instructions aimed at agents
34
+
35
+ verify_before_pay("0xTokenYouAreAboutToApprove") # ERC-20 on Base (chain="ethereum" also works)
36
+ verify_before_pay("https://api.example.com/v1/quote", method="POST") # x402 API that only takes POST
37
+
38
+ # Untrusted content: check it, then hand the model the cleaned text as data
39
+ page = "...text of a web page..."
40
+ g = guard_untrusted(page)
41
+ if g.action == "drop":
42
+ raise RuntimeError(f"hostile content: {[f.get('kind') for f in g.findings]}")
43
+ context = fence("https://example.com/article", g.cleaned or page) # random boundary + "this is data"
44
+ # g.action == "read_only" -> continue, but without send, write or payment tools in this step
45
+ ```
46
+
47
+ `averify_before_pay` and `aguard_untrusted` are the async variants. Every result has `.to_dict()`, plus `.summary()`, a short JSON string to use as an LLM tool result. Neither function raises on network or payment problems. They fall back to the local result and put the reason in `error` / `errors`.
48
+
49
+ ## browser-use
50
+
51
+ ```python
52
+ from browser_use import Agent, Tools
53
+ from angel_agent_guard.integrations.browser_use import register
54
+
55
+ tools = register(Tools(), payer_address="0xYourAgentWallet") # adds verify_before_pay(url) and guard_page_text(text)
56
+ agent = Agent(
57
+ "Buy a USB-C cable under 10 EUR on https://shop.example.com. "
58
+ "Call verify_before_pay on the checkout page and stop before paying unless the verdict is pay.",
59
+ tools=tools,
60
+ )
61
+ agent.run_sync()
62
+ ```
63
+
64
+ ## LangChain / LangGraph
65
+
66
+ ```python
67
+ from langgraph.prebuilt import create_react_agent
68
+ from angel_agent_guard.integrations.langchain import angel_langchain_tools
69
+
70
+ tools = angel_langchain_tools(payer_address="0xYourAgentWallet") # [verify_before_pay, guard_untrusted]
71
+ agent = create_react_agent(model, tools)
72
+ ```
73
+
74
+ Install with `pip install "angel-agent-guard[langchain]"`.
75
+
76
+ ## CrewAI
77
+
78
+ ```python
79
+ from crewai import Agent
80
+ from angel_agent_guard.integrations.crewai import VerifyBeforePayTool, GuardUntrustedTool
81
+
82
+ buyer = Agent(
83
+ role="Purchasing agent",
84
+ goal="Buy what the user asked for, safely",
85
+ backstory="Checks every checkout before paying.",
86
+ tools=[VerifyBeforePayTool(payer_address="0xYourAgentWallet"), GuardUntrustedTool()],
87
+ )
88
+ ```
89
+
90
+ Install with `pip install "angel-agent-guard[crewai]"`.
91
+
92
+ ## CLI
93
+
94
+ ```bash
95
+ angel-agent-guard-py verify https://shop.example.com/checkout
96
+ angel-agent-guard-py verify --token 0x... --chain base
97
+ angel-agent-guard-py scan "text to check" # or a https URL, or - for stdin
98
+ angel-agent-guard-py quick "text" # offline
99
+ ```
100
+
101
+ Exit codes: `0` clean / pay, `1` caution / unchecked, `2` hostile / do_not_pay, `3` usage or error.
102
+
103
+ ## Pricing and paying
104
+
105
+ The local checks are free. The hosted checks run on [Angel](https://angel.oroboros-design.com/docs/prompt-injection-defense/). Each agent gets 3 free calls, then pays per call in USDC on Base via x402:
106
+
107
+ | Check | Price per call |
108
+ |---|---|
109
+ | pre-purchase check | 0.25 USDC |
110
+ | input guard | 0.99 USDC |
111
+ | token risk | 0.99 USDC |
112
+
113
+ Pass `payer_address` (or set `ANGEL_PAYER_ADDRESS`) so that your wallet gets its own 3 free calls. Without it, the free calls are shared by IP. Once the free calls are used up, the result carries `payment`, which holds the x402 offer, the price and a card checkout link a human can open.
114
+
115
+ To pay automatically, pass a paying HTTP client as `client=`. This can be any object with `.request()`, for example a paying client from the [x402 Python SDK](https://pypi.org/project/x402/) built on httpx or requests. The library checks an x402 offer locally (`check_offer`: official USDC, network, amount cap, same host) but never signs anything itself.
116
+
117
+ ## Limits and data
118
+
119
+ - The local pass removes invisible characters and flags the riskiest markers. It does not judge meaning. If it finds nothing, it reports `unchecked`, never `clean`.
120
+ - The hosted checks are deterministic: patterns, TLS, DNS, RDAP and on-chain reads, with no model. That makes them fast and repeatable, but a new attack, or a well-made fraud shop on an old domain, can pass. Keep the other layers:
121
+ - read external content as data
122
+ - give the step that reads content no dangerous tools
123
+ - require an approval the model cannot give itself before money moves
124
+ - For a hosted check, the text or URL is sent to `angel.oroboros-design.com`. Use `remote=False` for content that must not leave your machine.
125
+
126
+ Disclosure: the hosted checks are a paid service run by the author (Oroboros Design). The library is MIT-licensed.
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.24"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "angel-agent-guard"
7
+ version = "0.2.0"
8
+ description = "Guard an AI agent before it acts: pre-purchase check for any shop, checkout or payment link, paid API, x402 endpoint or token, and a prompt-injection check for untrusted content. Zero dependencies; browser-use, LangChain/LangGraph and CrewAI tools included."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.9"
13
+ authors = [{ name = "Oroboros Design" }]
14
+ keywords = ["ai-agent", "agent-security", "prompt-injection", "shopping-agent", "agentic-commerce", "checkout", "phishing", "x402", "usdc", "base", "browser-use", "langchain", "langgraph", "crewai", "mcp"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3 :: Only",
21
+ "Topic :: Security",
22
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
23
+ "Typing :: Typed",
24
+ ]
25
+ dependencies = []
26
+
27
+ [project.optional-dependencies]
28
+ browser-use = ["browser-use>=1.0"]
29
+ langchain = ["langchain-core>=0.3"]
30
+ crewai = ["crewai>=0.80"]
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/daoudandiaye98-alt/angel-agent-guard/tree/main/python"
34
+ Repository = "https://github.com/daoudandiaye98-alt/angel-agent-guard"
35
+ Documentation = "https://angel.oroboros-design.com/docs/prompt-injection-defense/"
36
+ Issues = "https://github.com/daoudandiaye98-alt/angel-agent-guard/issues"
37
+
38
+ [project.scripts]
39
+ angel-agent-guard-py = "angel_agent_guard.__main__:run"
40
+
41
+ [tool.hatch.build.targets.wheel]
42
+ packages = ["src/angel_agent_guard"]
43
+
44
+ [tool.hatch.build.targets.sdist]
45
+ include = ["src/angel_agent_guard", "tests", "README.md", "LICENSE", "pyproject.toml"]
@@ -0,0 +1,30 @@
1
+ """angel-agent-guard: two checks an AI agent should run before it acts.
2
+
3
+ - guard_untrusted(text_or_url): prompt-injection check for content written by someone else
4
+ - verify_before_pay(url_or_token): pre-purchase check for any shop, checkout or payment link, paid API, x402 endpoint or token
5
+ - quick_scan(text): offline pass (invisible Unicode, tag smuggling, exfiltration images), zero dependencies
6
+
7
+ Integrations: angel_agent_guard.integrations.browser_use, .langchain, .crewai
8
+ """
9
+
10
+ from ._guard import (
11
+ ANGEL_BASE,
12
+ PATHS,
13
+ VERSION,
14
+ GuardResult,
15
+ PaymentNeeded,
16
+ VerifyResult,
17
+ aguard_untrusted,
18
+ averify_before_pay,
19
+ guard_untrusted,
20
+ verify_before_pay,
21
+ )
22
+ from ._local import USDC, LocalFinding, OfferCheck, QuickScanResult, check_offer, decode_offer, fence, local_verdict, looks_like_url, quick_scan
23
+
24
+ __version__ = VERSION
25
+ __all__ = [
26
+ "VERSION", "ANGEL_BASE", "PATHS", "USDC",
27
+ "guard_untrusted", "verify_before_pay", "aguard_untrusted", "averify_before_pay",
28
+ "quick_scan", "local_verdict", "fence", "looks_like_url", "check_offer", "decode_offer",
29
+ "GuardResult", "VerifyResult", "PaymentNeeded", "QuickScanResult", "LocalFinding", "OfferCheck",
30
+ ]
@@ -0,0 +1,63 @@
1
+ """CLI: python -m angel_agent_guard (or angel-agent-guard-py) scan|verify|quick ...
2
+
3
+ Exit codes: 0 clean/pay, 1 caution/unchecked, 2 hostile/do_not_pay, 3 usage or error.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ import sys
10
+ from typing import List
11
+
12
+ from . import guard_untrusted, quick_scan, verify_before_pay
13
+ from ._local import local_verdict
14
+
15
+ USAGE = """usage:
16
+ angel-agent-guard-py scan "<text>" | <https-url> | - prompt-injection check (local + hosted)
17
+ angel-agent-guard-py verify <https-url> [--post] pre-purchase check (shop, checkout, payment link, API, x402)
18
+ angel-agent-guard-py verify --token 0x... [--chain base|ethereum]
19
+ angel-agent-guard-py quick "<text>" | - offline only"""
20
+
21
+
22
+ def main(argv: List[str]) -> int:
23
+ if not argv or argv[0] in ("-h", "--help"):
24
+ print(USAGE)
25
+ return 3
26
+ cmd, rest = argv[0], argv[1:]
27
+ flag = lambda name: rest[rest.index(name) + 1] if name in rest and rest.index(name) + 1 < len(rest) else None # noqa: E731
28
+ positional = [a for i, a in enumerate(rest) if not a.startswith("--") and (i == 0 or rest[i - 1] not in ("--token", "--chain"))]
29
+ if cmd in ("scan", "quick"):
30
+ text = sys.stdin.read() if positional[:1] == ["-"] else " ".join(positional)
31
+ if not text:
32
+ print(USAGE, file=sys.stderr)
33
+ return 3
34
+ if cmd == "quick":
35
+ q = quick_scan(text)
36
+ print(json.dumps(q.to_dict(), ensure_ascii=False, indent=2))
37
+ v = local_verdict(q)
38
+ return 2 if v == "hostile" else 1 if v == "caution" else 0
39
+ g = guard_untrusted(text)
40
+ print(json.dumps(g.to_dict(), ensure_ascii=False, indent=2))
41
+ return 0 if g.verdict == "clean" else 2 if g.verdict == "hostile" else 1
42
+ if cmd == "verify":
43
+ url, token = (positional[0] if positional else None), flag("--token")
44
+ if not url and not token:
45
+ print(USAGE, file=sys.stderr)
46
+ return 3
47
+ r = verify_before_pay(url=url, token=token, chain=flag("--chain") or "base", method="POST" if "--post" in rest else "GET")
48
+ print(json.dumps(r.to_dict(), ensure_ascii=False, indent=2, default=str))
49
+ return 0 if r.verdict == "pay" else 2 if r.verdict == "do_not_pay" else 1
50
+ print(USAGE, file=sys.stderr)
51
+ return 3
52
+
53
+
54
+ def run() -> None:
55
+ try:
56
+ sys.exit(main(sys.argv[1:]))
57
+ except Exception as e: # pragma: no cover
58
+ print(str(e), file=sys.stderr)
59
+ sys.exit(3)
60
+
61
+
62
+ if __name__ == "__main__":
63
+ run()