pynukez 3.3.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.
- pynukez-3.3.0/PKG-INFO +234 -0
- pynukez-3.3.0/README.md +188 -0
- pynukez-3.3.0/pynukez/__init__.py +1328 -0
- pynukez-3.3.0/pynukez/__main__.py +5 -0
- pynukez-3.3.0/pynukez/_async_client.py +2797 -0
- pynukez-3.3.0/pynukez/_async_http.py +147 -0
- pynukez-3.3.0/pynukez/_helpers.py +366 -0
- pynukez-3.3.0/pynukez/_http.py +520 -0
- pynukez-3.3.0/pynukez/auth.py +445 -0
- pynukez-3.3.0/pynukez/cli.py +89 -0
- pynukez-3.3.0/pynukez/client.py +3534 -0
- pynukez-3.3.0/pynukez/discovery.py +118 -0
- pynukez-3.3.0/pynukez/errors.py +518 -0
- pynukez-3.3.0/pynukez/evm_payment.py +593 -0
- pynukez-3.3.0/pynukez/hardening.py +292 -0
- pynukez-3.3.0/pynukez/payment.py +289 -0
- pynukez-3.3.0/pynukez/py.typed +2 -0
- pynukez-3.3.0/pynukez/signer.py +154 -0
- pynukez-3.3.0/pynukez/types.py +417 -0
- pynukez-3.3.0/pynukez.egg-info/PKG-INFO +234 -0
- pynukez-3.3.0/pynukez.egg-info/SOURCES.txt +37 -0
- pynukez-3.3.0/pynukez.egg-info/dependency_links.txt +1 -0
- pynukez-3.3.0/pynukez.egg-info/entry_points.txt +2 -0
- pynukez-3.3.0/pynukez.egg-info/requires.txt +24 -0
- pynukez-3.3.0/pynukez.egg-info/top_level.txt +1 -0
- pynukez-3.3.0/pyproject.toml +122 -0
- pynukez-3.3.0/setup.cfg +4 -0
- pynukez-3.3.0/tests/test_async_client.py +320 -0
- pynukez-3.3.0/tests/test_async_http_client.py +154 -0
- pynukez-3.3.0/tests/test_auth.py +447 -0
- pynukez-3.3.0/tests/test_client_methods.py +562 -0
- pynukez-3.3.0/tests/test_errors.py +89 -0
- pynukez-3.3.0/tests/test_evm_utils.py +33 -0
- pynukez-3.3.0/tests/test_hardening.py +52 -0
- pynukez-3.3.0/tests/test_http_client.py +230 -0
- pynukez-3.3.0/tests/test_operator_delegation.py +616 -0
- pynukez-3.3.0/tests/test_payment_utils.py +39 -0
- pynukez-3.3.0/tests/test_signer.py +164 -0
- pynukez-3.3.0/tests/test_types.py +150 -0
pynukez-3.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pynukez
|
|
3
|
+
Version: 3.3.0
|
|
4
|
+
Summary: Nukez SDK — agent-native storage with cryptographic verification
|
|
5
|
+
Author-email: Nukez <dev@nukez.xyz>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/nukez/pynukez
|
|
8
|
+
Project-URL: Documentation, https://docs.nukez.xyz/sdk/python
|
|
9
|
+
Project-URL: Repository, https://github.com/nukez/pynukez
|
|
10
|
+
Project-URL: Issues, https://github.com/nukez/pynukez/issues
|
|
11
|
+
Keywords: ai,agent,autonomous,storage,solana,blockchain,cryptographic,verification,llm,tool-calling
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
Requires-Dist: httpx>=0.24.0
|
|
27
|
+
Requires-Dist: pynacl>=1.5.0
|
|
28
|
+
Requires-Dist: base58>=2.1.0
|
|
29
|
+
Provides-Extra: solana
|
|
30
|
+
Requires-Dist: solana>=0.30.0; extra == "solana"
|
|
31
|
+
Requires-Dist: solders>=0.18.0; extra == "solana"
|
|
32
|
+
Provides-Extra: evm
|
|
33
|
+
Requires-Dist: web3>=6.0.0; extra == "evm"
|
|
34
|
+
Requires-Dist: eth-account>=0.10.0; extra == "evm"
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
37
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
38
|
+
Requires-Dist: pytest-mock>=3.10.0; extra == "dev"
|
|
39
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
40
|
+
Requires-Dist: isort>=5.12.0; extra == "dev"
|
|
41
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
42
|
+
Requires-Dist: types-requests>=2.28.0; extra == "dev"
|
|
43
|
+
Requires-Dist: python-dotenv>=1.0.0; extra == "dev"
|
|
44
|
+
Provides-Extra: all
|
|
45
|
+
Requires-Dist: pynukez[dev,evm,solana]; extra == "all"
|
|
46
|
+
|
|
47
|
+
# PyNukez
|
|
48
|
+
|
|
49
|
+
**Persistent storage for AI agents. Pay with SOL, store anything, get cryptographic proof.**
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install pynukez[solana]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 30-Second Example
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from pynukez import Nukez
|
|
59
|
+
|
|
60
|
+
client = Nukez(keypair_path="~/.config/solana/id.json")
|
|
61
|
+
|
|
62
|
+
# Buy storage
|
|
63
|
+
request = client.request_storage()
|
|
64
|
+
transfer = client.solana_transfer(request.pay_to_address, request.amount_sol)
|
|
65
|
+
receipt = client.confirm_storage(request.pay_req_id, transfer.signature)
|
|
66
|
+
|
|
67
|
+
# Use it
|
|
68
|
+
client.provision_locker(receipt.id)
|
|
69
|
+
urls = client.create_file(receipt.id, "notes.txt")
|
|
70
|
+
client.upload_bytes(urls.upload_url, b"Hello!")
|
|
71
|
+
data = client.download_bytes(urls.download_url) # b"Hello!"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**That's it.** Your agent now has permanent storage with a cryptographic receipt.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## First Time? Start Here
|
|
79
|
+
|
|
80
|
+
### 1. Get a Solana Wallet
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Install Solana CLI
|
|
84
|
+
sh -c "$(curl -sSfL https://release.solana.com/stable/install)"
|
|
85
|
+
|
|
86
|
+
# Create wallet
|
|
87
|
+
solana-keygen new --outfile ~/.config/solana/id.json
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 2. Get Test Money (Free)
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
solana config set --url devnet
|
|
94
|
+
solana airdrop 2
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 3. Install PyNukez
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
pip install pynukez[solana]
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 4. Store Something
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from pynukez import Nukez
|
|
107
|
+
|
|
108
|
+
client = Nukez(keypair_path="~/.config/solana/id.json")
|
|
109
|
+
|
|
110
|
+
# Buy storage (costs ~0.001 SOL on devnet)
|
|
111
|
+
request = client.request_storage()
|
|
112
|
+
transfer = client.solana_transfer(request.pay_to_address, request.amount_sol)
|
|
113
|
+
receipt = client.confirm_storage(request.pay_req_id, transfer.signature)
|
|
114
|
+
|
|
115
|
+
print(f"Your receipt: {receipt.id}") # Save this!
|
|
116
|
+
|
|
117
|
+
# Create your locker
|
|
118
|
+
client.provision_locker(receipt.id)
|
|
119
|
+
|
|
120
|
+
# Store a file
|
|
121
|
+
urls = client.create_file(receipt.id, "my_file.txt")
|
|
122
|
+
client.upload_bytes(urls.upload_url, b"My agent's data")
|
|
123
|
+
|
|
124
|
+
# Read it back
|
|
125
|
+
data = client.download_bytes(urls.download_url)
|
|
126
|
+
print(data) # b"My agent's data"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Quick Reference
|
|
132
|
+
|
|
133
|
+
| What you want | Code |
|
|
134
|
+
|--------------|------|
|
|
135
|
+
| Buy storage | `request = client.request_storage()` |
|
|
136
|
+
| Pay | `transfer = client.solana_transfer(request.pay_to_address, request.amount_sol)` |
|
|
137
|
+
| Get receipt | `receipt = client.confirm_storage(request.pay_req_id, transfer.signature)` |
|
|
138
|
+
| Setup locker | `client.provision_locker(receipt.id)` |
|
|
139
|
+
| Store data | `urls = client.create_file(receipt.id, "file.txt")` then `client.upload_bytes(urls.upload_url, data)` |
|
|
140
|
+
| Get data | `data = client.download_bytes(urls.download_url)` |
|
|
141
|
+
| List files | `files = client.list_files(receipt.id)` |
|
|
142
|
+
| Delete file | `client.delete_file(receipt.id, "file.txt")` |
|
|
143
|
+
| Verify | `result = client.verify_storage(receipt.id)` |
|
|
144
|
+
| Merkle proof | `proof = client.get_merkle_proof(receipt.id, "file.txt")` |
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Sandboxed App Uploads
|
|
149
|
+
|
|
150
|
+
If your agent runs in a proxied app sandbox (for example, `/mnt/data` path restrictions), path uploads can fail even when locker auth is valid.
|
|
151
|
+
|
|
152
|
+
Use the sandbox ingest flow instead:
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
job = client.sandbox_create_ingest_job(
|
|
156
|
+
receipt_id=receipt.id,
|
|
157
|
+
files=[{"filename": "image.png", "content_type": "image/png"}],
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
client.sandbox_append_ingest_part(
|
|
161
|
+
receipt_id=receipt.id,
|
|
162
|
+
job_id=job["job_id"],
|
|
163
|
+
file_id=job["files"][0]["file_id"],
|
|
164
|
+
part_no=0,
|
|
165
|
+
payload_b64="<chunk-0-base64>",
|
|
166
|
+
is_last=True,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
result = client.sandbox_complete_ingest_job(
|
|
170
|
+
receipt_id=receipt.id,
|
|
171
|
+
job_id=job["job_id"],
|
|
172
|
+
)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Convenience helpers are available:
|
|
176
|
+
- `client.sandbox_upload_bytes(...)`
|
|
177
|
+
- `client.sandbox_upload_base64(...)`
|
|
178
|
+
- `client.sandbox_upload_file_path(...)`
|
|
179
|
+
|
|
180
|
+
Important: if a valid `receipt_id` already exists, reuse it. Do not purchase storage again unless explicitly requested.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Important
|
|
185
|
+
|
|
186
|
+
**Save your `receipt.id`** — you need it for everything.
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
# First time
|
|
190
|
+
receipt = client.confirm_storage(...)
|
|
191
|
+
print(receipt.id) # Save this string somewhere!
|
|
192
|
+
|
|
193
|
+
# Later
|
|
194
|
+
files = client.list_files("your_saved_receipt_id")
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Going to Production
|
|
200
|
+
|
|
201
|
+
Change one line:
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
# Devnet (testing)
|
|
205
|
+
client = Nukez(keypair_path="~/.config/solana/id.json", network="devnet")
|
|
206
|
+
|
|
207
|
+
# Mainnet (production)
|
|
208
|
+
client = Nukez(keypair_path="~/.config/solana/id.json", network="mainnet-beta")
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Common Issues
|
|
214
|
+
|
|
215
|
+
| Problem | Fix |
|
|
216
|
+
|---------|-----|
|
|
217
|
+
| "Transaction not found" | Wait 3 seconds and retry `confirm_storage()` |
|
|
218
|
+
| "Insufficient funds" | Run `solana airdrop 2` (devnet only) |
|
|
219
|
+
| "URL expired" | Call `client.get_file_urls(receipt_id, filename)` for fresh URLs |
|
|
220
|
+
| "File not found" | Check `client.list_files(receipt_id)` to see what exists |
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Links
|
|
225
|
+
|
|
226
|
+
- [Examples](./examples/) — Working code you can copy
|
|
227
|
+
- [API Reference](./docs/API.md) — Every method explained
|
|
228
|
+
- [Source Code](./pynukez/) — Read it yourself
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
MIT
|
pynukez-3.3.0/README.md
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# PyNukez
|
|
2
|
+
|
|
3
|
+
**Persistent storage for AI agents. Pay with SOL, store anything, get cryptographic proof.**
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install pynukez[solana]
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## 30-Second Example
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from pynukez import Nukez
|
|
13
|
+
|
|
14
|
+
client = Nukez(keypair_path="~/.config/solana/id.json")
|
|
15
|
+
|
|
16
|
+
# Buy storage
|
|
17
|
+
request = client.request_storage()
|
|
18
|
+
transfer = client.solana_transfer(request.pay_to_address, request.amount_sol)
|
|
19
|
+
receipt = client.confirm_storage(request.pay_req_id, transfer.signature)
|
|
20
|
+
|
|
21
|
+
# Use it
|
|
22
|
+
client.provision_locker(receipt.id)
|
|
23
|
+
urls = client.create_file(receipt.id, "notes.txt")
|
|
24
|
+
client.upload_bytes(urls.upload_url, b"Hello!")
|
|
25
|
+
data = client.download_bytes(urls.download_url) # b"Hello!"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**That's it.** Your agent now has permanent storage with a cryptographic receipt.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## First Time? Start Here
|
|
33
|
+
|
|
34
|
+
### 1. Get a Solana Wallet
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Install Solana CLI
|
|
38
|
+
sh -c "$(curl -sSfL https://release.solana.com/stable/install)"
|
|
39
|
+
|
|
40
|
+
# Create wallet
|
|
41
|
+
solana-keygen new --outfile ~/.config/solana/id.json
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 2. Get Test Money (Free)
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
solana config set --url devnet
|
|
48
|
+
solana airdrop 2
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 3. Install PyNukez
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install pynukez[solana]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 4. Store Something
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from pynukez import Nukez
|
|
61
|
+
|
|
62
|
+
client = Nukez(keypair_path="~/.config/solana/id.json")
|
|
63
|
+
|
|
64
|
+
# Buy storage (costs ~0.001 SOL on devnet)
|
|
65
|
+
request = client.request_storage()
|
|
66
|
+
transfer = client.solana_transfer(request.pay_to_address, request.amount_sol)
|
|
67
|
+
receipt = client.confirm_storage(request.pay_req_id, transfer.signature)
|
|
68
|
+
|
|
69
|
+
print(f"Your receipt: {receipt.id}") # Save this!
|
|
70
|
+
|
|
71
|
+
# Create your locker
|
|
72
|
+
client.provision_locker(receipt.id)
|
|
73
|
+
|
|
74
|
+
# Store a file
|
|
75
|
+
urls = client.create_file(receipt.id, "my_file.txt")
|
|
76
|
+
client.upload_bytes(urls.upload_url, b"My agent's data")
|
|
77
|
+
|
|
78
|
+
# Read it back
|
|
79
|
+
data = client.download_bytes(urls.download_url)
|
|
80
|
+
print(data) # b"My agent's data"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Quick Reference
|
|
86
|
+
|
|
87
|
+
| What you want | Code |
|
|
88
|
+
|--------------|------|
|
|
89
|
+
| Buy storage | `request = client.request_storage()` |
|
|
90
|
+
| Pay | `transfer = client.solana_transfer(request.pay_to_address, request.amount_sol)` |
|
|
91
|
+
| Get receipt | `receipt = client.confirm_storage(request.pay_req_id, transfer.signature)` |
|
|
92
|
+
| Setup locker | `client.provision_locker(receipt.id)` |
|
|
93
|
+
| Store data | `urls = client.create_file(receipt.id, "file.txt")` then `client.upload_bytes(urls.upload_url, data)` |
|
|
94
|
+
| Get data | `data = client.download_bytes(urls.download_url)` |
|
|
95
|
+
| List files | `files = client.list_files(receipt.id)` |
|
|
96
|
+
| Delete file | `client.delete_file(receipt.id, "file.txt")` |
|
|
97
|
+
| Verify | `result = client.verify_storage(receipt.id)` |
|
|
98
|
+
| Merkle proof | `proof = client.get_merkle_proof(receipt.id, "file.txt")` |
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Sandboxed App Uploads
|
|
103
|
+
|
|
104
|
+
If your agent runs in a proxied app sandbox (for example, `/mnt/data` path restrictions), path uploads can fail even when locker auth is valid.
|
|
105
|
+
|
|
106
|
+
Use the sandbox ingest flow instead:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
job = client.sandbox_create_ingest_job(
|
|
110
|
+
receipt_id=receipt.id,
|
|
111
|
+
files=[{"filename": "image.png", "content_type": "image/png"}],
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
client.sandbox_append_ingest_part(
|
|
115
|
+
receipt_id=receipt.id,
|
|
116
|
+
job_id=job["job_id"],
|
|
117
|
+
file_id=job["files"][0]["file_id"],
|
|
118
|
+
part_no=0,
|
|
119
|
+
payload_b64="<chunk-0-base64>",
|
|
120
|
+
is_last=True,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
result = client.sandbox_complete_ingest_job(
|
|
124
|
+
receipt_id=receipt.id,
|
|
125
|
+
job_id=job["job_id"],
|
|
126
|
+
)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Convenience helpers are available:
|
|
130
|
+
- `client.sandbox_upload_bytes(...)`
|
|
131
|
+
- `client.sandbox_upload_base64(...)`
|
|
132
|
+
- `client.sandbox_upload_file_path(...)`
|
|
133
|
+
|
|
134
|
+
Important: if a valid `receipt_id` already exists, reuse it. Do not purchase storage again unless explicitly requested.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Important
|
|
139
|
+
|
|
140
|
+
**Save your `receipt.id`** — you need it for everything.
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
# First time
|
|
144
|
+
receipt = client.confirm_storage(...)
|
|
145
|
+
print(receipt.id) # Save this string somewhere!
|
|
146
|
+
|
|
147
|
+
# Later
|
|
148
|
+
files = client.list_files("your_saved_receipt_id")
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Going to Production
|
|
154
|
+
|
|
155
|
+
Change one line:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
# Devnet (testing)
|
|
159
|
+
client = Nukez(keypair_path="~/.config/solana/id.json", network="devnet")
|
|
160
|
+
|
|
161
|
+
# Mainnet (production)
|
|
162
|
+
client = Nukez(keypair_path="~/.config/solana/id.json", network="mainnet-beta")
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Common Issues
|
|
168
|
+
|
|
169
|
+
| Problem | Fix |
|
|
170
|
+
|---------|-----|
|
|
171
|
+
| "Transaction not found" | Wait 3 seconds and retry `confirm_storage()` |
|
|
172
|
+
| "Insufficient funds" | Run `solana airdrop 2` (devnet only) |
|
|
173
|
+
| "URL expired" | Call `client.get_file_urls(receipt_id, filename)` for fresh URLs |
|
|
174
|
+
| "File not found" | Check `client.list_files(receipt_id)` to see what exists |
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Links
|
|
179
|
+
|
|
180
|
+
- [Examples](./examples/) — Working code you can copy
|
|
181
|
+
- [API Reference](./docs/API.md) — Every method explained
|
|
182
|
+
- [Source Code](./pynukez/) — Read it yourself
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
MIT
|