cracked-ai 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.
- cracked_ai-0.1.0/PKG-INFO +148 -0
- cracked_ai-0.1.0/README.md +127 -0
- cracked_ai-0.1.0/pyproject.toml +42 -0
- cracked_ai-0.1.0/setup.cfg +4 -0
- cracked_ai-0.1.0/src/cracked_ai/__init__.py +69 -0
- cracked_ai-0.1.0/src/cracked_ai/__main__.py +105 -0
- cracked_ai-0.1.0/src/cracked_ai/client.py +329 -0
- cracked_ai-0.1.0/src/cracked_ai/errors.py +66 -0
- cracked_ai-0.1.0/src/cracked_ai/py.typed +0 -0
- cracked_ai-0.1.0/src/cracked_ai/schema.py +96 -0
- cracked_ai-0.1.0/src/cracked_ai/types.py +193 -0
- cracked_ai-0.1.0/src/cracked_ai.egg-info/PKG-INFO +148 -0
- cracked_ai-0.1.0/src/cracked_ai.egg-info/SOURCES.txt +16 -0
- cracked_ai-0.1.0/src/cracked_ai.egg-info/dependency_links.txt +1 -0
- cracked_ai-0.1.0/src/cracked_ai.egg-info/entry_points.txt +2 -0
- cracked_ai-0.1.0/src/cracked_ai.egg-info/requires.txt +3 -0
- cracked_ai-0.1.0/src/cracked_ai.egg-info/top_level.txt +1 -0
- cracked_ai-0.1.0/tests/test_smoke.py +151 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cracked-ai
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Zero-dependency Python client for Cracked, the agent tool router: discover, inspect, run and poll 50,000+ tools with one API key.
|
|
5
|
+
Author: Cracked
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://cracked.ai
|
|
8
|
+
Project-URL: Documentation, https://cracked.ai/docs/sdks
|
|
9
|
+
Project-URL: API reference, https://cracked.ai/docs/api
|
|
10
|
+
Keywords: agents,tools,api,llm,scraping,mcp,cracked
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
17
|
+
Requires-Python: >=3.9
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Provides-Extra: test
|
|
20
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
21
|
+
|
|
22
|
+
# cracked-ai (Python)
|
|
23
|
+
|
|
24
|
+
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.
|
|
25
|
+
|
|
26
|
+
Publishing to PyPI soon. Until then install from the repo path:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install ./sdk/python # from the repo root
|
|
30
|
+
# or, once published:
|
|
31
|
+
pip install cracked-ai
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Python 3.9+. Uses only the standard library (`urllib`).
|
|
35
|
+
|
|
36
|
+
## Quick start
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import os
|
|
40
|
+
from cracked_ai import Cracked, CrackedError
|
|
41
|
+
|
|
42
|
+
c = Cracked() # reads CRACKED_API_KEY; base URL from CRACKED_BASE_URL (default https://cracked.ai/v1)
|
|
43
|
+
|
|
44
|
+
hits = c.discover("weather forecast for a lat/long", limit=5)["results"]
|
|
45
|
+
best = hits[0]
|
|
46
|
+
schema = c.inspect(best["provider"], best["endpoint"])["input"]["body"] # JSON Schema, never guess fields
|
|
47
|
+
|
|
48
|
+
rec = c.run("open-meteo", "/forecast", {"latitude": 30.27, "longitude": -97.74})
|
|
49
|
+
print(rec["status"], rec["billing"]["totalUsd"])
|
|
50
|
+
print(rec["output"])
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
No key yet? Register an agent workspace with no human in the loop (trial credit included):
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
reg = Cracked.register_agent(name="my-agent")
|
|
57
|
+
reg.client.run(...) # ready to use
|
|
58
|
+
print(reg.api_key) # shown once: store it as CRACKED_API_KEY
|
|
59
|
+
print(reg.claim_url) # give this to your human: adds credit, unlocks top-ups
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Methods
|
|
63
|
+
|
|
64
|
+
| Method | Endpoint | Notes |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| `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}` |
|
|
67
|
+
| `inspect(provider, endpoint)` | `POST /inspect` | Schema in `["input"]["body"]`, price, health |
|
|
68
|
+
| `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`) |
|
|
69
|
+
| `run_capability(capability, input=None, ...)` | `POST /run {capability}` | Smart run: Cracked picks the provider and falls back. Adds `routedTo`, `attempts` |
|
|
70
|
+
| `capabilities(id=None)` | `GET /capabilities?id=` | List capabilities (input schema + ranked candidates), or one by id. No auth |
|
|
71
|
+
| `get_run(run_id, wait=None)` | `GET /runs/{id}?wait=` | `wait` long-polls up to 100 s |
|
|
72
|
+
| `wait_for(run_id, timeout=300, poll_wait=30)` | polls `GET /runs/{id}` | Raises `CrackedTimeout` (with `.record`) if still RUNNING |
|
|
73
|
+
| `stop(run_id)` | `POST /runs/{id}/stop` | 409 if already finished |
|
|
74
|
+
| `runs(limit=25)` | `GET /runs` | Recent runs, no output bodies |
|
|
75
|
+
| `feedback(run_id, ok, note=None)` | `POST /runs/{id}/feedback` | Thumbs up/down; small bounty on claimed workspaces |
|
|
76
|
+
| `balance()` | `GET /wallet/balance` | `balance`, `lifetimeTopUp`, `lifetimeSpend` |
|
|
77
|
+
| `whoami()` | `GET /auth/whoami` | |
|
|
78
|
+
| `referrals()` / `referral_link()` | `GET /referrals` | Share the link: both sides get credit |
|
|
79
|
+
| `bounty_program()` | `GET /bounties/program` | Public, no auth |
|
|
80
|
+
| `bounties()` | `GET /bounties` | Your bounty ledger |
|
|
81
|
+
| `claim_bounty(url)` | `POST /bounties` | Backlink/listing bounty for a page carrying your referral link |
|
|
82
|
+
| `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, ...)` |
|
|
83
|
+
|
|
84
|
+
All responses are plain `dict`s typed as `TypedDict`s (`RunRecord`, `SmartRunRecord`, `DiscoverResult`, `InspectResult`, `Balance`, ...). Import them from `cracked_ai`.
|
|
85
|
+
|
|
86
|
+
## Async runs
|
|
87
|
+
|
|
88
|
+
Scrapers marked `async: true` in discover results can take minutes. Either let the client poll:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
rec = c.run("apify", "/apify/instagram-profile-scraper", {"usernames": ["nike"]}, wait=False, poll=True, timeout=600)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
or drive it yourself:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
rec = c.run("apify", "/apify/instagram-profile-scraper", {"usernames": ["nike"]}, wait=False) # HTTP 202, status RUNNING
|
|
98
|
+
rec = c.wait_for(rec["runId"], timeout=600) # or c.get_run(id, wait=30) in a loop
|
|
99
|
+
c.stop(rec["runId"]) # abort if needed
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
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.
|
|
103
|
+
|
|
104
|
+
## Errors
|
|
105
|
+
|
|
106
|
+
Every non-2xx response raises `CrackedError` with `.status`, `.code`, `.message` (and `.run_id` when relevant):
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
try:
|
|
110
|
+
c.run("some-provider", "/endpoint", {...})
|
|
111
|
+
except CrackedError as e:
|
|
112
|
+
if e.insufficient_funds: # 402: top up at https://cracked.ai/app/billing or earn credit via bounties
|
|
113
|
+
...
|
|
114
|
+
elif e.unauthorized: # 401: bad or missing key
|
|
115
|
+
...
|
|
116
|
+
elif e.rate_limited: # 429
|
|
117
|
+
...
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Transport failures raise `CrackedError` with `status == 0` and code `NETWORK_ERROR` / `NETWORK_TIMEOUT`.
|
|
121
|
+
|
|
122
|
+
## CLI
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
python -m cracked_ai discover "instagram profile followers" -l 5
|
|
126
|
+
python -m cracked_ai inspect open-meteo /forecast
|
|
127
|
+
python -m cracked_ai run open-meteo /forecast '{"latitude":30.27,"longitude":-97.74}'
|
|
128
|
+
python -m cracked_ai capability instagram-profile '{"username":"nike"}'
|
|
129
|
+
python -m cracked_ai get <runId> -w 30
|
|
130
|
+
python -m cracked_ai balance
|
|
131
|
+
python -m cracked_ai register my-agent
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
(`cracked-ai ...` works too once installed via pip.)
|
|
135
|
+
|
|
136
|
+
## Tests
|
|
137
|
+
|
|
138
|
+
`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).
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
CRACKED_API_KEY=ck_live_... CRACKED_BASE_URL=https://cracked.ai/v1 python -m pytest sdk/python/tests -q
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Publishing
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
cd sdk/python && python -m pip install build twine && python -m build && twine upload dist/*
|
|
148
|
+
```
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# cracked-ai (Python)
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
Publishing to PyPI soon. Until then install from the repo path:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install ./sdk/python # from the repo root
|
|
9
|
+
# or, once published:
|
|
10
|
+
pip install cracked-ai
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Python 3.9+. Uses only the standard library (`urllib`).
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import os
|
|
19
|
+
from cracked_ai import Cracked, CrackedError
|
|
20
|
+
|
|
21
|
+
c = Cracked() # reads CRACKED_API_KEY; base URL from CRACKED_BASE_URL (default https://cracked.ai/v1)
|
|
22
|
+
|
|
23
|
+
hits = c.discover("weather forecast for a lat/long", limit=5)["results"]
|
|
24
|
+
best = hits[0]
|
|
25
|
+
schema = c.inspect(best["provider"], best["endpoint"])["input"]["body"] # JSON Schema, never guess fields
|
|
26
|
+
|
|
27
|
+
rec = c.run("open-meteo", "/forecast", {"latitude": 30.27, "longitude": -97.74})
|
|
28
|
+
print(rec["status"], rec["billing"]["totalUsd"])
|
|
29
|
+
print(rec["output"])
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
No key yet? Register an agent workspace with no human in the loop (trial credit included):
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
reg = Cracked.register_agent(name="my-agent")
|
|
36
|
+
reg.client.run(...) # ready to use
|
|
37
|
+
print(reg.api_key) # shown once: store it as CRACKED_API_KEY
|
|
38
|
+
print(reg.claim_url) # give this to your human: adds credit, unlocks top-ups
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Methods
|
|
42
|
+
|
|
43
|
+
| Method | Endpoint | Notes |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| `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}` |
|
|
46
|
+
| `inspect(provider, endpoint)` | `POST /inspect` | Schema in `["input"]["body"]`, price, health |
|
|
47
|
+
| `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`) |
|
|
48
|
+
| `run_capability(capability, input=None, ...)` | `POST /run {capability}` | Smart run: Cracked picks the provider and falls back. Adds `routedTo`, `attempts` |
|
|
49
|
+
| `capabilities(id=None)` | `GET /capabilities?id=` | List capabilities (input schema + ranked candidates), or one by id. No auth |
|
|
50
|
+
| `get_run(run_id, wait=None)` | `GET /runs/{id}?wait=` | `wait` long-polls up to 100 s |
|
|
51
|
+
| `wait_for(run_id, timeout=300, poll_wait=30)` | polls `GET /runs/{id}` | Raises `CrackedTimeout` (with `.record`) if still RUNNING |
|
|
52
|
+
| `stop(run_id)` | `POST /runs/{id}/stop` | 409 if already finished |
|
|
53
|
+
| `runs(limit=25)` | `GET /runs` | Recent runs, no output bodies |
|
|
54
|
+
| `feedback(run_id, ok, note=None)` | `POST /runs/{id}/feedback` | Thumbs up/down; small bounty on claimed workspaces |
|
|
55
|
+
| `balance()` | `GET /wallet/balance` | `balance`, `lifetimeTopUp`, `lifetimeSpend` |
|
|
56
|
+
| `whoami()` | `GET /auth/whoami` | |
|
|
57
|
+
| `referrals()` / `referral_link()` | `GET /referrals` | Share the link: both sides get credit |
|
|
58
|
+
| `bounty_program()` | `GET /bounties/program` | Public, no auth |
|
|
59
|
+
| `bounties()` | `GET /bounties` | Your bounty ledger |
|
|
60
|
+
| `claim_bounty(url)` | `POST /bounties` | Backlink/listing bounty for a page carrying your referral link |
|
|
61
|
+
| `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, ...)` |
|
|
62
|
+
|
|
63
|
+
All responses are plain `dict`s typed as `TypedDict`s (`RunRecord`, `SmartRunRecord`, `DiscoverResult`, `InspectResult`, `Balance`, ...). Import them from `cracked_ai`.
|
|
64
|
+
|
|
65
|
+
## Async runs
|
|
66
|
+
|
|
67
|
+
Scrapers marked `async: true` in discover results can take minutes. Either let the client poll:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
rec = c.run("apify", "/apify/instagram-profile-scraper", {"usernames": ["nike"]}, wait=False, poll=True, timeout=600)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
or drive it yourself:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
rec = c.run("apify", "/apify/instagram-profile-scraper", {"usernames": ["nike"]}, wait=False) # HTTP 202, status RUNNING
|
|
77
|
+
rec = c.wait_for(rec["runId"], timeout=600) # or c.get_run(id, wait=30) in a loop
|
|
78
|
+
c.stop(rec["runId"]) # abort if needed
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
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.
|
|
82
|
+
|
|
83
|
+
## Errors
|
|
84
|
+
|
|
85
|
+
Every non-2xx response raises `CrackedError` with `.status`, `.code`, `.message` (and `.run_id` when relevant):
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
try:
|
|
89
|
+
c.run("some-provider", "/endpoint", {...})
|
|
90
|
+
except CrackedError as e:
|
|
91
|
+
if e.insufficient_funds: # 402: top up at https://cracked.ai/app/billing or earn credit via bounties
|
|
92
|
+
...
|
|
93
|
+
elif e.unauthorized: # 401: bad or missing key
|
|
94
|
+
...
|
|
95
|
+
elif e.rate_limited: # 429
|
|
96
|
+
...
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Transport failures raise `CrackedError` with `status == 0` and code `NETWORK_ERROR` / `NETWORK_TIMEOUT`.
|
|
100
|
+
|
|
101
|
+
## CLI
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
python -m cracked_ai discover "instagram profile followers" -l 5
|
|
105
|
+
python -m cracked_ai inspect open-meteo /forecast
|
|
106
|
+
python -m cracked_ai run open-meteo /forecast '{"latitude":30.27,"longitude":-97.74}'
|
|
107
|
+
python -m cracked_ai capability instagram-profile '{"username":"nike"}'
|
|
108
|
+
python -m cracked_ai get <runId> -w 30
|
|
109
|
+
python -m cracked_ai balance
|
|
110
|
+
python -m cracked_ai register my-agent
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
(`cracked-ai ...` works too once installed via pip.)
|
|
114
|
+
|
|
115
|
+
## Tests
|
|
116
|
+
|
|
117
|
+
`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).
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
CRACKED_API_KEY=ck_live_... CRACKED_BASE_URL=https://cracked.ai/v1 python -m pytest sdk/python/tests -q
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Publishing
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
cd sdk/python && python -m pip install build twine && python -m build && twine upload dist/*
|
|
127
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cracked-ai"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Zero-dependency Python client for Cracked, the agent tool router: discover, inspect, run and poll 50,000+ tools with one API key."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Cracked" }]
|
|
13
|
+
keywords = ["agents", "tools", "api", "llm", "scraping", "mcp", "cracked"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
20
|
+
"Topic :: Software Development :: Libraries",
|
|
21
|
+
]
|
|
22
|
+
dependencies = []
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://cracked.ai"
|
|
26
|
+
Documentation = "https://cracked.ai/docs/sdks"
|
|
27
|
+
"API reference" = "https://cracked.ai/docs/api"
|
|
28
|
+
|
|
29
|
+
[project.scripts]
|
|
30
|
+
cracked-ai = "cracked_ai.__main__:main"
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
test = ["pytest>=7"]
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.packages.find]
|
|
36
|
+
where = ["src"]
|
|
37
|
+
|
|
38
|
+
[tool.setuptools.package-data]
|
|
39
|
+
cracked_ai = ["py.typed"]
|
|
40
|
+
|
|
41
|
+
[tool.pytest.ini_options]
|
|
42
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""cracked_ai: zero-dependency Python client for Cracked, the agent tool router.
|
|
2
|
+
|
|
3
|
+
from cracked_ai import Cracked
|
|
4
|
+
c = Cracked() # CRACKED_API_KEY, CRACKED_BASE_URL
|
|
5
|
+
rec = c.run("open-meteo", "/forecast", {"latitude": 30.27, "longitude": -97.74})
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .client import (
|
|
9
|
+
DEFAULT_BASE_URL,
|
|
10
|
+
SERVER_MAX_POLL_WAIT,
|
|
11
|
+
TERMINAL_STATUSES,
|
|
12
|
+
AgentRegistration,
|
|
13
|
+
Cracked,
|
|
14
|
+
__version__,
|
|
15
|
+
is_terminal,
|
|
16
|
+
)
|
|
17
|
+
from .errors import CrackedError, CrackedTimeout
|
|
18
|
+
from .types import (
|
|
19
|
+
Balance,
|
|
20
|
+
Billing,
|
|
21
|
+
BountyClaim,
|
|
22
|
+
DiscoverResponse,
|
|
23
|
+
DiscoverResult,
|
|
24
|
+
FeedbackResult,
|
|
25
|
+
HealthSummary,
|
|
26
|
+
InspectResult,
|
|
27
|
+
Price,
|
|
28
|
+
ProviderResponse,
|
|
29
|
+
ReferralStats,
|
|
30
|
+
RegistrationResponse,
|
|
31
|
+
RouteAttempt,
|
|
32
|
+
RoutedTo,
|
|
33
|
+
RunList,
|
|
34
|
+
RunRecord,
|
|
35
|
+
RunStatus,
|
|
36
|
+
SmartRunRecord,
|
|
37
|
+
StopResult,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"Cracked",
|
|
42
|
+
"AgentRegistration",
|
|
43
|
+
"CrackedError",
|
|
44
|
+
"CrackedTimeout",
|
|
45
|
+
"DEFAULT_BASE_URL",
|
|
46
|
+
"SERVER_MAX_POLL_WAIT",
|
|
47
|
+
"TERMINAL_STATUSES",
|
|
48
|
+
"is_terminal",
|
|
49
|
+
"__version__",
|
|
50
|
+
"Balance",
|
|
51
|
+
"Billing",
|
|
52
|
+
"BountyClaim",
|
|
53
|
+
"DiscoverResponse",
|
|
54
|
+
"DiscoverResult",
|
|
55
|
+
"FeedbackResult",
|
|
56
|
+
"HealthSummary",
|
|
57
|
+
"InspectResult",
|
|
58
|
+
"Price",
|
|
59
|
+
"ProviderResponse",
|
|
60
|
+
"ReferralStats",
|
|
61
|
+
"RegistrationResponse",
|
|
62
|
+
"RouteAttempt",
|
|
63
|
+
"RoutedTo",
|
|
64
|
+
"RunList",
|
|
65
|
+
"RunRecord",
|
|
66
|
+
"RunStatus",
|
|
67
|
+
"SmartRunRecord",
|
|
68
|
+
"StopResult",
|
|
69
|
+
]
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Minimal CLI: ``python -m cracked_ai discover "instagram followers"``.
|
|
2
|
+
|
|
3
|
+
Commands: discover, inspect, run, capability, get, stop, balance, referral, register.
|
|
4
|
+
Set CRACKED_API_KEY (and optionally CRACKED_BASE_URL). Output is JSON.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import json
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
from .client import Cracked
|
|
14
|
+
from .errors import CrackedError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _json_arg(s: str | None) -> dict:
|
|
18
|
+
if not s:
|
|
19
|
+
return {}
|
|
20
|
+
if s.startswith("@"):
|
|
21
|
+
with open(s[1:], "r", encoding="utf-8") as f:
|
|
22
|
+
return json.load(f)
|
|
23
|
+
return json.loads(s)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def main(argv: list[str] | None = None) -> int:
|
|
27
|
+
p = argparse.ArgumentParser(prog="python -m cracked_ai", description="Cracked tool router CLI (JSON output)")
|
|
28
|
+
p.add_argument("--base-url", help="override CRACKED_BASE_URL")
|
|
29
|
+
p.add_argument("--api-key", help="override CRACKED_API_KEY")
|
|
30
|
+
sub = p.add_subparsers(dest="cmd", required=True)
|
|
31
|
+
|
|
32
|
+
d = sub.add_parser("discover", help="search tools in natural language")
|
|
33
|
+
d.add_argument("query", nargs="+")
|
|
34
|
+
d.add_argument("-l", "--limit", type=int, default=8)
|
|
35
|
+
d.add_argument("--live", action="store_true", help="only providers that run without your own key")
|
|
36
|
+
|
|
37
|
+
i = sub.add_parser("inspect", help="schema, price, health")
|
|
38
|
+
i.add_argument("provider")
|
|
39
|
+
i.add_argument("endpoint")
|
|
40
|
+
|
|
41
|
+
r = sub.add_parser("run", help="execute an endpoint")
|
|
42
|
+
r.add_argument("provider")
|
|
43
|
+
r.add_argument("endpoint")
|
|
44
|
+
r.add_argument("input", nargs="?", help='JSON object or @file.json')
|
|
45
|
+
r.add_argument("--no-wait", action="store_true", help="return the 202 immediately, do not poll")
|
|
46
|
+
r.add_argument("--timeout", type=float, default=300)
|
|
47
|
+
|
|
48
|
+
cap = sub.add_parser("capability", help="smart run by capability name, e.g. instagram-profile")
|
|
49
|
+
cap.add_argument("capability")
|
|
50
|
+
cap.add_argument("input", nargs="?", help='JSON object or @file.json')
|
|
51
|
+
cap.add_argument("--no-wait", action="store_true")
|
|
52
|
+
cap.add_argument("--timeout", type=float, default=300)
|
|
53
|
+
|
|
54
|
+
g = sub.add_parser("get", help="fetch a run")
|
|
55
|
+
g.add_argument("run_id")
|
|
56
|
+
g.add_argument("-w", "--wait", type=int, default=0, help="long-poll seconds (max 100)")
|
|
57
|
+
|
|
58
|
+
s = sub.add_parser("stop", help="abort a running job")
|
|
59
|
+
s.add_argument("run_id")
|
|
60
|
+
|
|
61
|
+
sub.add_parser("balance", help="wallet balance")
|
|
62
|
+
sub.add_parser("referral", help="referral link and earnings")
|
|
63
|
+
sub.add_parser("program", help="bounty program (no auth)")
|
|
64
|
+
|
|
65
|
+
reg = sub.add_parser("register", help="self-register an agent workspace and print the key once")
|
|
66
|
+
reg.add_argument("name", nargs="?")
|
|
67
|
+
reg.add_argument("--ref")
|
|
68
|
+
|
|
69
|
+
a = p.parse_args(argv)
|
|
70
|
+
try:
|
|
71
|
+
if a.cmd == "register":
|
|
72
|
+
out = Cracked.register_agent(a.name, a.ref, base_url=a.base_url).raw
|
|
73
|
+
else:
|
|
74
|
+
c = Cracked(api_key=a.api_key, base_url=a.base_url)
|
|
75
|
+
if a.cmd == "discover":
|
|
76
|
+
out = c.discover(" ".join(a.query), limit=a.limit, live_only=True if a.live else None)
|
|
77
|
+
elif a.cmd == "inspect":
|
|
78
|
+
out = c.inspect(a.provider, a.endpoint)
|
|
79
|
+
elif a.cmd == "run":
|
|
80
|
+
out = c.run(a.provider, a.endpoint, _json_arg(a.input), wait=not a.no_wait, timeout=a.timeout)
|
|
81
|
+
elif a.cmd == "capability":
|
|
82
|
+
out = c.run_capability(a.capability, _json_arg(a.input), wait=not a.no_wait, timeout=a.timeout)
|
|
83
|
+
elif a.cmd == "get":
|
|
84
|
+
out = c.get_run(a.run_id, wait=a.wait or None)
|
|
85
|
+
elif a.cmd == "stop":
|
|
86
|
+
out = c.stop(a.run_id)
|
|
87
|
+
elif a.cmd == "balance":
|
|
88
|
+
out = c.balance()
|
|
89
|
+
elif a.cmd == "referral":
|
|
90
|
+
out = c.referrals()
|
|
91
|
+
elif a.cmd == "program":
|
|
92
|
+
out = c.bounty_program()
|
|
93
|
+
else: # pragma: no cover
|
|
94
|
+
p.error("unknown command")
|
|
95
|
+
json.dump(out, sys.stdout, indent=2)
|
|
96
|
+
sys.stdout.write("\n")
|
|
97
|
+
return 0
|
|
98
|
+
except CrackedError as e:
|
|
99
|
+
json.dump({"error": e.code, "status": e.status, "message": e.message, **({"runId": e.run_id} if e.run_id else {})}, sys.stderr, indent=2)
|
|
100
|
+
sys.stderr.write("\n")
|
|
101
|
+
return 2
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__":
|
|
105
|
+
sys.exit(main())
|