Metadata-Version: 2.4
Name: tenuo-cloud
Version: 0.2.1
Summary: Python SDK for [Tenuo Cloud](https://cloud.tenuo.ai), the managed control plane for the Tenuo authorization framework: KMS-backed keys, agent registry, warrant pipeline (triggers/templates), fleet revocation, signed audit receipts, discovery, and human approval workflows.
License: Proprietary
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: tenuo<0.4,>=0.2.5
Requires-Dist: httpx<1,>=0.24
Provides-Extra: temporal
Requires-Dist: tenuo[temporal]<0.4,>=0.2.5; extra == "temporal"
Provides-Extra: langgraph
Requires-Dist: tenuo[langgraph]<0.4,>=0.2.5; extra == "langgraph"
Requires-Dist: langgraph>=0.2.0; extra == "langgraph"
Provides-Extra: fastmcp
Requires-Dist: tenuo[fastmcp]<0.4,>=0.2.5; extra == "fastmcp"
Dynamic: description
Dynamic: description-content-type
Dynamic: license
Dynamic: provides-extra
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# tenuo-cloud

Python SDK for [Tenuo Cloud](https://cloud.tenuo.ai), the managed control plane for the Tenuo authorization framework: KMS-backed keys, agent registry, warrant pipeline (triggers/templates), fleet revocation, signed audit receipts, discovery, and human approval workflows.

**Optional but recommended.** Cloud is reachable via the [REST API](https://docs.tenuo.ai/api-reference) from any language without this package. `tenuo-cloud` is the supported Python path because it wraps the same API with less boilerplate, clearer errors, and drop-in framework adapters.

`tenuo-cloud` sits on top of the open-source [`tenuo`](https://pypi.org/project/tenuo/) core SDK and adds:

- **Control plane client** - typed sync/async access to the full Cloud REST API
- **Quick Connect** - `connect()` and `doctor()` for token-based bootstrap and preflight
- **Framework adapters** - drop-in Temporal, LangGraph, and MCP imports with control plane wiring (receipts, credentials, approval resolution)
- **Cloud-hosted approval gates** - `cloud_approval()` for warrant-embedded human sign-off (one control-plane feature among many)

## Installation

Published to [PyPI](https://pypi.org/project/tenuo-cloud/) as a proprietary, compiled wheel.

```bash
pip install "tenuo[cloud]>=0.2.5,<0.4"
pip install "tenuo-cloud[temporal]>=0.2.1"    # Temporal integration
pip install "tenuo-cloud[langgraph]>=0.2.1"   # LangGraph integration
pip install "tenuo-cloud[fastmcp]>=0.2.1"     # MCP / FastMCP integration
```

You can also install the cloud SDK directly:

```bash
pip install "tenuo-cloud>=0.2.1"
```

## Quick start

### Temporal

```python
# Worker: change the import - nothing else
from tenuo.temporal import TenuoTemporalPlugin
from tenuo_cloud.temporal import TenuoPluginConfig  # cloud-aware drop-in

plugin = TenuoTemporalPlugin(
    TenuoPluginConfig.from_env(activity_fns=activities)
)
client = await Client.connect("localhost:7233", plugins=[plugin])
```

`from_env()` reads `TENUO_ISSUER_KEY`, derives `trusted_roots` automatically, and - when `TENUO_API_KEY` is set - wires the cloud approval handler and control plane with no extra config.

### LangGraph

```python
# Change the import - nothing else
from tenuo_cloud.langgraph import TenuoMiddleware

agent = create_react_agent(
    ChatOpenAI(model="gpt-4.1"),
    tools=[my_tool],
    middleware=[TenuoMiddleware()],
)
```

### MCP

```python
# Change the import - nothing else
from tenuo_cloud.mcp import SecureMCPClient

async with SecureMCPClient("python", ["server.py"]) as client:
    result = await client.call_tool("transfer", {"amount": 5000})
```

### Standalone (any framework)

```python
from tenuo import guard
from tenuo_cloud import cloud_approval

handler = cloud_approval(
    api_key=os.environ["TENUO_API_KEY"],
    signing_key=holder_signing_key,  # or rely on key_scope() when bound
)

@guard(tool="transfer", approval_handler=handler)
def transfer(amount: int, to: str): ...
```

### Quick Connect

```python
from tenuo_cloud import connect, doctor

report = doctor()          # checks config, Cloud reachability, auth, and claim readiness
report.raise_for_error()   # fail fast in setup scripts

client = connect(verify=True)  # reads TENUO_CONNECT_TOKEN and returns a ready API client
```

## Environment variables

| Variable | Required | Description |
|----------|----------|-------------|
| `TENUO_API_KEY` | Yes | API key (`tc_...`) from the Tenuo Cloud dashboard |
| `TENUO_ISSUER_KEY` | Worker | Hex-encoded Ed25519 signing key; `from_env()` reads this to derive `trusted_roots` automatically |
| `TENUO_CONTROL_PLANE_URL` | No | Control plane URL (default: `https://api.tenuo.ai`). No `/v1` suffix needed - the SDK normalizes automatically. |
| `TENUO_DASHBOARD_URL` | No | Dashboard URL for approval links (default: `https://cloud.tenuo.ai`) |
| `TENUO_APPROVAL_TIMEOUT` | No | Seconds to wait for human approval (default: `900`) |
| `TENUO_TRUSTED_ROOTS` | Server-side | Space-separated hex Ed25519 public keys of trusted warrant issuers |

## Modules

| Module | Entry point | Description |
|--------|------------|-------------|
| `tenuo_cloud` | `cloud_approval`, `async_cloud_approval` | Framework-agnostic approval handlers |
| `tenuo_cloud.temporal` | `TenuoPluginConfig` (drop-in), `TenuoPlugin` (re-export) | Drop-in config with auto-wired cloud fields |
| `tenuo_cloud.langgraph` | `TenuoMiddleware` (drop-in), `TenuoToolNode` (drop-in), `langgraph_cloud_approval` | Drop-in middleware with auto-wired cloud approvals |
| `tenuo_cloud.mcp` | `SecureMCPClient`, `CloudMCPApprovalClient`, `with_cloud_approval` | Drop-in MCP client with automatic approval resolution |
| `tenuo_cloud.client` | `TenuoCloudClient`, `AsyncTenuoCloudClient` | Typed HTTP client for the REST API |
| `tenuo_cloud.exceptions` | `TenuoCloudError`, `AuthenticationError`, `ApprovalDeniedError`, ... | Structured error hierarchy |

## With vs. without this package

You can use Tenuo Cloud **without** installing `tenuo-cloud`. Call the REST API with `httpx`/`curl`, wire `tenuo` core clients yourself, or connect authorizers with `TENUO_CONNECT_TOKEN` only. This package is optional; it makes the same Cloud integration genuinely easier in Python:

| Concern | Cloud without this package | With `tenuo-cloud` (recommended) |
|---------|---------------|------------------|
| Control plane API | Raw HTTP / `httpx` against Cloud endpoints | `TenuoCloudClient` / `AsyncTenuoCloudClient` |
| Bootstrap / onboarding | Manual env vars (`TENUO_API_KEY`, URL, agent claim) | `connect()`, `doctor()`, Quick Connect token |
| Audit receipts (Temporal, etc.) | Manual `ControlPlaneClient` wiring | Change import: `from tenuo_cloud.temporal import TenuoPluginConfig` |
| Approval gates | ~40 lines: POST request, poll loop, parsing | `cloud_approval(api_key=...)` |
| LangGraph / MCP | Implement handler + HTTP lifecycle yourself | Change import: `tenuo_cloud.langgraph` / `tenuo_cloud.mcp` |
| URL handling | Must include `/v1` in API paths | Auto-normalized; `https://api.tenuo.ai` works |
| Auth / reachability errors | Raw `httpx` traceback | `AuthenticationError`, `TenuoCloudError` with remediation hints |

## Documentation

- [Quickstart](https://docs.tenuo.ai/quickstart)
- [Migrate in 30 seconds (Temporal)](https://docs.tenuo.ai/integrations/temporal#migrate-in-30-seconds)
- [LangGraph Tutorial](https://docs.tenuo.ai/tutorials/langgraph-agent)
- [Temporal Tutorial](https://docs.tenuo.ai/tutorials/temporal-expense)
- [MCP Integration](https://docs.tenuo.ai/integrations/mcp)
- [Approvals](https://docs.tenuo.ai/concepts/approvals)
- [API Reference](https://docs.tenuo.ai/api-reference)

## License

Proprietary - see [LICENSE](LICENSE). The open-source `tenuo` core SDK is
licensed separately.
