agproxy 0.1.1__py3-none-any.whl
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,40 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agproxy
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: CLI for agproxy.shveik.dev (one of the shveik.dev agent products; same code as ag-cli)
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://agproxy.shveik.dev
|
|
7
|
+
Project-URL: Documentation, https://agproxy.shveik.dev/agents.md
|
|
8
|
+
Project-URL: shveik.dev, https://shveik.dev
|
|
9
|
+
Project-URL: agdata, https://agdata.shveik.dev
|
|
10
|
+
Project-URL: agproxy, https://agproxy.shveik.dev
|
|
11
|
+
Project-URL: agvps, https://agvps.shveik.dev
|
|
12
|
+
Project-URL: agmail, https://agmail.shveik.dev
|
|
13
|
+
Project-URL: agpay, https://agpay.shveik.dev
|
|
14
|
+
Keywords: ai-agents,x402,mpp,cli,shveik
|
|
15
|
+
Requires-Python: >=3.8
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# agproxy
|
|
19
|
+
|
|
20
|
+
CLI for https://agproxy.shveik.dev (the same code as `ag-cli`, which covers all five products).
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
agproxy endpoints
|
|
24
|
+
agproxy docs
|
|
25
|
+
agproxy quote <endpoint> '{...}'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Commands: endpoints, docs, pricing, openapi, health, quote, call. It never holds keys and never signs a payment.
|
|
29
|
+
|
|
30
|
+
## The shveik.dev agent products
|
|
31
|
+
|
|
32
|
+
Pay-per-call services for AI agents, paid in stablecoins with x402 or MPP, no accounts or API keys. Built by [shveik.dev](https://shveik.dev).
|
|
33
|
+
|
|
34
|
+
- [agdata](https://agdata.shveik.dev): fresh web data (any URL as Markdown, search, maps, reviews, jobs, social)
|
|
35
|
+
- [agproxy](https://agproxy.shveik.dev): residential proxy packs and a raw fetch
|
|
36
|
+
- [agvps](https://agvps.shveik.dev): Linux servers by the month
|
|
37
|
+
- [agmail](https://agmail.shveik.dev): receive-only agent inboxes
|
|
38
|
+
- [agpay](https://agpay.shveik.dev): custodial hold between two agents
|
|
39
|
+
|
|
40
|
+
Docs for agents: `/agents.md` on each site. Homepage: https://shveik.dev
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
agproxy_cli.py,sha256=WPFAPRTpb0pigOHXKCSEOVdXzS43684IuKS-Kx1Z-LI,5910
|
|
2
|
+
agproxy-0.1.1.dist-info/METADATA,sha256=Mb4thAiEk9SaM3yS8glkBeZIMxR0CCFeUkr_xUjwfyo,1600
|
|
3
|
+
agproxy-0.1.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
4
|
+
agproxy-0.1.1.dist-info/entry_points.txt,sha256=__bH9sHSP6bS-YMQLe896MPM_RlIet9U-dyFkAIZXv8,45
|
|
5
|
+
agproxy-0.1.1.dist-info/top_level.txt,sha256=NIaMc_ZLNomiyfdhyMW_8fLeAF31jKDSVZaL8hpJHW4,12
|
|
6
|
+
agproxy-0.1.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
agproxy_cli
|
agproxy_cli.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""ag: one small CLI for the shveik.dev agent products (agdata, agproxy, agvps, agmail, agpay).
|
|
2
|
+
|
|
3
|
+
Zero dependencies, Python 3.8+. It lists endpoints, shows quotes and sends calls you have signed
|
|
4
|
+
elsewhere; it never holds keys and never signs a payment.
|
|
5
|
+
"""
|
|
6
|
+
import base64
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import urllib.error
|
|
11
|
+
import urllib.request
|
|
12
|
+
|
|
13
|
+
__version__ = "0.1.1"
|
|
14
|
+
|
|
15
|
+
PRODUCTS = {"data": "agdata", "proxy": "agproxy", "vps": "agvps", "mail": "agmail", "pay": "agpay"}
|
|
16
|
+
DEFAULT_PRODUCT = "agproxy" # a product-named package (agvps, agpay, ...) sets this
|
|
17
|
+
|
|
18
|
+
USAGE = """ag <product> <command> [args]
|
|
19
|
+
|
|
20
|
+
products data proxy vps mail pay (or the full name: agdata, agproxy, ...)
|
|
21
|
+
commands endpoints list the API endpoints (JSON)
|
|
22
|
+
docs | pricing | openapi print the agent guide, the price list, the OpenAPI document
|
|
23
|
+
health liveness check
|
|
24
|
+
quote <endpoint> [json] send the call unpaid and print the 402 quote (exit 3)
|
|
25
|
+
call <endpoint> <json> --payment <signature>
|
|
26
|
+
send a call whose payment you signed elsewhere
|
|
27
|
+
(or set AG_PAYMENT); the CLI never holds keys
|
|
28
|
+
|
|
29
|
+
options --origin <url> override the service origin (or AG_ORIGIN)
|
|
30
|
+
|
|
31
|
+
example ag data endpoints
|
|
32
|
+
ag data quote unblock '{"url":"https://example.com"}'
|
|
33
|
+
exit 0 ok, 1 error, 2 usage, 3 payment required (quote printed)
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def fail(message, code=1):
|
|
38
|
+
sys.stderr.write(message + "\n")
|
|
39
|
+
sys.exit(code)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def take_option(args, name):
|
|
43
|
+
if name not in args:
|
|
44
|
+
return None
|
|
45
|
+
at = args.index(name)
|
|
46
|
+
if at + 1 >= len(args):
|
|
47
|
+
fail("%s needs a value" % name, 2)
|
|
48
|
+
value = args[at + 1]
|
|
49
|
+
del args[at:at + 2]
|
|
50
|
+
return value
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def show(text):
|
|
54
|
+
try:
|
|
55
|
+
sys.stdout.write(json.dumps(json.loads(text), indent=2) + "\n")
|
|
56
|
+
except ValueError:
|
|
57
|
+
sys.stdout.write(text if text.endswith("\n") else text + "\n")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def request(url, method="GET", body=None, headers=None):
|
|
61
|
+
"""Returns (status, headers, text); HTTP error statuses are returned, not raised."""
|
|
62
|
+
data = body.encode() if body is not None else None
|
|
63
|
+
req = urllib.request.Request(url, data=data, method=method, headers=headers or {})
|
|
64
|
+
try:
|
|
65
|
+
with urllib.request.urlopen(req, timeout=180) as response:
|
|
66
|
+
return response.status, response.headers, response.read().decode("utf-8", "replace")
|
|
67
|
+
except urllib.error.HTTPError as error:
|
|
68
|
+
return error.code, error.headers, error.read().decode("utf-8", "replace")
|
|
69
|
+
except urllib.error.URLError as error:
|
|
70
|
+
fail("request failed: %s" % error.reason)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get(origin, path):
|
|
74
|
+
status, _, text = request(origin + path)
|
|
75
|
+
if status >= 400:
|
|
76
|
+
fail("HTTP %d %s\n%s" % (status, path, text))
|
|
77
|
+
show(text)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def endpoint_path(name):
|
|
81
|
+
return name if name.startswith("/") else "/x402/" + name
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def paid_request(origin, endpoint, body, extra_headers):
|
|
85
|
+
headers = {"content-type": "application/json", "user-agent": "ag-cli/" + __version__}
|
|
86
|
+
headers.update(extra_headers)
|
|
87
|
+
return request(origin + endpoint_path(endpoint), "POST", body, headers)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def show_quote(status, headers, text):
|
|
91
|
+
sys.stdout.write("HTTP %d\n" % status)
|
|
92
|
+
required = headers.get("payment-required")
|
|
93
|
+
if required:
|
|
94
|
+
show(base64.b64decode(required).decode("utf-8", "replace"))
|
|
95
|
+
elif headers.get("www-authenticate"):
|
|
96
|
+
sys.stdout.write("WWW-Authenticate: %s\n" % headers.get("www-authenticate"))
|
|
97
|
+
else:
|
|
98
|
+
show(text)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def main(argv=None):
|
|
102
|
+
args = list(sys.argv[1:] if argv is None else argv)
|
|
103
|
+
origin_option = take_option(args, "--origin")
|
|
104
|
+
payment = take_option(args, "--payment") or os.environ.get("AG_PAYMENT")
|
|
105
|
+
full_names = list(PRODUCTS.values())
|
|
106
|
+
if DEFAULT_PRODUCT and (not args or (args[0] not in PRODUCTS and args[0] not in full_names and args[0] not in ("products", "help", "-h", "--help"))):
|
|
107
|
+
args.insert(0, DEFAULT_PRODUCT)
|
|
108
|
+
if not args or args[0] in ("help", "-h", "--help"):
|
|
109
|
+
sys.stdout.write(USAGE)
|
|
110
|
+
return
|
|
111
|
+
if args[0] == "products":
|
|
112
|
+
for short, full in PRODUCTS.items():
|
|
113
|
+
sys.stdout.write("%s\t%s\thttps://%s.shveik.dev\n" % (short, full, full))
|
|
114
|
+
return
|
|
115
|
+
product = PRODUCTS.get(args[0]) or (args[0] if args[0] in full_names else None)
|
|
116
|
+
if not product:
|
|
117
|
+
fail('unknown product "%s"\n\n%s' % (args[0], USAGE), 2)
|
|
118
|
+
origin = (origin_option or os.environ.get("AG_ORIGIN") or "https://%s.shveik.dev" % product).rstrip("/")
|
|
119
|
+
command = args[1] if len(args) > 1 else ""
|
|
120
|
+
rest = args[2:]
|
|
121
|
+
if command == "endpoints":
|
|
122
|
+
get(origin, "/v1")
|
|
123
|
+
elif command == "docs":
|
|
124
|
+
get(origin, "/agents.md")
|
|
125
|
+
elif command == "pricing":
|
|
126
|
+
get(origin, "/pricing.md")
|
|
127
|
+
elif command == "openapi":
|
|
128
|
+
get(origin, "/openapi.json")
|
|
129
|
+
elif command == "health":
|
|
130
|
+
get(origin, "/health")
|
|
131
|
+
elif command == "quote":
|
|
132
|
+
if not rest:
|
|
133
|
+
fail("quote needs an endpoint name or path", 2)
|
|
134
|
+
status, headers, text = paid_request(origin, rest[0], rest[1] if len(rest) > 1 else "{}", {})
|
|
135
|
+
show_quote(status, headers, text)
|
|
136
|
+
sys.exit(3 if status == 402 else 0 if status < 400 else 1)
|
|
137
|
+
elif command == "call":
|
|
138
|
+
if len(rest) < 2:
|
|
139
|
+
fail("call needs an endpoint and a JSON body", 2)
|
|
140
|
+
if not payment:
|
|
141
|
+
fail("a signed payment is required: --payment <signature> or AG_PAYMENT (sign the quote with your own wallet)", 2)
|
|
142
|
+
status, headers, text = paid_request(origin, rest[0], rest[1], {"PAYMENT-SIGNATURE": payment})
|
|
143
|
+
if status == 402:
|
|
144
|
+
show_quote(status, headers, text)
|
|
145
|
+
sys.exit(3)
|
|
146
|
+
show(text)
|
|
147
|
+
sys.exit(0 if status < 400 else 1)
|
|
148
|
+
else:
|
|
149
|
+
fail('unknown command "%s"\n\n%s' % (command, USAGE), 2)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
if __name__ == "__main__":
|
|
153
|
+
main()
|