Metadata-Version: 2.5
Name: neocrew-connector
Version: 0.1.0
Summary: Typed client for the Crewlink API, for your backend, generated from its OpenAPI document.
Project-URL: Homepage, https://github.com/WowLabz/crewlink
Project-URL: Source, https://github.com/WowLabz/crewlink/tree/main/packages/connector-py
Author: Neocrew AI
License-Expression: MIT
License-File: LICENSE
Keywords: api,connectors,crewlink,integrations,neocrew,oauth,sdk
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: anyio>=3.0
Requires-Dist: httpx>=0.21.2
Requires-Dist: pydantic>=2.0
Requires-Dist: typing-extensions>=4.0.0
Description-Content-Type: text/markdown

# neocrew-connector

A typed client for the Crewlink API, for your Python backend. It holds your API
key and reaches every public operation. Generated with [Fern](https://buildwithfern.com)
from the API's own OpenAPI document — the same document `@neocrew/connector` is
generated from, and the two SDKs name the same things the same way: one instance
per environment, every resource a property, every operation a method, every
request and response a pydantic model, and every refusal an exception carrying
the API's own error body.

```bash
pip install neocrew-connector   # or: uv add neocrew-connector
```

```python
import os
from neocrew_connector import Crewlink

crewlink = Crewlink(token=os.environ["CREWLINK_API_KEY"], base_url="https://crewlink.example.com")

session = crewlink.connect.create_session(principal_id="user_8471", allowed_integrations=["github"])
print(session.data.connect_url)

page = crewlink.connections.list(limit=50)
for connection in page.data:
    print(connection.id, connection.status)
```

A **principal** is whoever a connection belongs to — a person, a service or an
agent — named by whatever `principal_id` you choose. An API key must send it on
`connect.create_session`, `secrets.create`, `secrets.put` and `secrets.read_env`
(`400 invalid_body` without); a principal token may omit it, and a different
value is `403 principal_mismatch`.

Set `CREWLINK_API_KEY` and `CREWLINK_BASE_URL` and both arguments can be omitted
— the same two names the TypeScript client reads.

## Errors

A refused call raises. Each status has its own class, all subclasses of
`neocrew_connector.core.ApiError`, and `.body` is the API's error shape:

```python
from neocrew_connector.errors import NotFoundError

try:
    crewlink.connections.get(id="conn_missing")
except NotFoundError as e:
    print(e.body["error"]["code"])   # "not_found"
```

Retries on `429` and `5xx` are built in (two by default; `max_retries=` to
change it, `timeout=` for the deadline).

## Async

`AsyncCrewlink` is the same client with `await`:

```python
from neocrew_connector import AsyncCrewlink

crewlink = AsyncCrewlink(token=..., base_url=...)
page = await crewlink.connections.list(limit=50)
repos = (await crewlink.proxy.for_(integration_key="github", principal_id="user_8471").aget("/user/repos")).json()
```

## The proxy

Call a provider's own API as one connected account. You write the provider's own
path and body — the ones its docs show — and Crewlink attaches the credential.

```python
github = crewlink.proxy.for_(integration_key="github", principal_id="user_8471")
repos = github.get("/user/repos", params={"per_page": 100}).json()
github.post("/repos/acme/api/issues", {"title": "Ship it"})
```

Every method answers the provider's own `httpx.Response`, untouched: a 404 or a
429 is the provider's, so its docs and your own error handling still apply. Only
a request that never reached the provider comes back as Crewlink's error shape.

Name the connection by `connection_id` or by `principal_id` — a principal has at
most one connection per integration, so both reach the same row; the second only
saves you storing the id. A user who never finished OAuth answers 404 either way
— that is the moment to mint a connect session.

For a vendor SDK that takes an `httpx` client, hand it the transport and it
works unchanged, never seeing a token:

```python
import httpx
client = httpx.Client(transport=github.transport, base_url="https://api.github.com")
```

## Principal tokens

A principal token is the credential you hand to the principal's own device: a
page in your product, a phone app, or an agent's MCP client. Mint one from your
backend, per session, with the `principal_id` taken from your own session — never
your API key:

```python
minted = crewlink.principal_tokens.create(name="web session", principal_id=request.user.id, scope="read")
# minted.data.token → hand to the page; minted.data.mcp_url → hand to an agent
```

The device then uses `@neocrew/connector-client` (TypeScript). There is no
Python client for principal tokens: a Python process holding one is a backend,
and a backend should hold the API key instead.

## Regenerating

`src/neocrew_connector` is generated, never edited — except `client.py` and `_proxy.py`,
the proxy, which no generator can express and which `.fernignore` keeps. From
the repo root:

```bash
npm run sdk:generate
```

That exports the document the API serves at `/docs/json` and regenerates all
three SDKs from it. Which operations belong here is decided by the API itself: it
stamps `x-fern-audiences: [public]` on every operation a `ck_` key may call, and
the SDK's method names with `x-fern-sdk-group-name` / `x-fern-sdk-method-name`,
so this package and the TypeScript one cannot disagree. The generator is pinned
in `../fern/generators.yml` and runs in Docker.

## Releasing

Bump `version` in `pyproject.toml`, then push a tag that names it:

```bash
git tag neocrew-connector-v0.1.1 && git push origin neocrew-connector-v0.1.1
```

`.github/workflows/publish-python.yml` runs the tests, builds the wheel, proves
it installs and imports in a clean environment, and publishes it through PyPI's
Trusted Publishing — the workflow's own identity, no token stored anywhere. A
tag whose number disagrees with `pyproject.toml` is refused before upload; a
version already on PyPI can never be re-uploaded, only followed.

MIT licensed.
