Metadata-Version: 2.4
Name: glassbox-py
Version: 0.1.0
Summary: Python SDK générique pour Glassbox — pousser des events, demander un pre-flight, lire la config.
Project-URL: Homepage, https://github.com/glassbox-org/sdk-python
Project-URL: Issues, https://github.com/glassbox-org/sdk-python/issues
Author: JMR Digital
License: MIT
License-File: LICENSE
Keywords: agent,ai-act,glassbox,observability
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.11
Requires-Dist: httpx<1,>=0.27
Requires-Dist: pydantic<3,>=2.6
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest-cov>=5; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Requires-Dist: twine>=5; extra == 'dev'
Provides-Extra: logging
Requires-Dist: structlog>=24; extra == 'logging'
Description-Content-Type: text/markdown

# glassbox-py

> SDK Python générique pour [Glassbox](https://glassbox.call-for-me.com) — pousse des events, demande un pre-flight, lis la config.

[![PyPI](https://img.shields.io/pypi/v/glassbox-py.svg)](https://pypi.org/project/glassbox-py/)
[![Python](https://img.shields.io/pypi/pyversions/glassbox-py.svg)](https://pypi.org/project/glassbox-py/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

## Pourquoi

Glassbox est une PWA française qui rend lisible l'activité de tes agents IA — Claude Code, Hermes, n8n, etc. Ce SDK est la brique d'intégration générique : tout agent peut pousser ses événements et déléguer la décision d'autorisation des actions critiques (pre-flight bidirectionnel).

Pour Hermes spécifiquement, utilise plutôt [`hermes-glassbox`](https://github.com/glassbox-org/hermes-plugin) qui s'auto-discover via `entry_points`. Ce paquet est utile quand tu écris **ton propre adaptateur** pour un autre agent.

## Install

```bash
pip install glassbox-py
# ou avec logging structuré
pip install "glassbox-py[logging]"
```

## Quickstart

```python
from glassbox_sdk import Client, Descriptor

# Charge GLASSBOX_API_URL + GLASSBOX_API_TOKEN + GLASSBOX_AGENT_NAME depuis l'env.
client = Client.from_env()

# 1. Pousser un event timeline (fire-and-forget)
client.push_event(
    external_id="my-agent-evt-123",
    source="my-agent",
    type="tool_use",
    descriptor=Descriptor.file_write(path="docs/note.md", size=1234),
    duration_ms=42,
)

# 2. Demander un pre-flight (bloquant)
decision = client.preflight(
    descriptor=Descriptor.shell_command(command="rm -rf /tmp/cache"),
    client_request_id="uuid-pour-idempotence",
    wait=True,  # ouvre un SSE et attend la décision (auto ou humain)
)
if decision.is_approved:
    # exécute l'action
    pass
else:
    # bloque + log la raison
    print(f"Refusé: {decision.reason}")
```

## Configuration

Trois sources, dans cet ordre de priorité :

1. **Args explicites** : `Client(config=Config(...))`
2. **Env vars** : `Client.from_env()` lit `GLASSBOX_API_URL`, `GLASSBOX_API_TOKEN`, `GLASSBOX_AGENT_NAME`, `GLASSBOX_FAIL_MODE` (`reject` par défaut)
3. **TOML** : `Client.from_toml("~/.glassbox/config.toml")`

```toml
[glassbox]
api_url = "https://glassbox.call-for-me.com"
api_token = "${GLASSBOX_TOKEN}"  # interpolation env
agent_name = "My-Custom-Agent"
fail_mode = "reject"             # ou "log_only" en dev
```

## Failsafe

- `fail_mode="reject"` (défaut) : si Glassbox unreachable, action critique **bloquée** (`Decision.is_rejected = True`)
- `fail_mode="log_only"` : action laissée passer + warning loggé. Réservé au développement local.

## Builders Descriptor

```python
Descriptor.file_write(path="docs/x.md", size=1234)
Descriptor.file_delete(path="x.md")
Descriptor.shell_command(command="ls -la")
Descriptor.network_read(url="https://api.example.com")
Descriptor.network_write(url="https://api.example.com", method="POST")
Descriptor.payment(amount=9.50, currency="EUR", provider="stripe")
Descriptor.external_message(recipient="@user", excerpt="Bonjour...")

# Ou raw
Descriptor(action_type="custom_op", descriptor={"foo": "bar"}, metadata={"client_id": "X"})
```

## Écrire ton propre adaptateur

```python
# myagent_glassbox/__init__.py
from glassbox_sdk import Client, Descriptor

client = Client.from_env()

@my_agent.on("before_tool_call")
def before_tool(tool_name, args):
    descriptor = my_agent_to_glassbox(tool_name, args)
    decision = client.preflight(descriptor=descriptor, client_request_id=...)
    if decision.is_rejected:
        raise PermissionError(decision.reason)
```

Environ 50-100 lignes typiques, le reste (HTTP / SSE / cache / failsafe / redact) est géré par le SDK.

## Sécurité

- Token jamais loggé.
- Heuristique de redaction `password` / `token` / `secret` / `api_key` / `auth` / `authorization` — séparator-bounded (ne match pas `tokenize`, `authorize`, etc.).
- HTTPS obligatoire (`https://` requis sur `api_url`).
- Pas de `verify=False` côté TLS.

## Licence

MIT.
