burnwatch 0.1.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.
- burnwatch-0.1.0/PKG-INFO +8 -0
- burnwatch-0.1.0/README.md +107 -0
- burnwatch-0.1.0/burnwatch/__init__.py +19 -0
- burnwatch-0.1.0/burnwatch/client.py +130 -0
- burnwatch-0.1.0/burnwatch/x402.py +163 -0
- burnwatch-0.1.0/burnwatch.egg-info/PKG-INFO +8 -0
- burnwatch-0.1.0/burnwatch.egg-info/SOURCES.txt +9 -0
- burnwatch-0.1.0/burnwatch.egg-info/dependency_links.txt +1 -0
- burnwatch-0.1.0/burnwatch.egg-info/top_level.txt +1 -0
- burnwatch-0.1.0/pyproject.toml +18 -0
- burnwatch-0.1.0/setup.cfg +4 -0
burnwatch-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: burnwatch
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Burnwatch SDK — mirror your AI agent's payments to Burnwatch for spend monitoring.
|
|
5
|
+
Project-URL: Homepage, https://getburnwatch.southforgeai.com
|
|
6
|
+
Project-URL: Repository, https://github.com/tsouth89/burnwatch-app
|
|
7
|
+
Project-URL: Documentation, https://github.com/tsouth89/burnwatch-app/blob/main/sdk/README.md
|
|
8
|
+
Requires-Python: >=3.9
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Burnwatch SDK (Python)
|
|
2
|
+
|
|
3
|
+
A thin, **stdlib-only** shim that mirrors your AI agent's payments to Burnwatch for spend
|
|
4
|
+
monitoring. Observe-only: it sends payment *metadata* (amount, recipient, resource, rail), never
|
|
5
|
+
your keys or funds, and it's **fail-open** — if Burnwatch is unreachable, your agent keeps paying.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install burnwatch
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or directly from GitHub (before PyPI release):
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install "burnwatch @ git+https://github.com/tsouth89/burnwatch-app.git#subdirectory=sdk"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Endpoint
|
|
20
|
+
|
|
21
|
+
Use your Burnwatch deployment base URL (no `/ingest` suffix — the client adds `/ingest/payments`):
|
|
22
|
+
|
|
23
|
+
- Production: `https://burnwatch.southforgeai.com`
|
|
24
|
+
- Local dev: `http://localhost:8010`
|
|
25
|
+
|
|
26
|
+
Create an ingest token in the dashboard (**Settings → Collector setup**) or via
|
|
27
|
+
`python -m scripts.make_token` on the backend.
|
|
28
|
+
|
|
29
|
+
## Basic use
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from burnwatch import BurnwatchClient
|
|
33
|
+
|
|
34
|
+
with BurnwatchClient(endpoint="https://burnwatch.southforgeai.com", token="bw_your_token") as bw:
|
|
35
|
+
bw.record(
|
|
36
|
+
agent_ref="agent_7f3c", # stable agent id
|
|
37
|
+
agent_name="research-bot", # optional; used when auto-provisioning
|
|
38
|
+
amount=0.002,
|
|
39
|
+
recipient="api.weather.dev", # payee / endpoint / address
|
|
40
|
+
resource="GET /forecast",
|
|
41
|
+
rail="x402",
|
|
42
|
+
currency="USDC",
|
|
43
|
+
context={"tx_hash": "0xabc...", "chain_id": "eip155:8453"}, # optional, public only
|
|
44
|
+
)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Calls are **buffered and flushed in the background** (every `flush_interval` seconds, or when
|
|
48
|
+
`max_batch` is reached), so `record()` never blocks your agent. Set `enabled=False` to disable
|
|
49
|
+
mirroring in tests without changing call sites.
|
|
50
|
+
|
|
51
|
+
## Context metadata
|
|
52
|
+
|
|
53
|
+
Pass optional `context` for dedup and richer detection. **Never** include private keys, mnemonics,
|
|
54
|
+
seeds, or raw signatures — the API returns **422** if forbidden keys are present.
|
|
55
|
+
|
|
56
|
+
| Key | Purpose |
|
|
57
|
+
|-----|---------|
|
|
58
|
+
| `tx_hash`, `payment_id`, `transaction_id` | Dedup (preferred over content hash) |
|
|
59
|
+
| `asset` | Token symbol when different from `currency` |
|
|
60
|
+
| `chain_id` | e.g. `eip155:8453` |
|
|
61
|
+
| `facilitator` | `coinbase`, self-hosted, etc. |
|
|
62
|
+
| `wallet_provider` | `cdp`, `wdk`, `agentkit`, etc. |
|
|
63
|
+
| `network` | `mainnet` or `testnet` |
|
|
64
|
+
|
|
65
|
+
## x402 wrapper
|
|
66
|
+
|
|
67
|
+
Use `X402Monitor` (or `PaymentMirror` metadata) to wrap your existing x402 HTTP client:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from burnwatch import BurnwatchClient, X402Monitor
|
|
71
|
+
|
|
72
|
+
with BurnwatchClient(endpoint="https://burnwatch.southforgeai.com", token="bw_...") as bw:
|
|
73
|
+
mon = X402Monitor(bw, agent_ref="agent_7f3c", agent_name="research-bot")
|
|
74
|
+
resp = mon.paid_get(x402_client.get, "https://api.weather.dev/forecast", max_amount=0.01)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Or mirror manually after your own client returns:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
resp = x402_client.get(url, max_amount=price)
|
|
81
|
+
mon.after_payment(resp, recipient=url, resource="GET /forecast")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
See `examples/x402_wrapper.py` for a runnable demo with a fake x402 client.
|
|
85
|
+
|
|
86
|
+
## Manual seam
|
|
87
|
+
|
|
88
|
+
If you prefer an explicit `record()` at the call site:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
def paid_get(url, price, *, bw, agent_ref):
|
|
92
|
+
resp = x402_client.get(url, max_amount=price) # your real x402 call
|
|
93
|
+
bw.record(agent_ref=agent_ref, amount=resp.amount_paid, recipient=url, resource="GET")
|
|
94
|
+
return resp
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Examples
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
BURNWATCH_ENDPOINT=http://localhost:8010 BURNWATCH_TOKEN=bw_... python examples/quickstart.py
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## What Burnwatch detects
|
|
104
|
+
|
|
105
|
+
The backend runs 10 transparent rules after warm-up (velocity, unknown payees, drain bursts,
|
|
106
|
+
off-pattern destinations, amount spikes, off-hours spend, new rails, concentration, counterparty
|
|
107
|
+
velocity, asset anomalies). See the root [`README.md`](../README.md) for the full table.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Burnwatch SDK — observe-only spend mirroring for AI agents.
|
|
2
|
+
|
|
3
|
+
Wrap your agent's payment client, call ``record()`` after each payment, and Burnwatch watches the
|
|
4
|
+
metadata for drains, overspends, and unknown counterparties. It never holds your keys or funds and
|
|
5
|
+
never sits in the payment path: mirroring is async, batched, and fail-open — if Burnwatch is
|
|
6
|
+
unreachable, your agent keeps paying as normal.
|
|
7
|
+
|
|
8
|
+
from burnwatch import BurnwatchClient
|
|
9
|
+
|
|
10
|
+
bw = BurnwatchClient(endpoint="https://burnwatch.southforgeai.com", token="bw_...")
|
|
11
|
+
# ... after your agent makes an x402 payment ...
|
|
12
|
+
bw.record(agent_ref="agent_7f3c", amount=0.002, recipient="api.weather.dev", resource="GET /forecast")
|
|
13
|
+
bw.close() # or use `with BurnwatchClient(...) as bw:`
|
|
14
|
+
"""
|
|
15
|
+
from burnwatch.client import BurnwatchClient
|
|
16
|
+
from burnwatch.x402 import PaymentMirror, X402Monitor
|
|
17
|
+
|
|
18
|
+
__all__ = ["BurnwatchClient", "X402Monitor", "PaymentMirror"]
|
|
19
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""The Burnwatch client: buffer payment metadata and mirror it outbound, async and fail-open."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
import threading
|
|
7
|
+
import urllib.request
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
log = logging.getLogger("burnwatch")
|
|
12
|
+
|
|
13
|
+
__sdk_version__ = "0.1.0"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BurnwatchClient:
|
|
17
|
+
"""Mirrors agent payments to the Burnwatch backend.
|
|
18
|
+
|
|
19
|
+
Design rules (CLAUDE.md §3): outbound-only, metadata-only, never in the money path. Every
|
|
20
|
+
network operation swallows its errors — a monitoring failure must never break the agent.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
endpoint: str,
|
|
26
|
+
token: str,
|
|
27
|
+
*,
|
|
28
|
+
flush_interval: float = 2.0,
|
|
29
|
+
max_batch: int = 100,
|
|
30
|
+
timeout: float = 3.0,
|
|
31
|
+
enabled: bool = True,
|
|
32
|
+
) -> None:
|
|
33
|
+
self._url = endpoint.rstrip("/") + "/ingest/payments"
|
|
34
|
+
self._token = token
|
|
35
|
+
self._flush_interval = flush_interval
|
|
36
|
+
self._max_batch = max_batch
|
|
37
|
+
self._timeout = timeout
|
|
38
|
+
self._enabled = enabled
|
|
39
|
+
|
|
40
|
+
self._buf: list[dict[str, Any]] = []
|
|
41
|
+
self._lock = threading.Lock()
|
|
42
|
+
self._stop = threading.Event()
|
|
43
|
+
self._thread: threading.Thread | None = None
|
|
44
|
+
if enabled:
|
|
45
|
+
self._thread = threading.Thread(target=self._loop, name="burnwatch-flush", daemon=True)
|
|
46
|
+
self._thread.start()
|
|
47
|
+
|
|
48
|
+
# -- public API -----------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
def record(
|
|
51
|
+
self,
|
|
52
|
+
*,
|
|
53
|
+
agent_ref: str,
|
|
54
|
+
amount: float,
|
|
55
|
+
recipient: str,
|
|
56
|
+
resource: str | None = None,
|
|
57
|
+
currency: str = "USDC",
|
|
58
|
+
rail: str = "x402",
|
|
59
|
+
status: str = "paid",
|
|
60
|
+
ts: datetime | None = None,
|
|
61
|
+
agent_name: str | None = None,
|
|
62
|
+
context: dict[str, Any] | None = None,
|
|
63
|
+
) -> None:
|
|
64
|
+
"""Queue one payment for mirroring. Non-blocking; never raises."""
|
|
65
|
+
if not self._enabled:
|
|
66
|
+
return
|
|
67
|
+
event = {
|
|
68
|
+
"agent_ref": agent_ref,
|
|
69
|
+
"amount": amount,
|
|
70
|
+
"recipient": recipient,
|
|
71
|
+
"resource": resource,
|
|
72
|
+
"currency": currency,
|
|
73
|
+
"rail": rail,
|
|
74
|
+
"status": status,
|
|
75
|
+
"ts": (ts or datetime.now(timezone.utc)).isoformat(),
|
|
76
|
+
}
|
|
77
|
+
if agent_name:
|
|
78
|
+
event["agent_name"] = agent_name
|
|
79
|
+
if context:
|
|
80
|
+
event["context"] = context
|
|
81
|
+
with self._lock:
|
|
82
|
+
self._buf.append(event)
|
|
83
|
+
full = len(self._buf) >= self._max_batch
|
|
84
|
+
if full:
|
|
85
|
+
self.flush()
|
|
86
|
+
|
|
87
|
+
def flush(self) -> None:
|
|
88
|
+
"""Send any buffered events now. Fail-open: network/HTTP errors are logged, not raised."""
|
|
89
|
+
with self._lock:
|
|
90
|
+
if not self._buf:
|
|
91
|
+
return
|
|
92
|
+
batch, self._buf = self._buf, []
|
|
93
|
+
try:
|
|
94
|
+
self._post({"events": batch, "sdk_version": __sdk_version__})
|
|
95
|
+
except Exception as exc: # noqa: BLE001 — monitoring must never break the caller
|
|
96
|
+
log.debug("burnwatch flush failed (%s events dropped): %s", len(batch), exc)
|
|
97
|
+
|
|
98
|
+
def close(self) -> None:
|
|
99
|
+
"""Stop the background flusher and send anything still buffered."""
|
|
100
|
+
self._stop.set()
|
|
101
|
+
if self._thread is not None:
|
|
102
|
+
self._thread.join(timeout=self._timeout + 1)
|
|
103
|
+
self.flush()
|
|
104
|
+
|
|
105
|
+
def __enter__(self) -> "BurnwatchClient":
|
|
106
|
+
return self
|
|
107
|
+
|
|
108
|
+
def __exit__(self, *exc: Any) -> None:
|
|
109
|
+
self.close()
|
|
110
|
+
|
|
111
|
+
# -- internals ------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
def _loop(self) -> None:
|
|
114
|
+
while not self._stop.wait(self._flush_interval):
|
|
115
|
+
self.flush()
|
|
116
|
+
|
|
117
|
+
def _post(self, payload: dict[str, Any]) -> None:
|
|
118
|
+
req = urllib.request.Request(
|
|
119
|
+
self._url,
|
|
120
|
+
data=json.dumps(payload).encode("utf-8"),
|
|
121
|
+
headers={"Authorization": f"Bearer {self._token}", "Content-Type": "application/json"},
|
|
122
|
+
method="POST",
|
|
123
|
+
)
|
|
124
|
+
with urllib.request.urlopen(req, timeout=self._timeout) as resp:
|
|
125
|
+
if resp.status >= 400:
|
|
126
|
+
log.debug("burnwatch ingest returned %s", resp.status)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# silence "no handler" warnings while staying quiet by default
|
|
130
|
+
log.addHandler(logging.NullHandler())
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""x402 payment wrapper — one-line integration over any paid HTTP client.
|
|
2
|
+
|
|
3
|
+
Burnwatch does not ship an x402 transport; this module wraps *your* client and mirrors metadata
|
|
4
|
+
after each successful payment. Fail-open: recording errors never propagate to the caller.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Any, Callable
|
|
10
|
+
|
|
11
|
+
from burnwatch.client import BurnwatchClient
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _extract_amount(result: Any, attr: str, fallback: float | None) -> float:
|
|
15
|
+
if isinstance(result, dict):
|
|
16
|
+
val = result.get(attr, result.get("amount"))
|
|
17
|
+
else:
|
|
18
|
+
val = getattr(result, attr, None)
|
|
19
|
+
if val is None:
|
|
20
|
+
val = getattr(result, "amount", None)
|
|
21
|
+
if val is None:
|
|
22
|
+
if fallback is None:
|
|
23
|
+
return 0.0
|
|
24
|
+
return float(fallback)
|
|
25
|
+
return float(val)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class PaymentMirror:
|
|
30
|
+
"""Normalized payment metadata passed to Burnwatch after an x402 call."""
|
|
31
|
+
|
|
32
|
+
amount: float
|
|
33
|
+
recipient: str
|
|
34
|
+
resource: str | None = None
|
|
35
|
+
currency: str = "USDC"
|
|
36
|
+
rail: str = "x402"
|
|
37
|
+
status: str = "paid"
|
|
38
|
+
context: dict[str, Any] | None = None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class X402Monitor:
|
|
42
|
+
"""Wrap paid x402 calls and auto-mirror metadata to Burnwatch.
|
|
43
|
+
|
|
44
|
+
Example::
|
|
45
|
+
|
|
46
|
+
bw = BurnwatchClient(endpoint="https://burnwatch.example.com", token="bw_...")
|
|
47
|
+
mon = X402Monitor(bw, agent_ref="agent_7f3c", agent_name="research-bot")
|
|
48
|
+
|
|
49
|
+
# pass your real x402 client's get function
|
|
50
|
+
resp = mon.paid_get(x402_client.get, "https://api.weather.dev/forecast", max_amount=0.01)
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
client: BurnwatchClient,
|
|
56
|
+
*,
|
|
57
|
+
agent_ref: str,
|
|
58
|
+
agent_name: str | None = None,
|
|
59
|
+
) -> None:
|
|
60
|
+
self._client = client
|
|
61
|
+
self._agent_ref = agent_ref
|
|
62
|
+
self._agent_name = agent_name
|
|
63
|
+
|
|
64
|
+
def mirror(self, payment: PaymentMirror) -> None:
|
|
65
|
+
"""Record a completed payment explicitly."""
|
|
66
|
+
self._client.record(
|
|
67
|
+
agent_ref=self._agent_ref,
|
|
68
|
+
agent_name=self._agent_name,
|
|
69
|
+
amount=payment.amount,
|
|
70
|
+
recipient=payment.recipient,
|
|
71
|
+
resource=payment.resource,
|
|
72
|
+
currency=payment.currency,
|
|
73
|
+
rail=payment.rail,
|
|
74
|
+
status=payment.status,
|
|
75
|
+
context=payment.context,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def after_payment(
|
|
79
|
+
self,
|
|
80
|
+
result: Any,
|
|
81
|
+
*,
|
|
82
|
+
recipient: str,
|
|
83
|
+
resource: str | None = None,
|
|
84
|
+
amount_attr: str = "amount_paid",
|
|
85
|
+
amount_fallback: float | None = None,
|
|
86
|
+
context: dict[str, Any] | None = None,
|
|
87
|
+
) -> Any:
|
|
88
|
+
"""Mirror an already-completed payment; returns ``result`` unchanged."""
|
|
89
|
+
self.mirror(
|
|
90
|
+
PaymentMirror(
|
|
91
|
+
amount=_extract_amount(result, amount_attr, amount_fallback),
|
|
92
|
+
recipient=recipient,
|
|
93
|
+
resource=resource,
|
|
94
|
+
context=context,
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
return result
|
|
98
|
+
|
|
99
|
+
def paid_call(
|
|
100
|
+
self,
|
|
101
|
+
pay_fn: Callable[..., Any],
|
|
102
|
+
recipient: str,
|
|
103
|
+
*,
|
|
104
|
+
resource: str | None = None,
|
|
105
|
+
amount_attr: str = "amount_paid",
|
|
106
|
+
amount_fallback: float | None = None,
|
|
107
|
+
context: dict[str, Any] | None = None,
|
|
108
|
+
**pay_kwargs: Any,
|
|
109
|
+
) -> Any:
|
|
110
|
+
"""Call ``pay_fn(**pay_kwargs)``, mirror metadata, return the result."""
|
|
111
|
+
result = pay_fn(**pay_kwargs)
|
|
112
|
+
return self.after_payment(
|
|
113
|
+
result,
|
|
114
|
+
recipient=recipient,
|
|
115
|
+
resource=resource,
|
|
116
|
+
amount_attr=amount_attr,
|
|
117
|
+
amount_fallback=amount_fallback,
|
|
118
|
+
context=context,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def paid_get(
|
|
122
|
+
self,
|
|
123
|
+
get_fn: Callable[..., Any],
|
|
124
|
+
url: str,
|
|
125
|
+
*,
|
|
126
|
+
max_amount: float | None = None,
|
|
127
|
+
resource: str | None = None,
|
|
128
|
+
amount_attr: str = "amount_paid",
|
|
129
|
+
**get_kwargs: Any,
|
|
130
|
+
) -> Any:
|
|
131
|
+
"""Common x402 GET pattern: ``get_fn(url, max_amount=...)`` then mirror."""
|
|
132
|
+
kwargs = dict(get_kwargs)
|
|
133
|
+
if max_amount is not None and "max_amount" not in kwargs:
|
|
134
|
+
kwargs["max_amount"] = max_amount
|
|
135
|
+
return self.paid_call(
|
|
136
|
+
lambda: get_fn(url, **kwargs),
|
|
137
|
+
url,
|
|
138
|
+
resource=resource or f"GET {url}",
|
|
139
|
+
amount_attr=amount_attr,
|
|
140
|
+
amount_fallback=max_amount,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def paid_post(
|
|
144
|
+
self,
|
|
145
|
+
post_fn: Callable[..., Any],
|
|
146
|
+
url: str,
|
|
147
|
+
*,
|
|
148
|
+
max_amount: float | None = None,
|
|
149
|
+
resource: str | None = None,
|
|
150
|
+
amount_attr: str = "amount_paid",
|
|
151
|
+
**post_kwargs: Any,
|
|
152
|
+
) -> Any:
|
|
153
|
+
"""Common x402 POST pattern: ``post_fn(url, max_amount=...)`` then mirror."""
|
|
154
|
+
kwargs = dict(post_kwargs)
|
|
155
|
+
if max_amount is not None and "max_amount" not in kwargs:
|
|
156
|
+
kwargs["max_amount"] = max_amount
|
|
157
|
+
return self.paid_call(
|
|
158
|
+
lambda: post_fn(url, **kwargs),
|
|
159
|
+
url,
|
|
160
|
+
resource=resource or f"POST {url}",
|
|
161
|
+
amount_attr=amount_attr,
|
|
162
|
+
amount_fallback=max_amount,
|
|
163
|
+
)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: burnwatch
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Burnwatch SDK — mirror your AI agent's payments to Burnwatch for spend monitoring.
|
|
5
|
+
Project-URL: Homepage, https://getburnwatch.southforgeai.com
|
|
6
|
+
Project-URL: Repository, https://github.com/tsouth89/burnwatch-app
|
|
7
|
+
Project-URL: Documentation, https://github.com/tsouth89/burnwatch-app/blob/main/sdk/README.md
|
|
8
|
+
Requires-Python: >=3.9
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
burnwatch
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "burnwatch"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Burnwatch SDK — mirror your AI agent's payments to Burnwatch for spend monitoring."
|
|
5
|
+
requires-python = ">=3.9"
|
|
6
|
+
dependencies = [] # stdlib-only on purpose: a monitor must never add weight or break your agent
|
|
7
|
+
|
|
8
|
+
[build-system]
|
|
9
|
+
requires = ["setuptools>=70"]
|
|
10
|
+
build-backend = "setuptools.build_meta"
|
|
11
|
+
|
|
12
|
+
[project.urls]
|
|
13
|
+
Homepage = "https://getburnwatch.southforgeai.com"
|
|
14
|
+
Repository = "https://github.com/tsouth89/burnwatch-app"
|
|
15
|
+
Documentation = "https://github.com/tsouth89/burnwatch-app/blob/main/sdk/README.md"
|
|
16
|
+
|
|
17
|
+
[tool.setuptools.packages.find]
|
|
18
|
+
include = ["burnwatch*"]
|