Metadata-Version: 2.4
Name: cracked-ai
Version: 0.1.0
Summary: Zero-dependency Python client for Cracked, the agent tool router: discover, inspect, run and poll 50,000+ tools with one API key.
Author: Cracked
License: MIT
Project-URL: Homepage, https://cracked.ai
Project-URL: Documentation, https://cracked.ai/docs/sdks
Project-URL: API reference, https://cracked.ai/docs/api
Keywords: agents,tools,api,llm,scraping,mcp,cracked
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"

# cracked-ai (Python)

Zero-dependency Python client for [Cracked](https://cracked.ai), the tool router for agents: one API key, one balance, 50,000+ tools (scrapers, search, data, AI models). Discover a tool in natural language, inspect its schema, run it, poll if it is asynchronous.

Publishing to PyPI soon. Until then install from the repo path:

```bash
pip install ./sdk/python            # from the repo root
# or, once published:
pip install cracked-ai
```

Python 3.9+. Uses only the standard library (`urllib`).

## Quick start

```python
import os
from cracked_ai import Cracked, CrackedError

c = Cracked()  # reads CRACKED_API_KEY; base URL from CRACKED_BASE_URL (default https://cracked.ai/v1)

hits = c.discover("weather forecast for a lat/long", limit=5)["results"]
best = hits[0]
schema = c.inspect(best["provider"], best["endpoint"])["input"]["body"]   # JSON Schema, never guess fields

rec = c.run("open-meteo", "/forecast", {"latitude": 30.27, "longitude": -97.74})
print(rec["status"], rec["billing"]["totalUsd"])
print(rec["output"])
```

No key yet? Register an agent workspace with no human in the loop (trial credit included):

```python
reg = Cracked.register_agent(name="my-agent")
reg.client.run(...)            # ready to use
print(reg.api_key)             # shown once: store it as CRACKED_API_KEY
print(reg.claim_url)           # give this to your human: adds credit, unlocks top-ups
```

## Methods

| Method | Endpoint | Notes |
|---|---|---|
| `discover(query, limit=8, live_only=None, min_score=None, category=None, provider=None, include_apify=None, include_unavailable=None)` | `POST /discover` | Returns `{query, count, results}` |
| `inspect(provider, endpoint)` | `POST /inspect` | Schema in `["input"]["body"]`, price, health |
| `run(provider, endpoint, input=None, wait=True, poll=None, webhook_url=None, timeout=300, poll_wait=30)` | `POST /run` | Returns a `RunRecord`; polls on 202 when `poll` (defaults to `wait`) |
| `run_capability(capability, input=None, ...)` | `POST /run {capability}` | Smart run: Cracked picks the provider and falls back. Adds `routedTo`, `attempts` |
| `capabilities(id=None)` | `GET /capabilities?id=` | List capabilities (input schema + ranked candidates), or one by id. No auth |
| `get_run(run_id, wait=None)` | `GET /runs/{id}?wait=` | `wait` long-polls up to 100 s |
| `wait_for(run_id, timeout=300, poll_wait=30)` | polls `GET /runs/{id}` | Raises `CrackedTimeout` (with `.record`) if still RUNNING |
| `stop(run_id)` | `POST /runs/{id}/stop` | 409 if already finished |
| `runs(limit=25)` | `GET /runs` | Recent runs, no output bodies |
| `feedback(run_id, ok, note=None)` | `POST /runs/{id}/feedback` | Thumbs up/down; small bounty on claimed workspaces |
| `balance()` | `GET /wallet/balance` | `balance`, `lifetimeTopUp`, `lifetimeSpend` |
| `whoami()` | `GET /auth/whoami` | |
| `referrals()` / `referral_link()` | `GET /referrals` | Share the link: both sides get credit |
| `bounty_program()` | `GET /bounties/program` | Public, no auth |
| `bounties()` | `GET /bounties` | Your bounty ledger |
| `claim_bounty(url)` | `POST /bounties` | Backlink/listing bounty for a page carrying your referral link |
| `Cracked.register_agent(name=None, ref=None, base_url=None)` | `POST /agents/register` | Classmethod, no auth. Returns `AgentRegistration(client, api_key, claim_url, referral_url, ...)` |

All responses are plain `dict`s typed as `TypedDict`s (`RunRecord`, `SmartRunRecord`, `DiscoverResult`, `InspectResult`, `Balance`, ...). Import them from `cracked_ai`.

## Async runs

Scrapers marked `async: true` in discover results can take minutes. Either let the client poll:

```python
rec = c.run("apify", "/apify/instagram-profile-scraper", {"usernames": ["nike"]}, wait=False, poll=True, timeout=600)
```

or drive it yourself:

```python
rec = c.run("apify", "/apify/instagram-profile-scraper", {"usernames": ["nike"]}, wait=False)   # HTTP 202, status RUNNING
rec = c.wait_for(rec["runId"], timeout=600)                                                     # or c.get_run(id, wait=30) in a loop
c.stop(rec["runId"])                                                                            # abort if needed
```

Terminal statuses: `COMPLETED`, `FAILED`, `BLOCKED`, `STOPPED`, `TIMED_OUT`. `COMPLETED` with `providerResponse.httpStatus == 404` means the provider found nothing; that is not an error. `BLOCKED` (HTTP 503, not billed) means the provider needs your own key: add it at `/app/connections` or pick another result.

## Errors

Every non-2xx response raises `CrackedError` with `.status`, `.code`, `.message` (and `.run_id` when relevant):

```python
try:
    c.run("some-provider", "/endpoint", {...})
except CrackedError as e:
    if e.insufficient_funds:   # 402: top up at https://cracked.ai/app/billing or earn credit via bounties
        ...
    elif e.unauthorized:       # 401: bad or missing key
        ...
    elif e.rate_limited:       # 429
        ...
```

Transport failures raise `CrackedError` with `status == 0` and code `NETWORK_ERROR` / `NETWORK_TIMEOUT`.

## CLI

```bash
python -m cracked_ai discover "instagram profile followers" -l 5
python -m cracked_ai inspect open-meteo /forecast
python -m cracked_ai run open-meteo /forecast '{"latitude":30.27,"longitude":-97.74}'
python -m cracked_ai capability instagram-profile '{"username":"nike"}'
python -m cracked_ai get <runId> -w 30
python -m cracked_ai balance
python -m cracked_ai register my-agent
```

(`cracked-ai ...` works too once installed via pip.)

## Tests

`tests/test_smoke.py` hits the live API and is skipped unless `CRACKED_API_KEY` is set. It spends well under $0.05 (two keyless weather calls).

```bash
CRACKED_API_KEY=ck_live_... CRACKED_BASE_URL=https://cracked.ai/v1 python -m pytest sdk/python/tests -q
```

## Publishing

```bash
cd sdk/python && python -m pip install build twine && python -m build && twine upload dist/*
```
