ausca 0.0.2__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.
ausca-0.0.2/PKG-INFO ADDED
@@ -0,0 +1,96 @@
1
+ Metadata-Version: 2.4
2
+ Name: ausca
3
+ Version: 0.0.2
4
+ Summary: Client for Ausca metered agent infrastructure services: catalog-bound invocations paid per call under a hard USD cap, through pluggable payment authorities (x402 v2 today), with receipt-backed results.
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://ausca.com
7
+ Project-URL: Documentation, https://ausca.com/docs
8
+ Project-URL: Repository, https://github.com/auscahq/ausca-integrations
9
+ Project-URL: Issues, https://github.com/auscahq/ausca-integrations/issues
10
+ Keywords: ausca,x402,agents,payments,mcp
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: httpx>=0.28
14
+ Requires-Dist: x402[evm]>=2.21.0
15
+ Requires-Dist: eth-account>=0.13
16
+
17
+ # ausca
18
+
19
+ Client for [Ausca](https://ausca.com) metered agent infrastructure services:
20
+ document OCR, document analysis, media transcription, browser sessions, and
21
+ agent inboxes. Paid per call under a hard USD cap you set, with no account
22
+ or provider API keys, and a settlement receipt on every completed
23
+ invocation.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ pip install ausca
29
+ ```
30
+
31
+ ## Use
32
+
33
+ ```python
34
+ import os
35
+ from ausca import AuscaClient
36
+
37
+ client = AuscaClient.with_local_key(
38
+ private_key=os.environ["AUSCA_PRIVATE_KEY"], # pays USDC on Base
39
+ max_payment_usd=0.50, # hard per-call cap
40
+ )
41
+
42
+ outcome = client.invoke("browser.session", {"duration_seconds": 600})
43
+ print(outcome.result)
44
+ print(outcome.payment.transaction) # settlement proof
45
+ ```
46
+
47
+ `invoke` resolves the offer's immutable revision, schema digests, and
48
+ payable route from the live catalog, reads the exact payment requirement,
49
+ pays it if it fits the cap, and retries the same bytes with the same
50
+ idempotency key. Each call starts with a fresh key. For recovery after an
51
+ uncertain response, pass the same caller-owned `idempotency_key`; use a new
52
+ key for another intentional purchase, even when its input is identical.
53
+
54
+ A CLI ships with the package:
55
+
56
+ ```bash
57
+ ausca catalog
58
+ ausca price document.ocr
59
+ AUSCA_PRIVATE_KEY=0x... AUSCA_MAX_PAYMENT_USD=0.50 \
60
+ ausca invoke browser.session '{"duration_seconds":600}'
61
+ # Reuse this key only to recover that same intended purchase:
62
+ ausca invoke browser.session '{"duration_seconds":600}' \
63
+ --idempotency-key browser-attempt-20260903-0001
64
+ ```
65
+
66
+ ## Payment rails
67
+
68
+ Payment is a pluggable authority, never a client concern. The built-in
69
+ `LocalKeyAuthority` pays x402 v2 with a local signing key through the
70
+ official [`x402`](https://pypi.org/project/x402/) library, which enforces
71
+ the cap through its spend controls before anything is signed; other rails
72
+ implement the same small `PaymentAuthority` protocol.
73
+ `client.pay_request(url, ...)` runs one paid call against any resource the
74
+ configured authority can pay; nothing in it is vendor-specific.
75
+
76
+ ## Artifact-backed offers
77
+
78
+ Document and media offers take an immutable artifact commitment instead of
79
+ raw bytes. `ausca commit file.pdf` sends the bytes to Ausca's keyless
80
+ temporary ingress and returns the commitment the offer input carries; the
81
+ library equivalent is `client.commit(data, media_type)`. Repeating the same
82
+ bytes is idempotent. The active offer catalog sets the usable input limit.
83
+
84
+ Successful paid state includes `receipt_ref.public_url`, an immutable
85
+ hash-only proof of the Ausca service, public price, completion time, and
86
+ receipt digest. It contains no request or result bytes, content digests, or
87
+ access capabilities. Anyone holding the unguessable URL can read it.
88
+
89
+ The full agent contract lives at <https://ausca.com/SKILL.md>; discover the
90
+ active offers at <https://ausca.com/catalog.json>. The npm equivalent is
91
+ [`ausca`](https://www.npmjs.com/package/ausca), which adds a local MCP
92
+ server (`npx ausca mcp`).
93
+
94
+ ## License
95
+
96
+ MIT
ausca-0.0.2/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # ausca
2
+
3
+ Client for [Ausca](https://ausca.com) metered agent infrastructure services:
4
+ document OCR, document analysis, media transcription, browser sessions, and
5
+ agent inboxes. Paid per call under a hard USD cap you set, with no account
6
+ or provider API keys, and a settlement receipt on every completed
7
+ invocation.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pip install ausca
13
+ ```
14
+
15
+ ## Use
16
+
17
+ ```python
18
+ import os
19
+ from ausca import AuscaClient
20
+
21
+ client = AuscaClient.with_local_key(
22
+ private_key=os.environ["AUSCA_PRIVATE_KEY"], # pays USDC on Base
23
+ max_payment_usd=0.50, # hard per-call cap
24
+ )
25
+
26
+ outcome = client.invoke("browser.session", {"duration_seconds": 600})
27
+ print(outcome.result)
28
+ print(outcome.payment.transaction) # settlement proof
29
+ ```
30
+
31
+ `invoke` resolves the offer's immutable revision, schema digests, and
32
+ payable route from the live catalog, reads the exact payment requirement,
33
+ pays it if it fits the cap, and retries the same bytes with the same
34
+ idempotency key. Each call starts with a fresh key. For recovery after an
35
+ uncertain response, pass the same caller-owned `idempotency_key`; use a new
36
+ key for another intentional purchase, even when its input is identical.
37
+
38
+ A CLI ships with the package:
39
+
40
+ ```bash
41
+ ausca catalog
42
+ ausca price document.ocr
43
+ AUSCA_PRIVATE_KEY=0x... AUSCA_MAX_PAYMENT_USD=0.50 \
44
+ ausca invoke browser.session '{"duration_seconds":600}'
45
+ # Reuse this key only to recover that same intended purchase:
46
+ ausca invoke browser.session '{"duration_seconds":600}' \
47
+ --idempotency-key browser-attempt-20260903-0001
48
+ ```
49
+
50
+ ## Payment rails
51
+
52
+ Payment is a pluggable authority, never a client concern. The built-in
53
+ `LocalKeyAuthority` pays x402 v2 with a local signing key through the
54
+ official [`x402`](https://pypi.org/project/x402/) library, which enforces
55
+ the cap through its spend controls before anything is signed; other rails
56
+ implement the same small `PaymentAuthority` protocol.
57
+ `client.pay_request(url, ...)` runs one paid call against any resource the
58
+ configured authority can pay; nothing in it is vendor-specific.
59
+
60
+ ## Artifact-backed offers
61
+
62
+ Document and media offers take an immutable artifact commitment instead of
63
+ raw bytes. `ausca commit file.pdf` sends the bytes to Ausca's keyless
64
+ temporary ingress and returns the commitment the offer input carries; the
65
+ library equivalent is `client.commit(data, media_type)`. Repeating the same
66
+ bytes is idempotent. The active offer catalog sets the usable input limit.
67
+
68
+ Successful paid state includes `receipt_ref.public_url`, an immutable
69
+ hash-only proof of the Ausca service, public price, completion time, and
70
+ receipt digest. It contains no request or result bytes, content digests, or
71
+ access capabilities. Anyone holding the unguessable URL can read it.
72
+
73
+ The full agent contract lives at <https://ausca.com/SKILL.md>; discover the
74
+ active offers at <https://ausca.com/catalog.json>. The npm equivalent is
75
+ [`ausca`](https://www.npmjs.com/package/ausca), which adds a local MCP
76
+ server (`npx ausca mcp`).
77
+
78
+ ## License
79
+
80
+ MIT
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ausca"
7
+ version = "0.0.2"
8
+ description = "Client for Ausca metered agent infrastructure services: catalog-bound invocations paid per call under a hard USD cap, through pluggable payment authorities (x402 v2 today), with receipt-backed results."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.10"
12
+ keywords = ["ausca", "x402", "agents", "payments", "mcp"]
13
+ dependencies = [
14
+ "httpx>=0.28",
15
+ "x402[evm]>=2.21.0",
16
+ "eth-account>=0.13",
17
+ ]
18
+
19
+ [project.scripts]
20
+ ausca = "ausca.cli:main"
21
+
22
+ [project.urls]
23
+ Homepage = "https://ausca.com"
24
+ Documentation = "https://ausca.com/docs"
25
+ Repository = "https://github.com/auscahq/ausca-integrations"
26
+ Issues = "https://github.com/auscahq/ausca-integrations/issues"
27
+
28
+ [tool.setuptools.packages.find]
29
+ where = ["src"]
ausca-0.0.2/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,47 @@
1
+ """Ausca: metered agent infrastructure services, paid per call."""
2
+
3
+ from ausca.artifacts import (
4
+ MAX_ARTIFACT_BYTES,
5
+ ArtifactCommitment,
6
+ ArtifactError,
7
+ ArtifactStore,
8
+ AuscaArtifactStore,
9
+ )
10
+ from ausca.client import (
11
+ CATALOG_URL,
12
+ ORIGIN,
13
+ SKILL_URL,
14
+ AuscaClient,
15
+ AuscaError,
16
+ InvocationResult,
17
+ Offer,
18
+ Price,
19
+ PriceOption,
20
+ )
21
+ from ausca.payment import (
22
+ InertAuthority,
23
+ LocalKeyAuthority,
24
+ PaymentAuthority,
25
+ PaymentReceipt,
26
+ )
27
+
28
+ __all__ = [
29
+ "ArtifactCommitment",
30
+ "ArtifactError",
31
+ "ArtifactStore",
32
+ "AuscaArtifactStore",
33
+ "AuscaClient",
34
+ "AuscaError",
35
+ "CATALOG_URL",
36
+ "InertAuthority",
37
+ "InvocationResult",
38
+ "LocalKeyAuthority",
39
+ "ORIGIN",
40
+ "Offer",
41
+ "PaymentAuthority",
42
+ "PaymentReceipt",
43
+ "Price",
44
+ "PriceOption",
45
+ "MAX_ARTIFACT_BYTES",
46
+ "SKILL_URL",
47
+ ]
@@ -0,0 +1,97 @@
1
+ """Artifact commitments for document and media offers.
2
+
3
+ Artifact-backed offers take an immutable input commitment instead of raw
4
+ bytes. This port turns bytes into that commitment; Ausca's keyless ingress is
5
+ the default implementation and custom stores remain an explicit seam.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import base64
11
+ import hashlib
12
+ from dataclasses import dataclass
13
+ from typing import Protocol
14
+
15
+ import httpx
16
+
17
+ AUSCA_ORIGIN = "https://ausca.com"
18
+ MAX_ARTIFACT_BYTES = 25 * 1024 * 1024
19
+
20
+
21
+ class ArtifactError(Exception):
22
+ """A refused or failed artifact operation."""
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class ArtifactCommitment:
27
+ """The immutable input commitment an artifact-backed offer requires."""
28
+
29
+ artifact_ref: str
30
+ content_digest: str
31
+ media_type: str
32
+
33
+
34
+ class ArtifactStore(Protocol):
35
+ """Stores bytes and returns the commitment the invocation input carries."""
36
+
37
+ def commit(self, data: bytes, media_type: str) -> ArtifactCommitment: ...
38
+
39
+
40
+ class AuscaArtifactStore:
41
+ """Commits bytes through Ausca's keyless temporary artifact ingress."""
42
+
43
+ def __init__(
44
+ self,
45
+ *,
46
+ origin: str = AUSCA_ORIGIN,
47
+ http: httpx.Client | None = None,
48
+ ) -> None:
49
+ self._origin = origin.rstrip("/")
50
+ self._http = http or httpx.Client(timeout=120)
51
+
52
+ def commit(self, data: bytes, media_type: str) -> ArtifactCommitment:
53
+ if not data or len(data) > MAX_ARTIFACT_BYTES:
54
+ raise ArtifactError(f"artifact must contain 1 to {MAX_ARTIFACT_BYTES} bytes")
55
+ if not media_type or media_type.strip() != media_type or len(media_type) > 200:
56
+ raise ArtifactError("artifact media type is invalid")
57
+ digest_hex = hashlib.sha256(data).hexdigest()
58
+ content_digest = f"sha256:{digest_hex}"
59
+ request_hex = hashlib.sha256(
60
+ f"{content_digest}\n{media_type}".encode()
61
+ ).hexdigest()
62
+ response = self._http.post(
63
+ f"{self._origin}/v1/artifacts",
64
+ json={
65
+ "data_base64": base64.b64encode(data).decode(),
66
+ "content_digest": content_digest,
67
+ "media_type": media_type,
68
+ "idempotency_key": f"ausca-artifact-{request_hex[:32]}",
69
+ },
70
+ )
71
+ if response.status_code >= 400:
72
+ raise ArtifactError(
73
+ f"artifact ingress answered {response.status_code}: {response.text[:512]}"
74
+ )
75
+ try:
76
+ result = response.json()
77
+ except ValueError as error:
78
+ raise ArtifactError("artifact ingress answered non-JSON") from error
79
+ if set(result) != {"status", "artifact"} or result["status"] != "stored":
80
+ raise ArtifactError("artifact ingress returned malformed evidence")
81
+ evidence = result["artifact"]
82
+ if not isinstance(evidence, dict) or set(evidence) != {
83
+ "artifact_ref", "content_digest", "created_at", "media_type", "size_bytes"
84
+ }:
85
+ raise ArtifactError("artifact ingress returned malformed evidence")
86
+ artifact_ref = f"runx:artifact:{content_digest}"
87
+ if (
88
+ evidence["artifact_ref"] != artifact_ref
89
+ or evidence["content_digest"] != content_digest
90
+ or evidence["media_type"] != media_type
91
+ or evidence["size_bytes"] != len(data)
92
+ or not isinstance(evidence["created_at"], str)
93
+ ):
94
+ raise ArtifactError("artifact ingress returned mismatched evidence")
95
+ return ArtifactCommitment(
96
+ artifact_ref=artifact_ref, content_digest=content_digest, media_type=media_type
97
+ )
@@ -0,0 +1,181 @@
1
+ """The front-door verbs, mirroring the npm ``ausca`` bin.
2
+
3
+ Configuration is environment only: a signing key and a mandatory per-call
4
+ USD cap for paying verbs, nothing else. Artifact commits are keyless. Output
5
+ is JSON on stdout.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import dataclasses
11
+ import json
12
+ import os
13
+ import sys
14
+ from pathlib import Path
15
+ from typing import Any, Mapping, TextIO
16
+
17
+ from ausca.client import AuscaClient, ORIGIN
18
+
19
+ USAGE = """ausca: metered agent infrastructure services, paid per call
20
+
21
+ ausca catalog active offers, prices, routes
22
+ ausca price <offer-id> published price policy
23
+ ausca invoke <offer-id> [input] [--idempotency-key <key>]
24
+ paid invocation; input inline, @file, or stdin
25
+ ausca commit <file> artifact commitment for document and media offers
26
+
27
+ Environment: AUSCA_PRIVATE_KEY and AUSCA_MAX_PAYMENT_USD pay invocations;
28
+ AUSCA_NETWORK overrides the payment network; AUSCA_ORIGIN overrides the service.
29
+ The MCP server ships in the npm package: npx ausca mcp."""
30
+
31
+ MEDIA_TYPES = {
32
+ "pdf": "application/pdf",
33
+ "png": "image/png",
34
+ "jpg": "image/jpeg",
35
+ "jpeg": "image/jpeg",
36
+ "gif": "image/gif",
37
+ "webp": "image/webp",
38
+ "tif": "image/tiff",
39
+ "tiff": "image/tiff",
40
+ "txt": "text/plain",
41
+ "md": "text/markdown",
42
+ "html": "text/html",
43
+ "csv": "text/csv",
44
+ "json": "application/json",
45
+ "mp3": "audio/mpeg",
46
+ "wav": "audio/wav",
47
+ "m4a": "audio/mp4",
48
+ "flac": "audio/flac",
49
+ "ogg": "audio/ogg",
50
+ "mp4": "video/mp4",
51
+ "mov": "video/quicktime",
52
+ "webm": "video/webm",
53
+ "mkv": "video/x-matroska",
54
+ }
55
+
56
+
57
+ def media_type_for(name: str) -> str:
58
+ return MEDIA_TYPES.get(name.lower().rsplit(".", 1)[-1], "application/octet-stream")
59
+
60
+
61
+ def _client(env: Mapping[str, str], *, require_payment: bool = False) -> AuscaClient:
62
+ origin = env.get("AUSCA_ORIGIN", ORIGIN)
63
+ key = env.get("AUSCA_PRIVATE_KEY")
64
+ if not key:
65
+ if require_payment:
66
+ raise SystemExit(
67
+ "paid invocations need AUSCA_PRIVATE_KEY and AUSCA_MAX_PAYMENT_USD"
68
+ " in the environment"
69
+ )
70
+ return AuscaClient(origin=origin)
71
+ try:
72
+ cap = float(env.get("AUSCA_MAX_PAYMENT_USD", ""))
73
+ except ValueError:
74
+ cap = 0.0
75
+ if cap <= 0:
76
+ raise SystemExit(
77
+ "AUSCA_MAX_PAYMENT_USD must be a positive USD amount when"
78
+ " AUSCA_PRIVATE_KEY is set; refusing to guess a spend limit"
79
+ )
80
+ return AuscaClient.with_local_key(
81
+ private_key=key,
82
+ max_payment_usd=cap,
83
+ network=env.get("AUSCA_NETWORK", "eip155:8453"),
84
+ origin=origin,
85
+ )
86
+
87
+
88
+ def _json(value: Any) -> str:
89
+ if dataclasses.is_dataclass(value) and not isinstance(value, type):
90
+ value = dataclasses.asdict(value)
91
+ return json.dumps(value, indent=2, default=lambda item: dataclasses.asdict(item))
92
+
93
+
94
+ def _resolve_input(argument: str | None, stdin: TextIO) -> dict[str, Any]:
95
+ if argument is None or argument == "-":
96
+ text = stdin.read()
97
+ elif argument.startswith("@"):
98
+ text = Path(argument[1:]).read_text()
99
+ else:
100
+ text = argument
101
+ try:
102
+ return json.loads(text)
103
+ except ValueError as error:
104
+ raise SystemExit("invocation input must be valid JSON") from error
105
+
106
+
107
+ def _invocation_args(argv: list[str]) -> tuple[str, str | None, str | None]:
108
+ if not argv:
109
+ raise SystemExit(
110
+ "usage: ausca invoke <offer-id> [input] [--idempotency-key <key>]"
111
+ )
112
+ offer_id = argv[0]
113
+ input_argument: str | None = None
114
+ idempotency_key: str | None = None
115
+ index = 1
116
+ while index < len(argv):
117
+ argument = argv[index]
118
+ if argument == "--idempotency-key":
119
+ if idempotency_key is not None or index + 1 >= len(argv):
120
+ raise SystemExit("--idempotency-key requires one value")
121
+ idempotency_key = argv[index + 1]
122
+ index += 2
123
+ elif input_argument is None:
124
+ input_argument = argument
125
+ index += 1
126
+ else:
127
+ raise SystemExit(f"unexpected invoke argument {argument}")
128
+ return offer_id, input_argument, idempotency_key
129
+
130
+
131
+ def run(
132
+ argv: list[str],
133
+ env: Mapping[str, str],
134
+ stdout: TextIO,
135
+ stdin: TextIO,
136
+ ) -> int:
137
+ verb = argv[0] if argv else None
138
+ if verb in (None, "help", "--help"):
139
+ print(USAGE, file=stdout)
140
+ return 1 if verb is None else 0
141
+ if verb == "catalog":
142
+ offers = _client(env).catalog().get("offers", [])
143
+ listing = [
144
+ {key: offer.get(key) for key in ("offer_id", "title", "route", "price", "revision")}
145
+ for offer in offers
146
+ ]
147
+ print(_json(listing), file=stdout)
148
+ return 0
149
+ if verb == "price":
150
+ if len(argv) < 2:
151
+ raise SystemExit("usage: ausca price <offer-id>")
152
+ print(_json(_client(env).price(argv[1])), file=stdout)
153
+ return 0
154
+ if verb == "invoke":
155
+ offer_id, input_argument, idempotency_key = _invocation_args(argv[1:])
156
+ client = _client(env, require_payment=True)
157
+ outcome = client.invoke(
158
+ offer_id,
159
+ _resolve_input(input_argument, stdin),
160
+ idempotency_key=idempotency_key,
161
+ )
162
+ print(_json(outcome), file=stdout)
163
+ return 0
164
+ if verb == "commit":
165
+ if len(argv) < 2:
166
+ raise SystemExit("usage: ausca commit <file>")
167
+ path = Path(argv[1])
168
+ commitment = _client(env).commit(path.read_bytes(), media_type_for(path.name))
169
+ print(_json(commitment), file=stdout)
170
+ return 0
171
+ raise SystemExit(f"unknown verb {verb}; run ausca help")
172
+
173
+
174
+ def main() -> None:
175
+ try:
176
+ sys.exit(run(sys.argv[1:], os.environ, sys.stdout, sys.stdin))
177
+ except SystemExit:
178
+ raise
179
+ except Exception as error: # noqa: BLE001 - the CLI boundary reports and exits
180
+ print(str(error), file=sys.stderr)
181
+ sys.exit(1)