hax-sdk 0.2.4rc6__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.
@@ -0,0 +1,68 @@
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ node_modules
5
+ /.pnp
6
+ .pnp.*
7
+ .yarn/*
8
+ !.yarn/patches
9
+ !.yarn/plugins
10
+ !.yarn/releases
11
+ !.yarn/versions
12
+
13
+ # testing
14
+ /coverage
15
+
16
+ # next.js
17
+ /.next/
18
+ /out/
19
+
20
+ # production
21
+ /build
22
+
23
+ # misc
24
+ .DS_Store
25
+ *.pem
26
+
27
+ # debug
28
+ npm-debug.log*
29
+ yarn-debug.log*
30
+ yarn-error.log*
31
+ .pnpm-debug.log*
32
+
33
+ # env files (can opt-in for committing if needed)
34
+ .env
35
+
36
+ # vercel
37
+ .vercel
38
+
39
+ # typescript
40
+ *.tsbuildinfo
41
+ next-env.d.ts
42
+
43
+ # clerk configuration (can include secrets)
44
+ /.clerk/
45
+
46
+ # python
47
+ __pycache__/
48
+
49
+ # pipeline artifacts
50
+ .artifacts/
51
+
52
+ # build artifacts
53
+ server-bundle.cjs
54
+
55
+ # worktrees
56
+ .worktrees/
57
+
58
+ # python (additional)
59
+ *.pyc
60
+ *.pyo
61
+ *.pyd
62
+ .venv/
63
+ venv/
64
+ *.egg-info/
65
+ .pytest_cache/
66
+ .mypy_cache/
67
+ .ruff_cache/
68
+ .tox/
@@ -0,0 +1,311 @@
1
+ Metadata-Version: 2.4
2
+ Name: hax-sdk
3
+ Version: 0.2.4rc6
4
+ Summary: Python SDK for the HAX (Human Approval eXchange) API
5
+ Project-URL: Homepage, https://github.com/Agent-Field/hax-sdk
6
+ Project-URL: Documentation, https://github.com/Agent-Field/hax-sdk
7
+ Project-URL: Repository, https://github.com/Agent-Field/hax-sdk
8
+ Author: HAX Team
9
+ License-Expression: MIT
10
+ Keywords: api,approval,forms,hax,human-in-the-loop,sdk
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.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.9
21
+ Requires-Dist: cryptography>=41.0.0
22
+ Requires-Dist: httpx>=0.25.0
23
+ Requires-Dist: pydantic>=2.0.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: mypy>=1.0.0; extra == 'dev'
26
+ Requires-Dist: pytest-httpx>=0.30.0; extra == 'dev'
27
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
28
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # HAX Python SDK
32
+
33
+ Python client for the HAX (Human Approval eXchange) API. Enables agents and automated systems to programmatically collect human input.
34
+
35
+ ## Installation
36
+
37
+ The Python SDK is not yet published to PyPI. Install from source:
38
+
39
+ ```bash
40
+ pip install -e sdks/python # from the repo root
41
+ # or
42
+ pip install -e . # from sdks/python/
43
+ ```
44
+
45
+ ## Quick Start
46
+
47
+ ```python
48
+ from hax import HaxClient
49
+
50
+ client = HaxClient(
51
+ api_key="hax_live_...",
52
+ base_url="http://localhost:3000/api/v1",
53
+ )
54
+
55
+ request = client.create_request(
56
+ type="text-approval-v1",
57
+ payload={"text": "Deploy main to prod?", "approveLabel": "Ship it", "denyLabel": "Hold"},
58
+ webhook_url="https://myapp.com/webhook",
59
+ )
60
+
61
+ print("Share with approver:", request.url)
62
+
63
+ # Poll until completed/expired/cancelled
64
+ request = client.wait_for_response(request.id, timeout=300)
65
+ if request.is_completed:
66
+ print("Decision:", request.response.get("decision"))
67
+ ```
68
+
69
+ ## Features
70
+
71
+ - **Pydantic models**: Typed request/response models with validation
72
+ - **FormBuilder**: Fluent API for building typed forms with runtime type inference
73
+ - **E2E encryption**: RSA-OAEP + AES-GCM hybrid encryption for sensitive responses
74
+ - **Webhook verification**: HMAC-SHA256 signature verification
75
+ - **Delivery**: Send requests via email or SMS
76
+ - **Polling**: Built-in `wait_for_response` with configurable timeout
77
+ - **Error handling**: Typed exception hierarchy
78
+
79
+ ## Request Methods
80
+
81
+ ```python
82
+ # Create a request
83
+ request = client.create_request(
84
+ type="text-approval-v1",
85
+ payload={"text": "Approve this action?"},
86
+ title="Optional title",
87
+ description="Optional description",
88
+ webhook_url="https://myapp.com/webhook",
89
+ expires_in_seconds=3600,
90
+ metadata={"pr_number": 123},
91
+ )
92
+
93
+ # Send via email
94
+ request = client.request_via_email(
95
+ type="confirm-action-v1",
96
+ payload={"title": "Approve?", "confirmPhrase": "YES"},
97
+ to_email="approver@example.com",
98
+ subject="Approval Required",
99
+ )
100
+
101
+ # Send via SMS
102
+ request = client.request_via_sms(
103
+ type="text-approval-v1",
104
+ payload={"text": "Approve?"},
105
+ to_phone="+15551234567",
106
+ )
107
+
108
+ # Get a request by ID
109
+ request = client.get_request("req_123")
110
+
111
+ # List recent requests
112
+ requests = client.list_requests()
113
+
114
+ # Cancel a pending request
115
+ cancelled = client.cancel_request("req_123")
116
+
117
+ # Submit a response (for testing)
118
+ completed = client.submit_response("req_123", {"decision": "approve"})
119
+
120
+ # Wait for completion with timeout
121
+ result = client.wait_for_response("req_123", poll_interval=2.0, timeout=60)
122
+
123
+ # List available template types
124
+ types = client.list_types()
125
+ ```
126
+
127
+ ### Status Helpers
128
+
129
+ ```python
130
+ if request.is_pending:
131
+ print("Waiting for response...")
132
+ if request.is_completed:
133
+ print("Response:", request.response)
134
+ if request.is_expired:
135
+ print("Request expired")
136
+ if request.is_cancelled:
137
+ print("Request was cancelled")
138
+ ```
139
+
140
+ ## FormBuilder
141
+
142
+ Build typed forms with a fluent API:
143
+
144
+ ```python
145
+ from hax import HaxClient, FormBuilder
146
+
147
+ client = HaxClient(api_key="hax_live_...")
148
+
149
+ form = (FormBuilder()
150
+ .title("Event Registration")
151
+ .input("name", label="Full Name", required=True)
152
+ .input("email", label="Email", variant="email", required=True)
153
+ .number("age", label="Age", min=0, max=120)
154
+ .checkbox("newsletter", checkbox_label="Subscribe to newsletter"))
155
+
156
+ handle = client.create_form_request(form,
157
+ webhook_url="https://myapp.com/webhook")
158
+
159
+ print(f"Form URL: {handle.url}")
160
+
161
+ # Wait for typed response
162
+ response = handle.wait_for_response(timeout=300)
163
+ print(response.values.name) # str
164
+ print(response.values.email) # str
165
+ print(response.values.age) # float
166
+ print(response.values.newsletter) # bool
167
+ ```
168
+
169
+ ### Available Field Types
170
+
171
+ | Method | Output Type | Description |
172
+ |--------|------------|-------------|
173
+ | `.input(id)` | `str` | Text input (variants: text, email, url, tel) |
174
+ | `.textarea(id)` | `str` | Multi-line text input |
175
+ | `.select(id, options=...)` | `str` | Dropdown select |
176
+ | `.radio_group(id, options=...)` | `str` | Radio button group |
177
+ | `.date(id)` | `str` | Date picker (ISO format) |
178
+ | `.number(id)` | `float` | Numeric input |
179
+ | `.slider(id, min=, max=)` | `float` | Slider control |
180
+ | `.checkbox(id)` | `bool` | Single checkbox |
181
+ | `.switch(id)` | `bool` | Toggle switch |
182
+ | `.checkbox_group(id, options=...)` | `list[str]` | Multi-select checkboxes |
183
+ | `.hidden(id, value)` | `type(value)` | Hidden field |
184
+
185
+ ## Webhooks
186
+
187
+ Verify and parse webhook events:
188
+
189
+ ```python
190
+ from hax import verify_signature, parse_event
191
+
192
+ # In your webhook handler
193
+ def handle_webhook(request):
194
+ # Verify signature
195
+ is_valid = verify_signature(
196
+ payload=request.body,
197
+ signature=request.headers["X-Hax-Signature"],
198
+ secret="whsec_...",
199
+ )
200
+ if not is_valid:
201
+ return 400, "Invalid signature"
202
+
203
+ # Parse the event
204
+ event = parse_event(request.body)
205
+
206
+ if event.event_type == "completed":
207
+ print(f"Request {event.request_id} completed!")
208
+ print(f"Response: {event.response}")
209
+ elif event.event_type == "expired":
210
+ print(f"Request {event.request_id} expired")
211
+
212
+ return 200, "OK"
213
+ ```
214
+
215
+ ### Event Types
216
+
217
+ - `request.sent` — Notification was delivered (email/SMS)
218
+ - `request.opened` — Human opened the request link
219
+ - `request.completed` — Human submitted a response
220
+ - `request.expired` — Request expired without action
221
+
222
+ ## Encryption
223
+
224
+ For sensitive response data, use end-to-end encryption:
225
+
226
+ ```python
227
+ from hax import HaxClient
228
+
229
+ # Passphrase-based (automatic encrypt/decrypt)
230
+ client = HaxClient(
231
+ api_key="hax_live_...",
232
+ encryption_key="my-secret-passphrase",
233
+ )
234
+
235
+ # Public key is automatically sent with requests
236
+ request = client.create_request(
237
+ type="text-approval-v1",
238
+ payload={"text": "Approve this sensitive action?"},
239
+ )
240
+
241
+ # Response is automatically decrypted when retrieved
242
+ completed = client.get_request(request.id)
243
+ print(completed.response) # Decrypted plaintext
244
+ ```
245
+
246
+ ### Manual Decryption
247
+
248
+ ```python
249
+ from hax import generate_key_pair, decrypt_response, is_encrypted_response
250
+
251
+ public_key, private_key = generate_key_pair("my-secret")
252
+
253
+ # Use public_key when creating the client
254
+ client = HaxClient(api_key="...", public_key=public_key)
255
+
256
+ # Later, manually decrypt
257
+ request = client.get_request("req_123")
258
+ if is_encrypted_response(request.response):
259
+ decrypted = decrypt_response(request.response["_encrypted"], private_key)
260
+ ```
261
+
262
+ ## Error Handling
263
+
264
+ ```python
265
+ from hax import (
266
+ HaxError, # Base error
267
+ AuthenticationError, # Invalid API key (401)
268
+ ValidationError, # Invalid request data (400/422)
269
+ NotFoundError, # Resource not found (404)
270
+ RateLimitError, # Too many requests (429)
271
+ ServerError, # Server error (500+)
272
+ DecryptionError, # Decryption failure
273
+ )
274
+
275
+ try:
276
+ request = client.create_request(...)
277
+ except AuthenticationError:
278
+ print("Check your API key")
279
+ except ValidationError as e:
280
+ print(f"Invalid request: {e}")
281
+ except RateLimitError:
282
+ print("Rate limited, try again later")
283
+ except HaxError as e:
284
+ print(f"API error: {e}")
285
+ ```
286
+
287
+ ## Template Types
288
+
289
+ | Template | Description |
290
+ |----------|-------------|
291
+ | `text-approval-v1` | Show text and collect an approve/deny decision |
292
+ | `confirm-action-v1` | Require typing a specific phrase to confirm a destructive action |
293
+ | `collect-email-v1` | Prompt the user for an email address |
294
+ | `form-builder` | Advanced forms with field types, layouts, validation, and conditional logic |
295
+ | `multi-choice-selection-v1` | Single or multiple selection from customizable option cards |
296
+ | `code-changes-v1` | GitHub-style diff view with inline line comments |
297
+ | `rich-text-editor-v1` | Markdown-formatted text editing for documents and reports |
298
+ | `file-upload-v1` | Collect files (documents, images, CSVs) from users |
299
+ | `signature-capture-v1` | Capture e-signatures with optional signer name and legal text |
300
+ | `data-table-review-v1` | Review, select, or edit tabular data |
301
+ | `scheduling-picker-v1` | Date/time slot selection with optional recurring schedules |
302
+ | `multi-step-wizard-v1` | Sequential steps with navigation and progress indicator |
303
+ | `side-by-side-comparison-v1` | Compare two versions with diff highlighting |
304
+ | `terminal-output-v1` | Display command output/logs with approve-to-continue |
305
+
306
+ ## Notes
307
+
308
+ - Auth is **API key only**. Provide the key via `HaxClient(api_key=...)`; Clerk/session auth is not required for API access.
309
+ - API responses wrap resources (e.g., `{"request": {...}}`); the SDK unwraps this automatically.
310
+ - Template payloads and responses are flexible; consult the template configs for the fields each template expects/returns.
311
+ - The client supports context manager usage: `with HaxClient(...) as client:`
@@ -0,0 +1,281 @@
1
+ # HAX Python SDK
2
+
3
+ Python client for the HAX (Human Approval eXchange) API. Enables agents and automated systems to programmatically collect human input.
4
+
5
+ ## Installation
6
+
7
+ The Python SDK is not yet published to PyPI. Install from source:
8
+
9
+ ```bash
10
+ pip install -e sdks/python # from the repo root
11
+ # or
12
+ pip install -e . # from sdks/python/
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```python
18
+ from hax import HaxClient
19
+
20
+ client = HaxClient(
21
+ api_key="hax_live_...",
22
+ base_url="http://localhost:3000/api/v1",
23
+ )
24
+
25
+ request = client.create_request(
26
+ type="text-approval-v1",
27
+ payload={"text": "Deploy main to prod?", "approveLabel": "Ship it", "denyLabel": "Hold"},
28
+ webhook_url="https://myapp.com/webhook",
29
+ )
30
+
31
+ print("Share with approver:", request.url)
32
+
33
+ # Poll until completed/expired/cancelled
34
+ request = client.wait_for_response(request.id, timeout=300)
35
+ if request.is_completed:
36
+ print("Decision:", request.response.get("decision"))
37
+ ```
38
+
39
+ ## Features
40
+
41
+ - **Pydantic models**: Typed request/response models with validation
42
+ - **FormBuilder**: Fluent API for building typed forms with runtime type inference
43
+ - **E2E encryption**: RSA-OAEP + AES-GCM hybrid encryption for sensitive responses
44
+ - **Webhook verification**: HMAC-SHA256 signature verification
45
+ - **Delivery**: Send requests via email or SMS
46
+ - **Polling**: Built-in `wait_for_response` with configurable timeout
47
+ - **Error handling**: Typed exception hierarchy
48
+
49
+ ## Request Methods
50
+
51
+ ```python
52
+ # Create a request
53
+ request = client.create_request(
54
+ type="text-approval-v1",
55
+ payload={"text": "Approve this action?"},
56
+ title="Optional title",
57
+ description="Optional description",
58
+ webhook_url="https://myapp.com/webhook",
59
+ expires_in_seconds=3600,
60
+ metadata={"pr_number": 123},
61
+ )
62
+
63
+ # Send via email
64
+ request = client.request_via_email(
65
+ type="confirm-action-v1",
66
+ payload={"title": "Approve?", "confirmPhrase": "YES"},
67
+ to_email="approver@example.com",
68
+ subject="Approval Required",
69
+ )
70
+
71
+ # Send via SMS
72
+ request = client.request_via_sms(
73
+ type="text-approval-v1",
74
+ payload={"text": "Approve?"},
75
+ to_phone="+15551234567",
76
+ )
77
+
78
+ # Get a request by ID
79
+ request = client.get_request("req_123")
80
+
81
+ # List recent requests
82
+ requests = client.list_requests()
83
+
84
+ # Cancel a pending request
85
+ cancelled = client.cancel_request("req_123")
86
+
87
+ # Submit a response (for testing)
88
+ completed = client.submit_response("req_123", {"decision": "approve"})
89
+
90
+ # Wait for completion with timeout
91
+ result = client.wait_for_response("req_123", poll_interval=2.0, timeout=60)
92
+
93
+ # List available template types
94
+ types = client.list_types()
95
+ ```
96
+
97
+ ### Status Helpers
98
+
99
+ ```python
100
+ if request.is_pending:
101
+ print("Waiting for response...")
102
+ if request.is_completed:
103
+ print("Response:", request.response)
104
+ if request.is_expired:
105
+ print("Request expired")
106
+ if request.is_cancelled:
107
+ print("Request was cancelled")
108
+ ```
109
+
110
+ ## FormBuilder
111
+
112
+ Build typed forms with a fluent API:
113
+
114
+ ```python
115
+ from hax import HaxClient, FormBuilder
116
+
117
+ client = HaxClient(api_key="hax_live_...")
118
+
119
+ form = (FormBuilder()
120
+ .title("Event Registration")
121
+ .input("name", label="Full Name", required=True)
122
+ .input("email", label="Email", variant="email", required=True)
123
+ .number("age", label="Age", min=0, max=120)
124
+ .checkbox("newsletter", checkbox_label="Subscribe to newsletter"))
125
+
126
+ handle = client.create_form_request(form,
127
+ webhook_url="https://myapp.com/webhook")
128
+
129
+ print(f"Form URL: {handle.url}")
130
+
131
+ # Wait for typed response
132
+ response = handle.wait_for_response(timeout=300)
133
+ print(response.values.name) # str
134
+ print(response.values.email) # str
135
+ print(response.values.age) # float
136
+ print(response.values.newsletter) # bool
137
+ ```
138
+
139
+ ### Available Field Types
140
+
141
+ | Method | Output Type | Description |
142
+ |--------|------------|-------------|
143
+ | `.input(id)` | `str` | Text input (variants: text, email, url, tel) |
144
+ | `.textarea(id)` | `str` | Multi-line text input |
145
+ | `.select(id, options=...)` | `str` | Dropdown select |
146
+ | `.radio_group(id, options=...)` | `str` | Radio button group |
147
+ | `.date(id)` | `str` | Date picker (ISO format) |
148
+ | `.number(id)` | `float` | Numeric input |
149
+ | `.slider(id, min=, max=)` | `float` | Slider control |
150
+ | `.checkbox(id)` | `bool` | Single checkbox |
151
+ | `.switch(id)` | `bool` | Toggle switch |
152
+ | `.checkbox_group(id, options=...)` | `list[str]` | Multi-select checkboxes |
153
+ | `.hidden(id, value)` | `type(value)` | Hidden field |
154
+
155
+ ## Webhooks
156
+
157
+ Verify and parse webhook events:
158
+
159
+ ```python
160
+ from hax import verify_signature, parse_event
161
+
162
+ # In your webhook handler
163
+ def handle_webhook(request):
164
+ # Verify signature
165
+ is_valid = verify_signature(
166
+ payload=request.body,
167
+ signature=request.headers["X-Hax-Signature"],
168
+ secret="whsec_...",
169
+ )
170
+ if not is_valid:
171
+ return 400, "Invalid signature"
172
+
173
+ # Parse the event
174
+ event = parse_event(request.body)
175
+
176
+ if event.event_type == "completed":
177
+ print(f"Request {event.request_id} completed!")
178
+ print(f"Response: {event.response}")
179
+ elif event.event_type == "expired":
180
+ print(f"Request {event.request_id} expired")
181
+
182
+ return 200, "OK"
183
+ ```
184
+
185
+ ### Event Types
186
+
187
+ - `request.sent` — Notification was delivered (email/SMS)
188
+ - `request.opened` — Human opened the request link
189
+ - `request.completed` — Human submitted a response
190
+ - `request.expired` — Request expired without action
191
+
192
+ ## Encryption
193
+
194
+ For sensitive response data, use end-to-end encryption:
195
+
196
+ ```python
197
+ from hax import HaxClient
198
+
199
+ # Passphrase-based (automatic encrypt/decrypt)
200
+ client = HaxClient(
201
+ api_key="hax_live_...",
202
+ encryption_key="my-secret-passphrase",
203
+ )
204
+
205
+ # Public key is automatically sent with requests
206
+ request = client.create_request(
207
+ type="text-approval-v1",
208
+ payload={"text": "Approve this sensitive action?"},
209
+ )
210
+
211
+ # Response is automatically decrypted when retrieved
212
+ completed = client.get_request(request.id)
213
+ print(completed.response) # Decrypted plaintext
214
+ ```
215
+
216
+ ### Manual Decryption
217
+
218
+ ```python
219
+ from hax import generate_key_pair, decrypt_response, is_encrypted_response
220
+
221
+ public_key, private_key = generate_key_pair("my-secret")
222
+
223
+ # Use public_key when creating the client
224
+ client = HaxClient(api_key="...", public_key=public_key)
225
+
226
+ # Later, manually decrypt
227
+ request = client.get_request("req_123")
228
+ if is_encrypted_response(request.response):
229
+ decrypted = decrypt_response(request.response["_encrypted"], private_key)
230
+ ```
231
+
232
+ ## Error Handling
233
+
234
+ ```python
235
+ from hax import (
236
+ HaxError, # Base error
237
+ AuthenticationError, # Invalid API key (401)
238
+ ValidationError, # Invalid request data (400/422)
239
+ NotFoundError, # Resource not found (404)
240
+ RateLimitError, # Too many requests (429)
241
+ ServerError, # Server error (500+)
242
+ DecryptionError, # Decryption failure
243
+ )
244
+
245
+ try:
246
+ request = client.create_request(...)
247
+ except AuthenticationError:
248
+ print("Check your API key")
249
+ except ValidationError as e:
250
+ print(f"Invalid request: {e}")
251
+ except RateLimitError:
252
+ print("Rate limited, try again later")
253
+ except HaxError as e:
254
+ print(f"API error: {e}")
255
+ ```
256
+
257
+ ## Template Types
258
+
259
+ | Template | Description |
260
+ |----------|-------------|
261
+ | `text-approval-v1` | Show text and collect an approve/deny decision |
262
+ | `confirm-action-v1` | Require typing a specific phrase to confirm a destructive action |
263
+ | `collect-email-v1` | Prompt the user for an email address |
264
+ | `form-builder` | Advanced forms with field types, layouts, validation, and conditional logic |
265
+ | `multi-choice-selection-v1` | Single or multiple selection from customizable option cards |
266
+ | `code-changes-v1` | GitHub-style diff view with inline line comments |
267
+ | `rich-text-editor-v1` | Markdown-formatted text editing for documents and reports |
268
+ | `file-upload-v1` | Collect files (documents, images, CSVs) from users |
269
+ | `signature-capture-v1` | Capture e-signatures with optional signer name and legal text |
270
+ | `data-table-review-v1` | Review, select, or edit tabular data |
271
+ | `scheduling-picker-v1` | Date/time slot selection with optional recurring schedules |
272
+ | `multi-step-wizard-v1` | Sequential steps with navigation and progress indicator |
273
+ | `side-by-side-comparison-v1` | Compare two versions with diff highlighting |
274
+ | `terminal-output-v1` | Display command output/logs with approve-to-continue |
275
+
276
+ ## Notes
277
+
278
+ - Auth is **API key only**. Provide the key via `HaxClient(api_key=...)`; Clerk/session auth is not required for API access.
279
+ - API responses wrap resources (e.g., `{"request": {...}}`); the SDK unwraps this automatically.
280
+ - Template payloads and responses are flexible; consult the template configs for the fields each template expects/returns.
281
+ - The client supports context manager usage: `with HaxClient(...) as client:`
@@ -0,0 +1 @@
1
+ # HAX SDK Examples