predev-api 0.11.0__tar.gz → 0.12.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.
- {predev_api-0.11.0/predev_api.egg-info → predev_api-0.12.0}/PKG-INFO +172 -13
- {predev_api-0.11.0 → predev_api-0.12.0}/README.md +171 -12
- {predev_api-0.11.0 → predev_api-0.12.0}/predev_api/__init__.py +1 -1
- {predev_api-0.11.0 → predev_api-0.12.0}/predev_api/client.py +132 -6
- {predev_api-0.11.0 → predev_api-0.12.0/predev_api.egg-info}/PKG-INFO +172 -13
- {predev_api-0.11.0 → predev_api-0.12.0}/setup.py +1 -1
- {predev_api-0.11.0 → predev_api-0.12.0}/LICENSE +0 -0
- {predev_api-0.11.0 → predev_api-0.12.0}/MANIFEST.in +0 -0
- {predev_api-0.11.0 → predev_api-0.12.0}/predev_api/exceptions.py +0 -0
- {predev_api-0.11.0 → predev_api-0.12.0}/predev_api.egg-info/SOURCES.txt +0 -0
- {predev_api-0.11.0 → predev_api-0.12.0}/predev_api.egg-info/dependency_links.txt +0 -0
- {predev_api-0.11.0 → predev_api-0.12.0}/predev_api.egg-info/requires.txt +0 -0
- {predev_api-0.11.0 → predev_api-0.12.0}/predev_api.egg-info/top_level.txt +0 -0
- {predev_api-0.11.0 → predev_api-0.12.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: predev-api
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.0
|
|
4
4
|
Summary: Python client for the Pre.dev Architect API - Generate comprehensive software specifications
|
|
5
5
|
Home-page: https://github.com/predotdev/predev-api
|
|
6
6
|
Author: Pre.dev
|
|
@@ -38,18 +38,27 @@ Dynamic: requires-dist
|
|
|
38
38
|
Dynamic: requires-python
|
|
39
39
|
Dynamic: summary
|
|
40
40
|
|
|
41
|
-
# pre.dev
|
|
41
|
+
# pre.dev API — Python Client
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
Python client for the [Pre.dev API](https://docs.pre.dev) — AI-powered software specs + browser automation.
|
|
44
44
|
|
|
45
45
|
## Features
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
|
|
47
|
+
**Specs**
|
|
48
|
+
- 🚀 **Fast Spec**: Comprehensive specifications for MVPs and prototypes
|
|
49
|
+
- 🔍 **Deep Spec**: Ultra-detailed specifications for complex systems
|
|
50
|
+
- ⚡ **Async Spec**: Non-blocking async methods with status polling
|
|
51
|
+
- 📄 File upload support (PDFs, docs, images as reference context)
|
|
52
|
+
|
|
53
|
+
**Browser automation (NEW)**
|
|
54
|
+
- 🌐 **Browser Tasks**: Scrape, fill forms, navigate pages with structured JSON output
|
|
55
|
+
- 📡 **SSE streaming**: Watch execution live — screenshots, plans, actions
|
|
56
|
+
- ⏱ **Async mode**: Fire-and-forget, poll for progress
|
|
57
|
+
- 🔁 **Retrieval**: Get any past task with full timeline for audit/replay
|
|
58
|
+
|
|
59
|
+
**Quality of life**
|
|
60
|
+
- ✨ Full type hints
|
|
61
|
+
- 🛡 Custom exceptions for auth / rate limit / API errors
|
|
53
62
|
|
|
54
63
|
## Installation
|
|
55
64
|
|
|
@@ -357,10 +366,6 @@ class SpecResponse:
|
|
|
357
366
|
|
|
358
367
|
# Integration URLs (when completed)
|
|
359
368
|
predevUrl: Optional[str] = None # Link to pre.dev project
|
|
360
|
-
lovableUrl: Optional[str] = None # Link to generate with Lovable
|
|
361
|
-
cursorUrl: Optional[str] = None # Link to generate with Cursor
|
|
362
|
-
v0Url: Optional[str] = None # Link to generate with v0
|
|
363
|
-
boltUrl: Optional[str] = None # Link to generate with Bolt
|
|
364
369
|
|
|
365
370
|
# Documentation (when doc_urls provided)
|
|
366
371
|
zippedDocsUrls: Optional[List[ZippedDocsUrl]] = None
|
|
@@ -494,3 +499,157 @@ For more information about the Pre.dev Architect API, visit:
|
|
|
494
499
|
## Support
|
|
495
500
|
|
|
496
501
|
For issues, questions, or contributions, please visit the [GitHub repository](https://github.com/predotdev/predev-api).
|
|
502
|
+
|
|
503
|
+
## Browser Tasks
|
|
504
|
+
|
|
505
|
+
Run browser automation — scrape data, fill forms, navigate pages, extract structured data. Each task navigates a URL, optionally performs actions, and returns typed JSON.
|
|
506
|
+
|
|
507
|
+
### Quick start
|
|
508
|
+
|
|
509
|
+
```python
|
|
510
|
+
from predev_api import PredevAPI
|
|
511
|
+
|
|
512
|
+
client = PredevAPI(api_key="your_api_key")
|
|
513
|
+
|
|
514
|
+
result = client.browser_tasks([
|
|
515
|
+
{
|
|
516
|
+
"url": "https://news.ycombinator.com",
|
|
517
|
+
"instruction": "Extract the top 5 stories",
|
|
518
|
+
"output": {
|
|
519
|
+
"type": "object",
|
|
520
|
+
"properties": {
|
|
521
|
+
"stories": {
|
|
522
|
+
"type": "array",
|
|
523
|
+
"items": {
|
|
524
|
+
"type": "object",
|
|
525
|
+
"properties": {
|
|
526
|
+
"title": {"type": "string"},
|
|
527
|
+
"points": {"type": "number"},
|
|
528
|
+
},
|
|
529
|
+
},
|
|
530
|
+
},
|
|
531
|
+
},
|
|
532
|
+
},
|
|
533
|
+
}
|
|
534
|
+
])
|
|
535
|
+
|
|
536
|
+
for story in result["results"][0]["data"]["stories"]:
|
|
537
|
+
print(f"{story['title']} ({story['points']} pts)")
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
### Three execution modes
|
|
541
|
+
|
|
542
|
+
#### 1. Sync (default) — wait for completion
|
|
543
|
+
|
|
544
|
+
```python
|
|
545
|
+
result = client.browser_tasks([
|
|
546
|
+
{"url": "https://example.com", "output": {"type": "object", "properties": {"heading": {"type": "string"}}}}
|
|
547
|
+
])
|
|
548
|
+
print(result["results"][0]["data"]) # {'heading': 'Example Domain'}
|
|
549
|
+
print(result["totalCreditsUsed"]) # 0.1
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
#### 2. Stream (`stream=True`) — live timeline via SSE
|
|
553
|
+
|
|
554
|
+
Yields events as the agent runs. Good for showing progress in a UI.
|
|
555
|
+
|
|
556
|
+
```python
|
|
557
|
+
for msg in client.browser_tasks(tasks, stream=True):
|
|
558
|
+
e, d = msg["event"], msg["data"]
|
|
559
|
+
if e == "task_event":
|
|
560
|
+
# navigation | screenshot | plan | action | validation | done
|
|
561
|
+
print(f"[{d['type']}]", d.get("data"))
|
|
562
|
+
elif e == "task_result":
|
|
563
|
+
print(f"Task {d['taskIndex']} done:", d.get("data"))
|
|
564
|
+
elif e == "done":
|
|
565
|
+
print("Batch complete:", d["totalCreditsUsed"], "credits")
|
|
566
|
+
elif e == "error":
|
|
567
|
+
print("Batch error:", d)
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
#### 3. Async (`run_async=True`) — fire-and-forget, poll for progress
|
|
571
|
+
|
|
572
|
+
Returns the batch ID immediately. Use for long-running batches or background jobs.
|
|
573
|
+
|
|
574
|
+
```python
|
|
575
|
+
import time
|
|
576
|
+
|
|
577
|
+
r = client.browser_tasks(tasks, run_async=True)
|
|
578
|
+
# {"id": "batch_abc", "status": "processing", "completed": 0, "total": 3}
|
|
579
|
+
|
|
580
|
+
while True:
|
|
581
|
+
state = client.get_browser_tasks(r["id"])
|
|
582
|
+
print(f"{state['completed']}/{state['total']}")
|
|
583
|
+
for done in state["results"]:
|
|
584
|
+
print(f" ✓ {done['url']} -> {done.get('data')}")
|
|
585
|
+
if state["status"] == "completed":
|
|
586
|
+
break
|
|
587
|
+
time.sleep(1)
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### Task shapes
|
|
591
|
+
|
|
592
|
+
Each task's behavior is determined by which fields are set:
|
|
593
|
+
|
|
594
|
+
| Fields | Shape | Example |
|
|
595
|
+
|---|---|---|
|
|
596
|
+
| `url` + `output` | **Scrape** | Extract structured data from a page |
|
|
597
|
+
| `url` + `instruction` | **Act** | Click, navigate, search |
|
|
598
|
+
| `url` + `instruction` + `input` | **Form fill** | Fill & submit a form |
|
|
599
|
+
| `url` + `instruction` + `output` | **Act + extract** | Navigate then extract data |
|
|
600
|
+
|
|
601
|
+
### Retrieving a batch with the full timeline
|
|
602
|
+
|
|
603
|
+
Every task records navigation, screenshots, LLM plans, actions, validations. Retrieve for audit, replay, or debugging:
|
|
604
|
+
|
|
605
|
+
```python
|
|
606
|
+
import base64
|
|
607
|
+
|
|
608
|
+
details = client.get_browser_tasks(batch_id, include_events=True)
|
|
609
|
+
for result in details["results"]:
|
|
610
|
+
for ev in result.get("events", []):
|
|
611
|
+
if ev["type"] == "screenshot":
|
|
612
|
+
with open(f"screenshot_iter{ev.get('iteration')}.jpg", "wb") as f:
|
|
613
|
+
f.write(base64.b64decode(ev["data"]["base64"]))
|
|
614
|
+
elif ev["type"] == "plan":
|
|
615
|
+
print(f"Iter {ev.get('iteration')} plan: {ev['data'].get('notes')}")
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
### Parallel batch example
|
|
619
|
+
|
|
620
|
+
```python
|
|
621
|
+
# Scrape 100 URLs with 20 browsers in parallel
|
|
622
|
+
urls = [...100 urls...]
|
|
623
|
+
result = client.browser_tasks(
|
|
624
|
+
[{"url": u, "output": {"type": "object", "properties": {"title": {"type": "string"}}}} for u in urls],
|
|
625
|
+
concurrency=20
|
|
626
|
+
)
|
|
627
|
+
print(f"{result['completed']}/{result['total']} done in {result['totalCreditsUsed']} credits")
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
### Browser task methods
|
|
631
|
+
|
|
632
|
+
| Method | Returns | Use when |
|
|
633
|
+
|---|---|---|
|
|
634
|
+
| `browser_tasks(tasks, concurrency=N)` | dict | Default — wait for completion |
|
|
635
|
+
| `browser_tasks(tasks, stream=True)` | iterator of SSE dicts | Live UI showing execution timeline |
|
|
636
|
+
| `browser_tasks(tasks, run_async=True)` | dict (empty results, returned immediately) | Long batches, background jobs |
|
|
637
|
+
| `get_browser_tasks(batch_id, include_events=False)` | dict | Poll progress or retrieve a completed batch |
|
|
638
|
+
|
|
639
|
+
### Task result statuses
|
|
640
|
+
|
|
641
|
+
| Status | Meaning |
|
|
642
|
+
|---|---|
|
|
643
|
+
| `SUCCESS` | Task completed, data extracted |
|
|
644
|
+
| `BLOCKED` | Page blocked automation (bot detection) |
|
|
645
|
+
| `TIMEOUT` | Task exceeded time limit |
|
|
646
|
+
| `LOOP` | Agent detected it was stuck in a loop |
|
|
647
|
+
| `ERROR` | Unexpected error |
|
|
648
|
+
| `NO_TARGET` | Could not find target elements |
|
|
649
|
+
| `CAPTCHA_FAILED` | CAPTCHA solve failed |
|
|
650
|
+
|
|
651
|
+
### Pricing
|
|
652
|
+
|
|
653
|
+
- Minimum: **0.1 credits per task** ($0.01)
|
|
654
|
+
- 10x margin on underlying LLM + sandbox compute
|
|
655
|
+
- 1 credit = $0.10
|
|
@@ -1,15 +1,24 @@
|
|
|
1
|
-
# pre.dev
|
|
1
|
+
# pre.dev API — Python Client
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Python client for the [Pre.dev API](https://docs.pre.dev) — AI-powered software specs + browser automation.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
|
|
7
|
+
**Specs**
|
|
8
|
+
- 🚀 **Fast Spec**: Comprehensive specifications for MVPs and prototypes
|
|
9
|
+
- 🔍 **Deep Spec**: Ultra-detailed specifications for complex systems
|
|
10
|
+
- ⚡ **Async Spec**: Non-blocking async methods with status polling
|
|
11
|
+
- 📄 File upload support (PDFs, docs, images as reference context)
|
|
12
|
+
|
|
13
|
+
**Browser automation (NEW)**
|
|
14
|
+
- 🌐 **Browser Tasks**: Scrape, fill forms, navigate pages with structured JSON output
|
|
15
|
+
- 📡 **SSE streaming**: Watch execution live — screenshots, plans, actions
|
|
16
|
+
- ⏱ **Async mode**: Fire-and-forget, poll for progress
|
|
17
|
+
- 🔁 **Retrieval**: Get any past task with full timeline for audit/replay
|
|
18
|
+
|
|
19
|
+
**Quality of life**
|
|
20
|
+
- ✨ Full type hints
|
|
21
|
+
- 🛡 Custom exceptions for auth / rate limit / API errors
|
|
13
22
|
|
|
14
23
|
## Installation
|
|
15
24
|
|
|
@@ -317,10 +326,6 @@ class SpecResponse:
|
|
|
317
326
|
|
|
318
327
|
# Integration URLs (when completed)
|
|
319
328
|
predevUrl: Optional[str] = None # Link to pre.dev project
|
|
320
|
-
lovableUrl: Optional[str] = None # Link to generate with Lovable
|
|
321
|
-
cursorUrl: Optional[str] = None # Link to generate with Cursor
|
|
322
|
-
v0Url: Optional[str] = None # Link to generate with v0
|
|
323
|
-
boltUrl: Optional[str] = None # Link to generate with Bolt
|
|
324
329
|
|
|
325
330
|
# Documentation (when doc_urls provided)
|
|
326
331
|
zippedDocsUrls: Optional[List[ZippedDocsUrl]] = None
|
|
@@ -454,3 +459,157 @@ For more information about the Pre.dev Architect API, visit:
|
|
|
454
459
|
## Support
|
|
455
460
|
|
|
456
461
|
For issues, questions, or contributions, please visit the [GitHub repository](https://github.com/predotdev/predev-api).
|
|
462
|
+
|
|
463
|
+
## Browser Tasks
|
|
464
|
+
|
|
465
|
+
Run browser automation — scrape data, fill forms, navigate pages, extract structured data. Each task navigates a URL, optionally performs actions, and returns typed JSON.
|
|
466
|
+
|
|
467
|
+
### Quick start
|
|
468
|
+
|
|
469
|
+
```python
|
|
470
|
+
from predev_api import PredevAPI
|
|
471
|
+
|
|
472
|
+
client = PredevAPI(api_key="your_api_key")
|
|
473
|
+
|
|
474
|
+
result = client.browser_tasks([
|
|
475
|
+
{
|
|
476
|
+
"url": "https://news.ycombinator.com",
|
|
477
|
+
"instruction": "Extract the top 5 stories",
|
|
478
|
+
"output": {
|
|
479
|
+
"type": "object",
|
|
480
|
+
"properties": {
|
|
481
|
+
"stories": {
|
|
482
|
+
"type": "array",
|
|
483
|
+
"items": {
|
|
484
|
+
"type": "object",
|
|
485
|
+
"properties": {
|
|
486
|
+
"title": {"type": "string"},
|
|
487
|
+
"points": {"type": "number"},
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
},
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
}
|
|
494
|
+
])
|
|
495
|
+
|
|
496
|
+
for story in result["results"][0]["data"]["stories"]:
|
|
497
|
+
print(f"{story['title']} ({story['points']} pts)")
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Three execution modes
|
|
501
|
+
|
|
502
|
+
#### 1. Sync (default) — wait for completion
|
|
503
|
+
|
|
504
|
+
```python
|
|
505
|
+
result = client.browser_tasks([
|
|
506
|
+
{"url": "https://example.com", "output": {"type": "object", "properties": {"heading": {"type": "string"}}}}
|
|
507
|
+
])
|
|
508
|
+
print(result["results"][0]["data"]) # {'heading': 'Example Domain'}
|
|
509
|
+
print(result["totalCreditsUsed"]) # 0.1
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
#### 2. Stream (`stream=True`) — live timeline via SSE
|
|
513
|
+
|
|
514
|
+
Yields events as the agent runs. Good for showing progress in a UI.
|
|
515
|
+
|
|
516
|
+
```python
|
|
517
|
+
for msg in client.browser_tasks(tasks, stream=True):
|
|
518
|
+
e, d = msg["event"], msg["data"]
|
|
519
|
+
if e == "task_event":
|
|
520
|
+
# navigation | screenshot | plan | action | validation | done
|
|
521
|
+
print(f"[{d['type']}]", d.get("data"))
|
|
522
|
+
elif e == "task_result":
|
|
523
|
+
print(f"Task {d['taskIndex']} done:", d.get("data"))
|
|
524
|
+
elif e == "done":
|
|
525
|
+
print("Batch complete:", d["totalCreditsUsed"], "credits")
|
|
526
|
+
elif e == "error":
|
|
527
|
+
print("Batch error:", d)
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
#### 3. Async (`run_async=True`) — fire-and-forget, poll for progress
|
|
531
|
+
|
|
532
|
+
Returns the batch ID immediately. Use for long-running batches or background jobs.
|
|
533
|
+
|
|
534
|
+
```python
|
|
535
|
+
import time
|
|
536
|
+
|
|
537
|
+
r = client.browser_tasks(tasks, run_async=True)
|
|
538
|
+
# {"id": "batch_abc", "status": "processing", "completed": 0, "total": 3}
|
|
539
|
+
|
|
540
|
+
while True:
|
|
541
|
+
state = client.get_browser_tasks(r["id"])
|
|
542
|
+
print(f"{state['completed']}/{state['total']}")
|
|
543
|
+
for done in state["results"]:
|
|
544
|
+
print(f" ✓ {done['url']} -> {done.get('data')}")
|
|
545
|
+
if state["status"] == "completed":
|
|
546
|
+
break
|
|
547
|
+
time.sleep(1)
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### Task shapes
|
|
551
|
+
|
|
552
|
+
Each task's behavior is determined by which fields are set:
|
|
553
|
+
|
|
554
|
+
| Fields | Shape | Example |
|
|
555
|
+
|---|---|---|
|
|
556
|
+
| `url` + `output` | **Scrape** | Extract structured data from a page |
|
|
557
|
+
| `url` + `instruction` | **Act** | Click, navigate, search |
|
|
558
|
+
| `url` + `instruction` + `input` | **Form fill** | Fill & submit a form |
|
|
559
|
+
| `url` + `instruction` + `output` | **Act + extract** | Navigate then extract data |
|
|
560
|
+
|
|
561
|
+
### Retrieving a batch with the full timeline
|
|
562
|
+
|
|
563
|
+
Every task records navigation, screenshots, LLM plans, actions, validations. Retrieve for audit, replay, or debugging:
|
|
564
|
+
|
|
565
|
+
```python
|
|
566
|
+
import base64
|
|
567
|
+
|
|
568
|
+
details = client.get_browser_tasks(batch_id, include_events=True)
|
|
569
|
+
for result in details["results"]:
|
|
570
|
+
for ev in result.get("events", []):
|
|
571
|
+
if ev["type"] == "screenshot":
|
|
572
|
+
with open(f"screenshot_iter{ev.get('iteration')}.jpg", "wb") as f:
|
|
573
|
+
f.write(base64.b64decode(ev["data"]["base64"]))
|
|
574
|
+
elif ev["type"] == "plan":
|
|
575
|
+
print(f"Iter {ev.get('iteration')} plan: {ev['data'].get('notes')}")
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### Parallel batch example
|
|
579
|
+
|
|
580
|
+
```python
|
|
581
|
+
# Scrape 100 URLs with 20 browsers in parallel
|
|
582
|
+
urls = [...100 urls...]
|
|
583
|
+
result = client.browser_tasks(
|
|
584
|
+
[{"url": u, "output": {"type": "object", "properties": {"title": {"type": "string"}}}} for u in urls],
|
|
585
|
+
concurrency=20
|
|
586
|
+
)
|
|
587
|
+
print(f"{result['completed']}/{result['total']} done in {result['totalCreditsUsed']} credits")
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### Browser task methods
|
|
591
|
+
|
|
592
|
+
| Method | Returns | Use when |
|
|
593
|
+
|---|---|---|
|
|
594
|
+
| `browser_tasks(tasks, concurrency=N)` | dict | Default — wait for completion |
|
|
595
|
+
| `browser_tasks(tasks, stream=True)` | iterator of SSE dicts | Live UI showing execution timeline |
|
|
596
|
+
| `browser_tasks(tasks, run_async=True)` | dict (empty results, returned immediately) | Long batches, background jobs |
|
|
597
|
+
| `get_browser_tasks(batch_id, include_events=False)` | dict | Poll progress or retrieve a completed batch |
|
|
598
|
+
|
|
599
|
+
### Task result statuses
|
|
600
|
+
|
|
601
|
+
| Status | Meaning |
|
|
602
|
+
|---|---|
|
|
603
|
+
| `SUCCESS` | Task completed, data extracted |
|
|
604
|
+
| `BLOCKED` | Page blocked automation (bot detection) |
|
|
605
|
+
| `TIMEOUT` | Task exceeded time limit |
|
|
606
|
+
| `LOOP` | Agent detected it was stuck in a loop |
|
|
607
|
+
| `ERROR` | Unexpected error |
|
|
608
|
+
| `NO_TARGET` | Could not find target elements |
|
|
609
|
+
| `CAPTCHA_FAILED` | CAPTCHA solve failed |
|
|
610
|
+
|
|
611
|
+
### Pricing
|
|
612
|
+
|
|
613
|
+
- Minimum: **0.1 credits per task** ($0.01)
|
|
614
|
+
- 10x margin on underlying LLM + sandbox compute
|
|
615
|
+
- 1 credit = $0.10
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
Client for the Pre.dev Architect API
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from typing import Optional, Dict, Any, Literal, List, Union, BinaryIO
|
|
5
|
+
from typing import Optional, Dict, Any, Literal, List, Union, BinaryIO, Iterator
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
+
import json
|
|
7
8
|
import requests
|
|
8
9
|
from .exceptions import PredevAPIError, AuthenticationError, RateLimitError
|
|
9
10
|
|
|
@@ -131,7 +132,7 @@ class SpecGraphNode:
|
|
|
131
132
|
label: str = ""
|
|
132
133
|
type: Optional[str] = None
|
|
133
134
|
description: Optional[str] = None
|
|
134
|
-
level: Optional[
|
|
135
|
+
level: Optional[int] = None
|
|
135
136
|
hours: Optional[float] = None
|
|
136
137
|
|
|
137
138
|
|
|
@@ -199,10 +200,6 @@ class SpecResponse:
|
|
|
199
200
|
architectureInfographicUrl: Optional[str] = None
|
|
200
201
|
|
|
201
202
|
predevUrl: Optional[str] = None
|
|
202
|
-
lovableUrl: Optional[str] = None
|
|
203
|
-
cursorUrl: Optional[str] = None
|
|
204
|
-
v0Url: Optional[str] = None
|
|
205
|
-
boltUrl: Optional[str] = None
|
|
206
203
|
|
|
207
204
|
zippedDocsUrls: Optional[List['ZippedDocsUrl']] = None
|
|
208
205
|
|
|
@@ -611,6 +608,135 @@ class PredevAPI:
|
|
|
611
608
|
except requests.RequestException as e:
|
|
612
609
|
raise PredevAPIError(f"Request failed: {str(e)}") from e
|
|
613
610
|
|
|
611
|
+
def get_browser_tasks(
|
|
612
|
+
self,
|
|
613
|
+
batch_id: str,
|
|
614
|
+
include_events: bool = False
|
|
615
|
+
) -> Dict[str, Any]:
|
|
616
|
+
"""
|
|
617
|
+
Get the status + results of a browser tasks batch by ID.
|
|
618
|
+
|
|
619
|
+
Works for both in-progress and completed batches - use with async=True
|
|
620
|
+
submissions to poll for progress. Set include_events=True to get the
|
|
621
|
+
full timeline (screenshots, plans, actions, validations) for each task.
|
|
622
|
+
|
|
623
|
+
Args:
|
|
624
|
+
batch_id: The batch ID returned from browser_tasks
|
|
625
|
+
include_events: Include full event timeline (can be large due to screenshots)
|
|
626
|
+
|
|
627
|
+
Returns:
|
|
628
|
+
Dict with id, total, completed, results, totalCost, totalCreditsUsed, status
|
|
629
|
+
"""
|
|
630
|
+
qs = "?includeEvents=true" if include_events else ""
|
|
631
|
+
url = f"{self.base_url}/api/v1/browser-tasks/{batch_id}{qs}"
|
|
632
|
+
try:
|
|
633
|
+
response = requests.get(url, headers=self.headers, timeout=60)
|
|
634
|
+
self._handle_response(response)
|
|
635
|
+
return response.json()
|
|
636
|
+
except requests.RequestException as e:
|
|
637
|
+
raise PredevAPIError(f"Request failed: {str(e)}") from e
|
|
638
|
+
|
|
639
|
+
def browser_tasks(
|
|
640
|
+
self,
|
|
641
|
+
tasks: List[Dict[str, Any]],
|
|
642
|
+
concurrency: Optional[int] = None,
|
|
643
|
+
stream: bool = False,
|
|
644
|
+
run_async: bool = False
|
|
645
|
+
) -> Union[Dict[str, Any], Iterator[Dict[str, Any]]]:
|
|
646
|
+
"""
|
|
647
|
+
Run browser automation tasks - scrape data, fill forms, navigate pages.
|
|
648
|
+
|
|
649
|
+
Always pass an array of tasks (even for a single task). Each task has a
|
|
650
|
+
url and optional instruction, input, and output schema.
|
|
651
|
+
|
|
652
|
+
Args:
|
|
653
|
+
tasks: Array of tasks. Each task has:
|
|
654
|
+
- url (str, required): URL to navigate to
|
|
655
|
+
- instruction (str, optional): What to do on the page
|
|
656
|
+
- input (dict, optional): Form values / input data
|
|
657
|
+
- output (dict, optional): JSON Schema for structured output
|
|
658
|
+
concurrency: Parallel browsers (default 5, max 20)
|
|
659
|
+
stream: If True, returns an iterator of SSE events instead of
|
|
660
|
+
waiting for all tasks to complete
|
|
661
|
+
|
|
662
|
+
Returns:
|
|
663
|
+
Without stream: Dict with id, total, completed, results, totalCost, totalCreditsUsed
|
|
664
|
+
With stream: Iterator yielding dicts with 'event' and 'data' keys:
|
|
665
|
+
- event='task_event': real-time events (navigation, screenshot, plan, action, etc.)
|
|
666
|
+
- event='task_result': a single task completed
|
|
667
|
+
- event='done': batch complete (data is the full result)
|
|
668
|
+
- event='error': fatal error
|
|
669
|
+
|
|
670
|
+
Example:
|
|
671
|
+
>>> # Standard mode
|
|
672
|
+
>>> result = client.browser_tasks([
|
|
673
|
+
... {"url": "https://example.com", "output": {"type": "object", "properties": {"heading": {"type": "string"}}}}
|
|
674
|
+
... ])
|
|
675
|
+
>>> print(result["results"][0]["data"])
|
|
676
|
+
>>>
|
|
677
|
+
>>> # Stream mode
|
|
678
|
+
>>> for msg in client.browser_tasks([...], stream=True):
|
|
679
|
+
... if msg["event"] == "done":
|
|
680
|
+
... print("Done:", msg["data"]["totalCreditsUsed"], "credits")
|
|
681
|
+
"""
|
|
682
|
+
if stream:
|
|
683
|
+
return self._browser_tasks_stream(tasks, concurrency)
|
|
684
|
+
|
|
685
|
+
url = f"{self.base_url}/api/v1/browser-tasks"
|
|
686
|
+
payload: Dict[str, Any] = {"tasks": tasks}
|
|
687
|
+
if concurrency is not None:
|
|
688
|
+
payload["concurrency"] = concurrency
|
|
689
|
+
if run_async:
|
|
690
|
+
payload["async"] = True
|
|
691
|
+
|
|
692
|
+
try:
|
|
693
|
+
response = requests.post(
|
|
694
|
+
url,
|
|
695
|
+
headers=self.headers,
|
|
696
|
+
json=payload,
|
|
697
|
+
timeout=300
|
|
698
|
+
)
|
|
699
|
+
self._handle_response(response)
|
|
700
|
+
return response.json()
|
|
701
|
+
except requests.RequestException as e:
|
|
702
|
+
raise PredevAPIError(f"Request failed: {str(e)}") from e
|
|
703
|
+
|
|
704
|
+
def _browser_tasks_stream(
|
|
705
|
+
self,
|
|
706
|
+
tasks: List[Dict[str, Any]],
|
|
707
|
+
concurrency: Optional[int] = None
|
|
708
|
+
) -> Iterator[Dict[str, Any]]:
|
|
709
|
+
"""SSE streaming implementation for browser_tasks."""
|
|
710
|
+
url = f"{self.base_url}/api/v1/browser-tasks"
|
|
711
|
+
payload: Dict[str, Any] = {"tasks": tasks, "stream": True}
|
|
712
|
+
if concurrency is not None:
|
|
713
|
+
payload["concurrency"] = concurrency
|
|
714
|
+
|
|
715
|
+
try:
|
|
716
|
+
response = requests.post(
|
|
717
|
+
url,
|
|
718
|
+
headers=self.headers,
|
|
719
|
+
json=payload,
|
|
720
|
+
stream=True,
|
|
721
|
+
timeout=300
|
|
722
|
+
)
|
|
723
|
+
self._handle_response(response)
|
|
724
|
+
|
|
725
|
+
current_event = ""
|
|
726
|
+
for line in response.iter_lines(decode_unicode=True):
|
|
727
|
+
if not line:
|
|
728
|
+
continue
|
|
729
|
+
if line.startswith(":"):
|
|
730
|
+
continue
|
|
731
|
+
if line.startswith("event: "):
|
|
732
|
+
current_event = line[7:].strip()
|
|
733
|
+
elif line.startswith("data: ") and current_event:
|
|
734
|
+
data = json.loads(line[6:])
|
|
735
|
+
yield {"event": current_event, "data": data}
|
|
736
|
+
current_event = ""
|
|
737
|
+
except requests.RequestException as e:
|
|
738
|
+
raise PredevAPIError(f"Request failed: {str(e)}") from e
|
|
739
|
+
|
|
614
740
|
def _make_request(
|
|
615
741
|
self,
|
|
616
742
|
endpoint: str,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: predev-api
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.0
|
|
4
4
|
Summary: Python client for the Pre.dev Architect API - Generate comprehensive software specifications
|
|
5
5
|
Home-page: https://github.com/predotdev/predev-api
|
|
6
6
|
Author: Pre.dev
|
|
@@ -38,18 +38,27 @@ Dynamic: requires-dist
|
|
|
38
38
|
Dynamic: requires-python
|
|
39
39
|
Dynamic: summary
|
|
40
40
|
|
|
41
|
-
# pre.dev
|
|
41
|
+
# pre.dev API — Python Client
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
Python client for the [Pre.dev API](https://docs.pre.dev) — AI-powered software specs + browser automation.
|
|
44
44
|
|
|
45
45
|
## Features
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
|
|
47
|
+
**Specs**
|
|
48
|
+
- 🚀 **Fast Spec**: Comprehensive specifications for MVPs and prototypes
|
|
49
|
+
- 🔍 **Deep Spec**: Ultra-detailed specifications for complex systems
|
|
50
|
+
- ⚡ **Async Spec**: Non-blocking async methods with status polling
|
|
51
|
+
- 📄 File upload support (PDFs, docs, images as reference context)
|
|
52
|
+
|
|
53
|
+
**Browser automation (NEW)**
|
|
54
|
+
- 🌐 **Browser Tasks**: Scrape, fill forms, navigate pages with structured JSON output
|
|
55
|
+
- 📡 **SSE streaming**: Watch execution live — screenshots, plans, actions
|
|
56
|
+
- ⏱ **Async mode**: Fire-and-forget, poll for progress
|
|
57
|
+
- 🔁 **Retrieval**: Get any past task with full timeline for audit/replay
|
|
58
|
+
|
|
59
|
+
**Quality of life**
|
|
60
|
+
- ✨ Full type hints
|
|
61
|
+
- 🛡 Custom exceptions for auth / rate limit / API errors
|
|
53
62
|
|
|
54
63
|
## Installation
|
|
55
64
|
|
|
@@ -357,10 +366,6 @@ class SpecResponse:
|
|
|
357
366
|
|
|
358
367
|
# Integration URLs (when completed)
|
|
359
368
|
predevUrl: Optional[str] = None # Link to pre.dev project
|
|
360
|
-
lovableUrl: Optional[str] = None # Link to generate with Lovable
|
|
361
|
-
cursorUrl: Optional[str] = None # Link to generate with Cursor
|
|
362
|
-
v0Url: Optional[str] = None # Link to generate with v0
|
|
363
|
-
boltUrl: Optional[str] = None # Link to generate with Bolt
|
|
364
369
|
|
|
365
370
|
# Documentation (when doc_urls provided)
|
|
366
371
|
zippedDocsUrls: Optional[List[ZippedDocsUrl]] = None
|
|
@@ -494,3 +499,157 @@ For more information about the Pre.dev Architect API, visit:
|
|
|
494
499
|
## Support
|
|
495
500
|
|
|
496
501
|
For issues, questions, or contributions, please visit the [GitHub repository](https://github.com/predotdev/predev-api).
|
|
502
|
+
|
|
503
|
+
## Browser Tasks
|
|
504
|
+
|
|
505
|
+
Run browser automation — scrape data, fill forms, navigate pages, extract structured data. Each task navigates a URL, optionally performs actions, and returns typed JSON.
|
|
506
|
+
|
|
507
|
+
### Quick start
|
|
508
|
+
|
|
509
|
+
```python
|
|
510
|
+
from predev_api import PredevAPI
|
|
511
|
+
|
|
512
|
+
client = PredevAPI(api_key="your_api_key")
|
|
513
|
+
|
|
514
|
+
result = client.browser_tasks([
|
|
515
|
+
{
|
|
516
|
+
"url": "https://news.ycombinator.com",
|
|
517
|
+
"instruction": "Extract the top 5 stories",
|
|
518
|
+
"output": {
|
|
519
|
+
"type": "object",
|
|
520
|
+
"properties": {
|
|
521
|
+
"stories": {
|
|
522
|
+
"type": "array",
|
|
523
|
+
"items": {
|
|
524
|
+
"type": "object",
|
|
525
|
+
"properties": {
|
|
526
|
+
"title": {"type": "string"},
|
|
527
|
+
"points": {"type": "number"},
|
|
528
|
+
},
|
|
529
|
+
},
|
|
530
|
+
},
|
|
531
|
+
},
|
|
532
|
+
},
|
|
533
|
+
}
|
|
534
|
+
])
|
|
535
|
+
|
|
536
|
+
for story in result["results"][0]["data"]["stories"]:
|
|
537
|
+
print(f"{story['title']} ({story['points']} pts)")
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
### Three execution modes
|
|
541
|
+
|
|
542
|
+
#### 1. Sync (default) — wait for completion
|
|
543
|
+
|
|
544
|
+
```python
|
|
545
|
+
result = client.browser_tasks([
|
|
546
|
+
{"url": "https://example.com", "output": {"type": "object", "properties": {"heading": {"type": "string"}}}}
|
|
547
|
+
])
|
|
548
|
+
print(result["results"][0]["data"]) # {'heading': 'Example Domain'}
|
|
549
|
+
print(result["totalCreditsUsed"]) # 0.1
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
#### 2. Stream (`stream=True`) — live timeline via SSE
|
|
553
|
+
|
|
554
|
+
Yields events as the agent runs. Good for showing progress in a UI.
|
|
555
|
+
|
|
556
|
+
```python
|
|
557
|
+
for msg in client.browser_tasks(tasks, stream=True):
|
|
558
|
+
e, d = msg["event"], msg["data"]
|
|
559
|
+
if e == "task_event":
|
|
560
|
+
# navigation | screenshot | plan | action | validation | done
|
|
561
|
+
print(f"[{d['type']}]", d.get("data"))
|
|
562
|
+
elif e == "task_result":
|
|
563
|
+
print(f"Task {d['taskIndex']} done:", d.get("data"))
|
|
564
|
+
elif e == "done":
|
|
565
|
+
print("Batch complete:", d["totalCreditsUsed"], "credits")
|
|
566
|
+
elif e == "error":
|
|
567
|
+
print("Batch error:", d)
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
#### 3. Async (`run_async=True`) — fire-and-forget, poll for progress
|
|
571
|
+
|
|
572
|
+
Returns the batch ID immediately. Use for long-running batches or background jobs.
|
|
573
|
+
|
|
574
|
+
```python
|
|
575
|
+
import time
|
|
576
|
+
|
|
577
|
+
r = client.browser_tasks(tasks, run_async=True)
|
|
578
|
+
# {"id": "batch_abc", "status": "processing", "completed": 0, "total": 3}
|
|
579
|
+
|
|
580
|
+
while True:
|
|
581
|
+
state = client.get_browser_tasks(r["id"])
|
|
582
|
+
print(f"{state['completed']}/{state['total']}")
|
|
583
|
+
for done in state["results"]:
|
|
584
|
+
print(f" ✓ {done['url']} -> {done.get('data')}")
|
|
585
|
+
if state["status"] == "completed":
|
|
586
|
+
break
|
|
587
|
+
time.sleep(1)
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### Task shapes
|
|
591
|
+
|
|
592
|
+
Each task's behavior is determined by which fields are set:
|
|
593
|
+
|
|
594
|
+
| Fields | Shape | Example |
|
|
595
|
+
|---|---|---|
|
|
596
|
+
| `url` + `output` | **Scrape** | Extract structured data from a page |
|
|
597
|
+
| `url` + `instruction` | **Act** | Click, navigate, search |
|
|
598
|
+
| `url` + `instruction` + `input` | **Form fill** | Fill & submit a form |
|
|
599
|
+
| `url` + `instruction` + `output` | **Act + extract** | Navigate then extract data |
|
|
600
|
+
|
|
601
|
+
### Retrieving a batch with the full timeline
|
|
602
|
+
|
|
603
|
+
Every task records navigation, screenshots, LLM plans, actions, validations. Retrieve for audit, replay, or debugging:
|
|
604
|
+
|
|
605
|
+
```python
|
|
606
|
+
import base64
|
|
607
|
+
|
|
608
|
+
details = client.get_browser_tasks(batch_id, include_events=True)
|
|
609
|
+
for result in details["results"]:
|
|
610
|
+
for ev in result.get("events", []):
|
|
611
|
+
if ev["type"] == "screenshot":
|
|
612
|
+
with open(f"screenshot_iter{ev.get('iteration')}.jpg", "wb") as f:
|
|
613
|
+
f.write(base64.b64decode(ev["data"]["base64"]))
|
|
614
|
+
elif ev["type"] == "plan":
|
|
615
|
+
print(f"Iter {ev.get('iteration')} plan: {ev['data'].get('notes')}")
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
### Parallel batch example
|
|
619
|
+
|
|
620
|
+
```python
|
|
621
|
+
# Scrape 100 URLs with 20 browsers in parallel
|
|
622
|
+
urls = [...100 urls...]
|
|
623
|
+
result = client.browser_tasks(
|
|
624
|
+
[{"url": u, "output": {"type": "object", "properties": {"title": {"type": "string"}}}} for u in urls],
|
|
625
|
+
concurrency=20
|
|
626
|
+
)
|
|
627
|
+
print(f"{result['completed']}/{result['total']} done in {result['totalCreditsUsed']} credits")
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
### Browser task methods
|
|
631
|
+
|
|
632
|
+
| Method | Returns | Use when |
|
|
633
|
+
|---|---|---|
|
|
634
|
+
| `browser_tasks(tasks, concurrency=N)` | dict | Default — wait for completion |
|
|
635
|
+
| `browser_tasks(tasks, stream=True)` | iterator of SSE dicts | Live UI showing execution timeline |
|
|
636
|
+
| `browser_tasks(tasks, run_async=True)` | dict (empty results, returned immediately) | Long batches, background jobs |
|
|
637
|
+
| `get_browser_tasks(batch_id, include_events=False)` | dict | Poll progress or retrieve a completed batch |
|
|
638
|
+
|
|
639
|
+
### Task result statuses
|
|
640
|
+
|
|
641
|
+
| Status | Meaning |
|
|
642
|
+
|---|---|
|
|
643
|
+
| `SUCCESS` | Task completed, data extracted |
|
|
644
|
+
| `BLOCKED` | Page blocked automation (bot detection) |
|
|
645
|
+
| `TIMEOUT` | Task exceeded time limit |
|
|
646
|
+
| `LOOP` | Agent detected it was stuck in a loop |
|
|
647
|
+
| `ERROR` | Unexpected error |
|
|
648
|
+
| `NO_TARGET` | Could not find target elements |
|
|
649
|
+
| `CAPTCHA_FAILED` | CAPTCHA solve failed |
|
|
650
|
+
|
|
651
|
+
### Pricing
|
|
652
|
+
|
|
653
|
+
- Minimum: **0.1 credits per task** ($0.01)
|
|
654
|
+
- 10x margin on underlying LLM + sandbox compute
|
|
655
|
+
- 1 credit = $0.10
|
|
@@ -9,7 +9,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
|
|
|
9
9
|
|
|
10
10
|
setup(
|
|
11
11
|
name="predev-api",
|
|
12
|
-
version="0.
|
|
12
|
+
version="0.12.0",
|
|
13
13
|
author="Pre.dev",
|
|
14
14
|
author_email="support@pre.dev",
|
|
15
15
|
description="Python client for the Pre.dev Architect API - Generate comprehensive software specifications",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|