hunch-agent 0.1.0__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,42 @@
1
+ """hunch_agent — Python client for the Hunch agent platform.
2
+
3
+ Keyless, no-cap, auto-payout prediction-market betting. The paying wallet IS the
4
+ account — no API keys. Winners are paid out automatically on resolution.
5
+
6
+ from hunch_agent import HunchAgent
7
+ hunch = HunchAgent()
8
+ markets = hunch.markets(status="open", limit=5)
9
+ sim = hunch.bet(markets[0]["id"], "yes", 1, wallet_address="0x...", simulate=True)
10
+
11
+ Docs: https://www.playhunch.xyz/llms.txt
12
+ """
13
+
14
+ from .client import DEFAULT_BASE_URL, HunchAgent
15
+ from .errors import HunchApiError, HunchPaymentRequiredError
16
+ from .webhooks import sign_webhook_payload, verify_webhook
17
+ from .x402 import (
18
+ BASE_CHAIN_ID,
19
+ HunchX402Error,
20
+ build_transfer_authorization,
21
+ encode_x_payment_header,
22
+ parse_x402_challenge,
23
+ random_nonce,
24
+ )
25
+
26
+ __version__ = "0.1.0"
27
+
28
+ __all__ = [
29
+ "HunchAgent",
30
+ "DEFAULT_BASE_URL",
31
+ "HunchApiError",
32
+ "HunchPaymentRequiredError",
33
+ "verify_webhook",
34
+ "sign_webhook_payload",
35
+ "parse_x402_challenge",
36
+ "build_transfer_authorization",
37
+ "encode_x_payment_header",
38
+ "random_nonce",
39
+ "HunchX402Error",
40
+ "BASE_CHAIN_ID",
41
+ "__version__",
42
+ ]
hunch_agent/client.py ADDED
@@ -0,0 +1,205 @@
1
+ """HunchAgent — the Python client for the Hunch agent platform.
2
+
3
+ Mirrors the TypeScript SDK: thin typed GETs that return the wire dicts, and a
4
+ ``bet()`` that runs the full x402 loop (POST → 402 → sign EIP-3009 → retry with
5
+ ``X-PAYMENT``). ``simulate=True`` needs no wallet. The TS SDK owns the
6
+ live-route contract tests; this client is the convenience surface for Python
7
+ agents and is exercised against recorded fixtures.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import secrets
13
+ from typing import Any, Dict, List, Optional, Sequence
14
+
15
+ import httpx
16
+
17
+ from .errors import HunchApiError, HunchPaymentRequiredError
18
+ from .x402 import (
19
+ build_transfer_authorization,
20
+ encode_x_payment_header,
21
+ parse_x402_challenge,
22
+ )
23
+
24
+ DEFAULT_BASE_URL = "https://www.playhunch.xyz"
25
+
26
+
27
+ def _clean(params: Dict[str, Any]) -> Dict[str, Any]:
28
+ return {k: v for k, v in params.items() if v is not None}
29
+
30
+
31
+ def _random_idem() -> str:
32
+ return "sdk-" + secrets.token_hex(16)
33
+
34
+
35
+ def _sign_typed_data(account: Any, typed_data: Dict[str, Any]) -> str:
36
+ """Sign EIP-712 typed data with an ``eth_account`` local account → ``0x``+130 hex."""
37
+ from eth_account.messages import encode_typed_data
38
+
39
+ signable = encode_typed_data(full_message=typed_data)
40
+ signed = account.sign_message(signable)
41
+ signature = signed.signature.hex()
42
+ return signature if signature.startswith("0x") else "0x" + signature
43
+
44
+
45
+ class HunchAgent:
46
+ """A keyless client. Pass ``account`` (an ``eth_account`` LocalAccount) only
47
+ for real bets; reads and simulations need nothing."""
48
+
49
+ def __init__(
50
+ self,
51
+ base_url: str = DEFAULT_BASE_URL,
52
+ account: Any = None,
53
+ client: Optional[httpx.Client] = None,
54
+ timeout: float = 30.0,
55
+ ) -> None:
56
+ self.base_url = base_url.rstrip("/")
57
+ self.account = account
58
+ self._client = client or httpx.Client(timeout=timeout)
59
+
60
+ # -- internals ---------------------------------------------------------
61
+
62
+ def _get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
63
+ res = self._client.get(self.base_url + path, params=params)
64
+ return self._unwrap(res)
65
+
66
+ def _unwrap(self, res: httpx.Response) -> Any:
67
+ body: Any = res.json() if res.content else {}
68
+ if res.status_code >= 400:
69
+ raise HunchApiError(res.status_code, body if isinstance(body, dict) else {})
70
+ return body
71
+
72
+ # -- read surface ------------------------------------------------------
73
+
74
+ def markets(
75
+ self,
76
+ status: Optional[str] = None,
77
+ type: Optional[str] = None,
78
+ token: Optional[str] = None,
79
+ ids: Optional[Sequence[str]] = None,
80
+ limit: Optional[int] = None,
81
+ ) -> List[Dict[str, Any]]:
82
+ params = _clean(
83
+ {
84
+ "status": status,
85
+ "type": type,
86
+ "token": token,
87
+ "ids": ",".join(ids) if ids else None,
88
+ "limit": limit,
89
+ }
90
+ )
91
+ return self._get("/api/agent/v1/markets", params).get("markets", [])
92
+
93
+ def discover(
94
+ self, q: Optional[str] = None, post: Optional[str] = None, limit: Optional[int] = None
95
+ ) -> List[Dict[str, Any]]:
96
+ params = _clean({"q": q, "post": post, "limit": limit})
97
+ return self._get("/api/agent/v1/discover", params).get("matches", [])
98
+
99
+ def market(self, market_id: str) -> Dict[str, Any]:
100
+ return self._get(f"/api/agent/v1/markets/{market_id}").get("market")
101
+
102
+ def research(self, market_id: str) -> Dict[str, Any]:
103
+ return self._get(f"/api/agent/v1/markets/{market_id}/research").get("research")
104
+
105
+ def quote(
106
+ self, market_id: str, side: str, size_usd: float, wallet: Optional[str] = None
107
+ ) -> Dict[str, Any]:
108
+ params = _clean(
109
+ {"marketId": market_id, "side": side, "sizeUsd": size_usd, "wallet": wallet}
110
+ )
111
+ return self._get("/api/agent/v1/quote", params).get("quote")
112
+
113
+ def positions(self, wallet: str) -> Dict[str, Any]:
114
+ return self._get("/api/agent/v1/positions", {"wallet": wallet})
115
+
116
+ def result(self, market_id: str) -> Dict[str, Any]:
117
+ return self._get("/api/agent/v1/result", {"marketId": market_id}).get("result")
118
+
119
+ def proof(self, trade_id: str) -> Dict[str, Any]:
120
+ body = self._get(f"/api/agent/v1/proof/{trade_id}")
121
+ body.pop("meta", None)
122
+ return body
123
+
124
+ def readiness(self, address: str) -> Dict[str, Any]:
125
+ return self._get(f"/api/agent/v1/wallet/{address}/readiness").get("readiness")
126
+
127
+ def stats(self) -> Dict[str, Any]:
128
+ return self._get("/api/agent/v1/stats").get("stats")
129
+
130
+ def health(self) -> Dict[str, Any]:
131
+ return self._get("/api/agent/v1/health").get("health")
132
+
133
+ def poll(self, wallet: str, since: int = 0, limit: Optional[int] = None) -> Dict[str, Any]:
134
+ body = self._get(
135
+ "/api/agent/v1/resolved", _clean({"wallet": wallet, "since": since, "limit": limit})
136
+ )
137
+ return {
138
+ "events": body.get("events", []),
139
+ "nextSince": body.get("nextSince", since),
140
+ "count": body.get("count", 0),
141
+ }
142
+
143
+ # -- money path --------------------------------------------------------
144
+
145
+ def bet(
146
+ self,
147
+ market_id: str,
148
+ side: str,
149
+ size_usd: float,
150
+ *,
151
+ idem_key: Optional[str] = None,
152
+ wallet_address: Optional[str] = None,
153
+ quote_id: Optional[str] = None,
154
+ min_shares_out: Optional[float] = None,
155
+ max_price_cents: Optional[int] = None,
156
+ simulate: bool = False,
157
+ builder_code: Optional[str] = None,
158
+ ref: Optional[str] = None,
159
+ ) -> Dict[str, Any]:
160
+ """Place a bet. ``simulate=True`` is a $0 dry run (no account needed); a real
161
+ bet runs the x402 loop automatically. Raises ``HunchPaymentRequiredError`` if a
162
+ real bet needs payment but no account is set, or ``HunchApiError`` on a 4xx/5xx."""
163
+ wallet = wallet_address or (self.account.address if self.account else None)
164
+ if not wallet:
165
+ raise ValueError("bet() needs wallet_address or a configured account.")
166
+
167
+ body = _clean(
168
+ {
169
+ "marketId": market_id,
170
+ "side": side,
171
+ "sizeUsd": size_usd,
172
+ "idemKey": idem_key or _random_idem(),
173
+ "walletAddress": wallet,
174
+ "quoteId": quote_id,
175
+ "minSharesOut": min_shares_out,
176
+ "maxPriceCents": max_price_cents,
177
+ "simulate": True if simulate else None,
178
+ "builderCode": builder_code,
179
+ "ref": ref,
180
+ }
181
+ )
182
+ url = self.base_url + "/api/agent/v1/trade"
183
+ res = self._client.post(url, json=body)
184
+
185
+ if res.status_code == 402:
186
+ challenge = res.json()
187
+ if not self.account:
188
+ raise HunchPaymentRequiredError(challenge)
189
+ requirements = parse_x402_challenge(challenge)
190
+ typed_data, authorization = build_transfer_authorization(wallet, requirements)
191
+ signature = _sign_typed_data(self.account, typed_data)
192
+ header = encode_x_payment_header({**authorization, "signature": signature})
193
+ res = self._client.post(url, json=body, headers={"X-PAYMENT": header})
194
+
195
+ out = self._unwrap(res)
196
+ return out.get("receipt")
197
+
198
+ def close(self) -> None:
199
+ self._client.close()
200
+
201
+ def __enter__(self) -> "HunchAgent":
202
+ return self
203
+
204
+ def __exit__(self, *_exc: Any) -> None:
205
+ self.close()
hunch_agent/errors.py ADDED
@@ -0,0 +1,42 @@
1
+ """Typed errors for the Hunch agent client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict
6
+
7
+
8
+ class HunchApiError(Exception):
9
+ """Any non-2xx (and non-402-handled) Hunch API response.
10
+
11
+ Carries the errors-as-documentation body: ``error`` (a stable machine code),
12
+ ``message``, ``hint``, ``docsUrl``, ``retriable``, plus machine-actionable
13
+ fields like ``requiredUsd``.
14
+ """
15
+
16
+ def __init__(self, status: int, body: Dict[str, Any]) -> None:
17
+ self.status = status
18
+ self.body = body or {}
19
+ super().__init__(
20
+ self.body.get("message")
21
+ or self.body.get("error")
22
+ or f"Hunch API error {status}"
23
+ )
24
+
25
+ @property
26
+ def code(self) -> str:
27
+ return self.body.get("error", "unknown_error")
28
+
29
+ @property
30
+ def retriable(self) -> bool:
31
+ return self.body.get("retriable") is True
32
+
33
+
34
+ class HunchPaymentRequiredError(Exception):
35
+ """A real bet hit a 402 but no signing ``account`` was configured to pay."""
36
+
37
+ def __init__(self, challenge: Dict[str, Any]) -> None:
38
+ self.challenge = challenge
39
+ super().__init__(
40
+ "This bet requires an x402 USDC payment, but no signing `account` was "
41
+ "configured. Pass `account=` to HunchAgent, or use simulate=True."
42
+ )
@@ -0,0 +1,63 @@
1
+ """Webhook signature verification — the receiving half of Hunch's signed events.
2
+
3
+ The recipe is byte-identical to the server's ``signEvent`` (src/agent/events.ts)
4
+ and the TS SDK: ``hex(hmacSHA256(secret, eventId + "." + timestamp + "." + body))``,
5
+ sent as ``Hunch-Signature: v1=<hex>`` alongside ``Hunch-Event-Id`` /
6
+ ``Hunch-Timestamp``. Verify over the RAW request body.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ import hmac
13
+ import json
14
+ import re
15
+ from typing import Any, Dict, Optional
16
+
17
+
18
+ def _read_header(headers: Any, name: str) -> Optional[str]:
19
+ # Works for dict-likes and frameworks' case-insensitive header maps.
20
+ if hasattr(headers, "get"):
21
+ value = headers.get(name)
22
+ if value is None:
23
+ value = headers.get(name.lower())
24
+ if value is not None:
25
+ return value if isinstance(value, str) else value[0]
26
+ try:
27
+ items = headers.items()
28
+ except AttributeError:
29
+ items = dict(headers).items()
30
+ lower = name.lower()
31
+ for key, value in items:
32
+ if str(key).lower() == lower:
33
+ return value if isinstance(value, str) else value[0]
34
+ return None
35
+
36
+
37
+ def sign_webhook_payload(secret: str, event_id: str, timestamp: str, body: str) -> str:
38
+ """The bare ``hex(hmacSHA256(secret, eventId + "." + timestamp + "." + body))``."""
39
+ message = f"{event_id}.{timestamp}.{body}".encode("utf-8")
40
+ return hmac.new(secret.encode("utf-8"), message, hashlib.sha256).hexdigest()
41
+
42
+
43
+ def verify_webhook(headers: Any, body: str, secret: str) -> Dict[str, Any]:
44
+ """Verify a Hunch webhook delivery. Returns ``{valid, event, reason}``; never raises."""
45
+ event_id = _read_header(headers, "Hunch-Event-Id")
46
+ timestamp = _read_header(headers, "Hunch-Timestamp")
47
+ signature = _read_header(headers, "Hunch-Signature")
48
+ if not event_id or not timestamp or not signature:
49
+ return {"valid": False, "event": None, "reason": "missing_headers"}
50
+
51
+ match = re.match(r"^v1=([0-9a-f]+)$", signature.strip())
52
+ if not match:
53
+ return {"valid": False, "event": None, "reason": "bad_signature_format"}
54
+
55
+ expected = sign_webhook_payload(secret, event_id, timestamp, body)
56
+ if not hmac.compare_digest(match.group(1), expected):
57
+ return {"valid": False, "event": None, "reason": "signature_mismatch"}
58
+
59
+ try:
60
+ event = json.loads(body)
61
+ except (ValueError, TypeError):
62
+ event = None
63
+ return {"valid": True, "event": event, "reason": "ok"}
hunch_agent/x402.py ADDED
@@ -0,0 +1,127 @@
1
+ """Client-side x402 codec — the payment half of the Hunch agent rail.
2
+
3
+ Mirrors the TS SDK (`packages/hunch-agent-sdk/src/x402.ts`) and the server codec
4
+ (`src/lib/x402.ts`): parse a 402 challenge, build the EIP-3009
5
+ ``transferWithAuthorization`` typed data, and encode the base64 ``X-PAYMENT``
6
+ header the trade route decodes. Pure — signing is done by the caller's
7
+ ``eth_account`` account.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import base64
13
+ import json
14
+ import secrets
15
+ import time
16
+ from typing import Any, Dict, Optional, Tuple
17
+
18
+ #: Base mainnet chain id — the only settlement chain on the agent rail.
19
+ BASE_CHAIN_ID = 8453
20
+
21
+ _DEFAULT_VALID_SECONDS = 10 * 60
22
+ _CLOCK_SKEW_SECONDS = 60
23
+
24
+ #: EIP-712 type for USDC's EIP-3009 ``transferWithAuthorization``.
25
+ TRANSFER_WITH_AUTHORIZATION_TYPES = {
26
+ "TransferWithAuthorization": [
27
+ {"name": "from", "type": "address"},
28
+ {"name": "to", "type": "address"},
29
+ {"name": "value", "type": "uint256"},
30
+ {"name": "validAfter", "type": "uint256"},
31
+ {"name": "validBefore", "type": "uint256"},
32
+ {"name": "nonce", "type": "bytes32"},
33
+ ],
34
+ }
35
+
36
+
37
+ class HunchX402Error(Exception):
38
+ """Raised when a 402 challenge is missing or not the Base USDC exact scheme."""
39
+
40
+
41
+ def parse_x402_challenge(body: Dict[str, Any]) -> Dict[str, Any]:
42
+ """Return the single usable payment requirement from a 402 challenge body."""
43
+ accepts = (body or {}).get("accepts")
44
+ if not isinstance(accepts, list) or not accepts:
45
+ raise HunchX402Error("402 challenge did not advertise a payment requirement.")
46
+ first = accepts[0]
47
+ if first.get("scheme") != "exact":
48
+ raise HunchX402Error(f"Unsupported x402 scheme: {first.get('scheme')}")
49
+ if first.get("network") != "base":
50
+ raise HunchX402Error(f"Unsupported x402 network: {first.get('network')}")
51
+ if not first.get("payTo") or not first.get("asset") or not first.get("extra"):
52
+ raise HunchX402Error("402 challenge is missing payTo / asset / domain extra.")
53
+ return first
54
+
55
+
56
+ def random_nonce() -> str:
57
+ """A fresh, single-use 32-byte EIP-3009 nonce (``0x`` + 64 hex)."""
58
+ return "0x" + secrets.token_hex(32)
59
+
60
+
61
+ def build_transfer_authorization(
62
+ from_addr: str,
63
+ requirements: Dict[str, Any],
64
+ now: Optional[int] = None,
65
+ nonce: Optional[str] = None,
66
+ valid_seconds: int = _DEFAULT_VALID_SECONDS,
67
+ ) -> Tuple[Dict[str, Any], Dict[str, Any]]:
68
+ """Build ``(typed_data, authorization)`` from a parsed challenge requirement."""
69
+ now = int(now if now is not None else time.time())
70
+ valid_after = max(0, now - _CLOCK_SKEW_SECONDS)
71
+ valid_before = now + valid_seconds
72
+ nonce = nonce or random_nonce()
73
+ to = requirements["payTo"]
74
+ value = requirements["maxAmountRequired"]
75
+
76
+ authorization = {
77
+ "from": from_addr,
78
+ "to": to,
79
+ "value": value,
80
+ "validAfter": valid_after,
81
+ "validBefore": valid_before,
82
+ "nonce": nonce,
83
+ }
84
+ typed_data = {
85
+ "types": {
86
+ "EIP712Domain": [
87
+ {"name": "name", "type": "string"},
88
+ {"name": "version", "type": "string"},
89
+ {"name": "chainId", "type": "uint256"},
90
+ {"name": "verifyingContract", "type": "address"},
91
+ ],
92
+ **TRANSFER_WITH_AUTHORIZATION_TYPES,
93
+ },
94
+ "domain": {
95
+ "name": requirements["extra"]["name"],
96
+ "version": requirements["extra"]["version"],
97
+ "chainId": BASE_CHAIN_ID,
98
+ "verifyingContract": requirements["asset"],
99
+ },
100
+ "primaryType": "TransferWithAuthorization",
101
+ "message": {
102
+ "from": from_addr,
103
+ "to": to,
104
+ "value": int(value),
105
+ "validAfter": valid_after,
106
+ "validBefore": valid_before,
107
+ "nonce": nonce,
108
+ },
109
+ }
110
+ return typed_data, authorization
111
+
112
+
113
+ def encode_x_payment_header(signed_authorization: Dict[str, Any]) -> str:
114
+ """Encode a signed authorization into the base64 ``X-PAYMENT`` header value."""
115
+ payload = {
116
+ "x402Version": 1,
117
+ "scheme": "exact",
118
+ "network": "base",
119
+ "payload": {
120
+ "signature": signed_authorization["signature"],
121
+ "authorization": {
122
+ k: signed_authorization[k]
123
+ for k in ("from", "to", "value", "validAfter", "validBefore", "nonce")
124
+ },
125
+ },
126
+ }
127
+ return base64.b64encode(json.dumps(payload).encode("utf-8")).decode("ascii")
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.4
2
+ Name: hunch-agent
3
+ Version: 0.1.0
4
+ Summary: Python client for the Hunch agent platform — keyless, no-cap x402 prediction-market betting on Base.
5
+ Project-URL: Homepage, https://www.playhunch.xyz/agents
6
+ Project-URL: Documentation, https://www.playhunch.xyz/llms-full.txt
7
+ License: MIT
8
+ Keywords: agents,base,hunch,prediction-markets,usdc,x402
9
+ Requires-Python: >=3.9
10
+ Requires-Dist: eth-account>=0.10
11
+ Requires-Dist: httpx>=0.24
12
+ Provides-Extra: test
13
+ Requires-Dist: pytest>=7; extra == 'test'
14
+ Description-Content-Type: text/markdown
15
+
16
+ # hunch-agent (Python)
17
+
18
+ Python client for the [Hunch](https://www.playhunch.xyz) agent platform. Keyless,
19
+ no-cap, auto-payout.
20
+
21
+ ```bash
22
+ # PyPI release pending — install from source:
23
+ pip install "git+https://github.com/rajkaria/hunch.git#subdirectory=sdk/python"
24
+ # or from a local clone: pip install -e sdk/python
25
+ ```
26
+
27
+ ## $0 simulation (no wallet)
28
+
29
+ ```python
30
+ from hunch_agent import HunchAgent
31
+
32
+ hunch = HunchAgent() # defaults to https://www.playhunch.xyz
33
+ markets = hunch.markets(status="open", limit=5)
34
+ research = hunch.research(markets[0]["id"])
35
+ print(research["resolutionRules"]["description"], research["odds"])
36
+
37
+ sim = hunch.bet(
38
+ markets[0]["id"], "yes", 1,
39
+ wallet_address="0xYourWallet...", simulate=True,
40
+ )
41
+ print(sim["simulated"], sim["position"]) # True, {...}
42
+ ```
43
+
44
+ ## Real bet (x402 USDC on Base)
45
+
46
+ The client runs the whole x402 loop for you — POST, get the 402, sign the exact
47
+ USDC `transferWithAuthorization` with `eth_account`, retry with `X-PAYMENT`. The
48
+ wallet only needs USDC on Base; gas is sponsored. Winners are paid automatically —
49
+ no claim step.
50
+
51
+ ```python
52
+ from eth_account import Account
53
+ from hunch_agent import HunchAgent
54
+
55
+ account = Account.from_key("0x...") # a funded Base wallet
56
+ hunch = HunchAgent(account=account)
57
+
58
+ receipt = hunch.bet("market-id", "yes", 5) # <= $10: simple tier
59
+ print(receipt["txHash"], receipt["proofUrl"])
60
+
61
+ # > $10: lock a quote first.
62
+ q = hunch.quote("market-id", "yes", 250)
63
+ hunch.bet("market-id", "yes", 250, quote_id=q["quoteId"], min_shares_out=q["suggestedMinSharesOut"])
64
+ ```
65
+
66
+ ## Verifying webhooks
67
+
68
+ ```python
69
+ from hunch_agent import verify_webhook
70
+
71
+ result = verify_webhook(request.headers, raw_body, secret)
72
+ if result["valid"]:
73
+ handle(result["event"])
74
+ ```
75
+
76
+ The TypeScript SDK (`@hunchxyz/agent-sdk`) carries the full live-route contract
77
+ tests; this client is the Python convenience surface, tested against recorded
78
+ fixtures. Full protocol docs: <https://www.playhunch.xyz/llms-full.txt>.
@@ -0,0 +1,8 @@
1
+ hunch_agent/__init__.py,sha256=swLQsA7_3y8Ijhd1K4Xpnu1RXEK3kC6kbjRoiEn51uo,1169
2
+ hunch_agent/client.py,sha256=OeGS6isWj_k8q9yFHLXefBB7MTrRt2U__w_4u771JZI,7573
3
+ hunch_agent/errors.py,sha256=_w1O5jJ8G6-NFh_AUOhwzmAvAmCc9JXrgAwt9u792io,1308
4
+ hunch_agent/webhooks.py,sha256=PTxK7agC4llU7xHRKJmkehmJv7oF-lr092MIqrZjriI,2472
5
+ hunch_agent/x402.py,sha256=R-ZiNLO5ZAqsm0rm1C-lxfvAosU1_dax6SEFJTO_z5U,4485
6
+ hunch_agent-0.1.0.dist-info/METADATA,sha256=y21e66lBtu4ujiAcrGs4VLOm4h_sp9fCucPY3CqI-M0,2525
7
+ hunch_agent-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
8
+ hunch_agent-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any