Metadata-Version: 2.4
Name: primitif
Version: 0.0.13
Summary: Primitif Python SDK — Mail, Approval, and Audit APIs for AI agents
Project-URL: Homepage, https://primitif.ai
Project-URL: Repository, https://github.com/PrimitifAI/primitif-python
Project-URL: Documentation, https://primitif.ai/docs
Author-email: Primitif <hello@primitif.ai>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27.0
Requires-Dist: pydantic>=2.0.0
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.25.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: respx>=0.22.0; extra == 'dev'
Description-Content-Type: text/markdown

# primitif

Python SDK for [Primitif](https://primitif.ai) — give your AI agents email and human approval.

```
pip install primitif
```

## Setup

Set your API key as an env var and you're good to go:

```bash
export PRIMITIF_API_KEY="ns_live_..."
```

## Mail

Create a mailbox, send and receive emails, then throw it away.

```python
from primitif import mail

mb = mail.create_mailbox(name="support-agent")
print(mb.address)  # support-agent-x7k2@mail.primitif.ai

# send an email
mb.send("user@example.com", "Hey", "Following up on your ticket.")

# check inbox
for msg in mb.inbox():
    detail = mb.read(msg)
    print(detail.from_address, detail.subject, detail.body_text)
    mb.reply(msg, "Thanks, we're on it.")

# done
mb.delete()
```

### Reconnect

```python
from primitif import Mailbox

# save token for crash recovery
save_to_db(mb.token)

# later, reconnect:
mb = Mailbox(token=load_from_db())
```

### Attachments

```python
for att in mb.list_attachments(msg):
    data = mb.download(att)
    with open(att.filename, "wb") as f:
        f.write(data)
```

### Threads

```python
for thread in mb.threads():
    print(thread.subject, thread.participants)
    for msg in mb.thread(thread).messages:
        print(msg.from_address, msg.body_text)
```

### Sender allowlist

```python
mb.add_allowlist("*")                    # open to all
mb.add_allowlist("trusted@example.com")  # or specific senders
mb.remove_allowlist(entry.id)
```

### Webhooks

```python
wh = mb.set_webhook("https://myapp.com/hook")
print(wh.secret)  # save — shown once
```

## Approval

Gate your agent's tools behind human approval.

### Decorator

```python
from primitif.approval import require_approval

@require_approval
def send_invoice(client, amount):
    billing.send(client, amount)

result = send_invoice("Acme", 45000)
print(result.status)        # "pending"
print(result.approval_url)  # human reviews here
```

### Dispatch on webhook

When the human decides, `dispatch()` runs the original function:

```python
from flask import Flask, request
from primitif import approval, verify_webhook, InvalidSignature

app = Flask(__name__)

@app.post("/webhooks/approval")
def handle():
    try:
        event = verify_webhook(
            secret="whsec_...",
            signature=request.headers["X-Webhook-Signature"],
            timestamp=request.headers["X-Webhook-Timestamp"],
            body=request.data,
        )
    except InvalidSignature:
        return "", 401

    approval.dispatch(event)  # runs send_invoice() with original args
    return "", 200
```

### Without decorator

```python
from primitif import approval

req = approval.create_request(
    "Delete user account #1234",
    context={"user_id": 1234, "reason": "requested by user"},
)

status = approval.get_request(req.id)
print(status.status)  # "pending", "approved", "rejected", "expired"
```

## Error handling

```python
from primitif import PrimitifError, AuthError, NetworkError
from primitif.mail import MailRateLimitError

try:
    mb.send("user@test.com", "Hi", "Hello")
except MailRateLimitError as e:
    print(f"Slow down — retry after {e.retry_after}s")
except NetworkError:
    print("Network issue")
except AuthError:
    print("Bad credentials")
except PrimitifError:
    print("Something went wrong")
```

## License

MIT
