Metadata-Version: 2.5
Name: nocville
Version: 0.4.1
Summary: Nocville Status Pusher SDK for Python
Project-URL: Homepage, https://nocville.com
Author: Nocville Team
License-Expression: MIT
License-File: LICENSE
Keywords: monitoring,noc,nocville,status
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.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.9
Requires-Dist: httpx>=0.24.0
Provides-Extra: async
Requires-Dist: aiohttp>=3.8.0; extra == 'async'
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.20.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Description-Content-Type: text/markdown

# nocville

Python SDK for pushing status updates to Nocville.

## Installation

```bash
pip install nocville
```

For async support:

```bash
pip install nocville[async]
```

## Usage

### Synchronous

```python
from nocville import NocvilleClient, Status, Mood

client = NocvilleClient(
    api_key="your-api-key",
    service_id="my-service",
)

# Push a status update
response = client.push_status(
    status=Status.HEALTHY,
    mood=Mood.HAPPY,
    message="All systems operational",
    metrics={"cpu": 45, "memory": 60},
)

# Quick helpers
client.healthy("Everything is fine")
client.warning("High memory usage")
client.critical("Service down!")
```

### Asynchronous

```python
import asyncio
from nocville.client import AsyncNocvilleClient, Status

async def main():
    client = AsyncNocvilleClient(
        api_url="https://your-nocville-server.com",
        api_key="your-api-key",
        service_id="my-service",
    )

    response = await client.push_status(
        status=Status.HEALTHY,
        message="All systems operational",
    )

asyncio.run(main())
```

## Configuration

| Option        | Type  | Default                | Description                                 |
| ------------- | ----- | ---------------------- | ------------------------------------------- |
| `api_key`     | str   | required               | Team API key for authentication             |
| `api_url`     | str   | `https://nocville.com` | Nocville API URL (override for development) |
| `service_id`  | str   | required               | The service ID this client represents       |
| `timeout`     | float | 5.0                    | Request timeout in seconds                  |
| `retries`     | int   | 3                      | Number of retry attempts on failure         |
| `retry_delay` | float | 1.0                    | Base delay between retries in seconds       |
| `debug`       | bool  | False                  | Enable debug logging                        |

## Status Values

- `Status.HEALTHY` - Service is operating normally
- `Status.WARNING` - Service has non-critical issues
- `Status.CRITICAL` - Service has critical issues
- `Status.UNKNOWN` - Service status cannot be determined

## Mood Values

- `Mood.HAPPY` - NPC appears happy
- `Mood.CONCERNED` - NPC appears concerned
- `Mood.STRESSED` - NPC appears stressed
- `Mood.IDLE` - NPC appears neutral

## License

MIT

## Maps, AI coworkers and shutdown

Both clients accept `map="Ops"` (map name or ID) and the same keyword-only status fields:

```python
from nocville import NocvilleClient, BehaviorHints, CompanionSpec

client = NocvilleClient(api_key="your-api-key", service_id="build-agent", map="Ops")
behavior: BehaviorHints = {"activity": "busy", "busy": "seek-desk"}
companion: CompanionSpec = {
    "screenType": "desk-sign",
    "placement": "on-desk",
    "size": {"w": 3, "h": 1},
    "label": "checkout-api",
}
try:
    client.push_status(
        "healthy", name="Claude", message="Running tests",
        behavior=behavior, companion=companion,
        appearance={"family": "claude"},
        agent_stats={"added": 12, "removed": 3},
    )
finally:
    client.shutdown()
```

`behavior["activity"]` is optional (it is derived from status/mood when omitted). `busy` and
`critical` move the NPC to its `busy` target (desk, `seek-companion` device or equipment);
`idle` follows the `idle` hint; `waiting` freezes the NPC wherever it stands with a raised
hand and never walks it anywhere. Reserve `waiting` for "blocked on a human"; send `busy`
for a degraded (warning) service so it stays at its device.

`agent_stats` becomes `agentStats` on the wire. Behavior and companion dictionaries use
API camelCase keys. `offline=True` immediately removes the NPC. `shutdown()` stops and
drains heartbeats before sending an unknown/idle/offline sentinel, and returns a
`PushResponse`; use `await client.shutdown()` for `AsyncNocvilleClient`.

`auto_offline=True` opts into a best-effort interpreter-exit callback (default `False`).
For async clients this callback uses a synchronous request because the event loop may
already be closed. Prefer explicit shutdown in `finally`; exit callbacks cannot run after
SIGKILL or a crash. Python 3.9 or newer is required.

## Silly office characters

The `BehaviorHints` type supports the built-in office routines. Use matching `kind` and
`behavior.role` values so the character also gets the right appearance:

```python
client.push_status(
    "healthy",
    kind="office-cat",
    behavior={"role": "office-cat", "activity": "idle"},
)
```

Keep sending heartbeats while the character should stay in the office. Available roles are
`receptionist`, `security`, `janitor`, `barista`, `office-cat`, `office-dog`,
`plant-caretaker`, `it-technician`, `out-of-touch-executive`, and `slightly-inappropriate-hr`. The Nocville server runs
movement and dialogue; it must be updated to support these routines. For ready-made CLI
processes, use the `@nocville/npc` npm package.

## Encrypted Cloudflare workspaces

Run `nocville-bridge` from the `@nocville/node` package on the same machine, using
the team's verified workspace bundle and upstream API key. Then:

```python
client = NocvilleClient.from_bridge(
    "/home/me/.config/nocville/bridge.json.client.json", "my-service"
)
client.healthy("Everything is fine")
```

`AsyncNocvilleClient.from_bridge` supports the same configuration. The client file
contains a local bridge token, not the Cloudflare API key. Status and incident
operations are encrypted in the local bridge before they leave the machine.
Loopback requests bypass environment HTTP proxies and do not follow redirects.
Direct Cloudflare team keys are rejected to prevent accidental plaintext delivery.
Protect both the bridge's configuration and its producer profiles.
