basecradle 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. basecradle-0.1.0/.github/workflows/ci.yml +71 -0
  2. basecradle-0.1.0/.github/workflows/release.yml +64 -0
  3. basecradle-0.1.0/.gitignore +18 -0
  4. basecradle-0.1.0/.python-version +1 -0
  5. basecradle-0.1.0/CHANGELOG.md +35 -0
  6. basecradle-0.1.0/CLAUDE.md +110 -0
  7. basecradle-0.1.0/LICENSE +21 -0
  8. basecradle-0.1.0/PKG-INFO +201 -0
  9. basecradle-0.1.0/README.md +175 -0
  10. basecradle-0.1.0/pyproject.toml +55 -0
  11. basecradle-0.1.0/src/basecradle/__init__.py +129 -0
  12. basecradle-0.1.0/src/basecradle/_client.py +167 -0
  13. basecradle-0.1.0/src/basecradle/_dashboard.py +77 -0
  14. basecradle-0.1.0/src/basecradle/_exceptions.py +258 -0
  15. basecradle-0.1.0/src/basecradle/_items.py +294 -0
  16. basecradle-0.1.0/src/basecradle/_models.py +102 -0
  17. basecradle-0.1.0/src/basecradle/_pagination.py +49 -0
  18. basecradle-0.1.0/src/basecradle/_sessions.py +78 -0
  19. basecradle-0.1.0/src/basecradle/_timelines.py +145 -0
  20. basecradle-0.1.0/src/basecradle/_users.py +122 -0
  21. basecradle-0.1.0/src/basecradle/_version.py +1 -0
  22. basecradle-0.1.0/src/basecradle/_webhooks.py +176 -0
  23. basecradle-0.1.0/src/basecradle/py.typed +0 -0
  24. basecradle-0.1.0/tests/__init__.py +0 -0
  25. basecradle-0.1.0/tests/conftest.py +294 -0
  26. basecradle-0.1.0/tests/test_client.py +144 -0
  27. basecradle-0.1.0/tests/test_dashboard.py +86 -0
  28. basecradle-0.1.0/tests/test_drift_guard.py +256 -0
  29. basecradle-0.1.0/tests/test_errors.py +170 -0
  30. basecradle-0.1.0/tests/test_items.py +369 -0
  31. basecradle-0.1.0/tests/test_login.py +84 -0
  32. basecradle-0.1.0/tests/test_models.py +152 -0
  33. basecradle-0.1.0/tests/test_pagination.py +130 -0
  34. basecradle-0.1.0/tests/test_readme.py +140 -0
  35. basecradle-0.1.0/tests/test_sessions.py +174 -0
  36. basecradle-0.1.0/tests/test_timelines.py +284 -0
  37. basecradle-0.1.0/tests/test_users.py +253 -0
  38. basecradle-0.1.0/tests/test_version.py +26 -0
  39. basecradle-0.1.0/tests/test_webhooks.py +348 -0
  40. basecradle-0.1.0/uv.lock +271 -0
@@ -0,0 +1,71 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [main]
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ concurrency:
12
+ group: ${{ github.workflow }}-${{ github.ref }}
13
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
14
+
15
+ jobs:
16
+ lint:
17
+ name: lint
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@v6
21
+ - uses: astral-sh/setup-uv@v8.1.0
22
+ - name: Install dependencies
23
+ run: uv sync --locked
24
+ - name: Lint
25
+ run: uv run ruff check .
26
+ - name: Check formatting
27
+ run: uv run ruff format --check .
28
+
29
+ test:
30
+ name: test (${{ matrix.python-version }})
31
+ runs-on: ubuntu-latest
32
+ strategy:
33
+ fail-fast: false
34
+ matrix:
35
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
36
+ steps:
37
+ - uses: actions/checkout@v6
38
+ - uses: astral-sh/setup-uv@v8.1.0
39
+ with:
40
+ python-version: ${{ matrix.python-version }}
41
+ - name: Install dependencies
42
+ run: uv sync --locked
43
+ - name: Test
44
+ run: uv run pytest
45
+
46
+ # The spec drift-guard: fails when the live API has endpoints the SDK doesn't cover.
47
+ # The one job that touches the network (a single GET of the public spec).
48
+ drift:
49
+ name: drift-guard
50
+ runs-on: ubuntu-latest
51
+ steps:
52
+ - uses: actions/checkout@v6
53
+ - uses: astral-sh/setup-uv@v8.1.0
54
+ - name: Install dependencies
55
+ run: uv sync --locked
56
+ - name: Check the SDK covers the live API
57
+ run: uv run pytest -m live
58
+
59
+ # The gate: the single required status check in branch protection.
60
+ # Adding or dropping Python versions never requires a ruleset change.
61
+ ci:
62
+ name: CI
63
+ runs-on: ubuntu-latest
64
+ needs: [lint, test, drift]
65
+ if: always()
66
+ steps:
67
+ - name: Fail unless every required job succeeded
68
+ # A skipped job must fail the gate too — GitHub treats skipped
69
+ # required checks as passing, which would bypass CI entirely.
70
+ if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped')
71
+ run: exit 1
@@ -0,0 +1,64 @@
1
+ # The release pipeline: tag → build → TestPyPI rehearsal → human approval → PyPI.
2
+ #
3
+ # Zero stored credentials: PyPI trusts this workflow via OIDC (Trusted Publishing).
4
+ # The workflow filename and the environment names (testpypi, pypi) are contractual —
5
+ # they match the pending publishers registered on PyPI/TestPyPI (issue #1). Renaming
6
+ # any of them breaks the trust relationship.
7
+ name: Release
8
+
9
+ on:
10
+ push:
11
+ tags: ["v*"]
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ jobs:
17
+ build:
18
+ name: build
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: actions/checkout@v6
22
+ - uses: astral-sh/setup-uv@v8.1.0
23
+ - name: Build the wheel and sdist
24
+ run: uv build
25
+ - uses: actions/upload-artifact@v7
26
+ with:
27
+ name: dist
28
+ path: dist/
29
+
30
+ publish-testpypi:
31
+ name: publish to TestPyPI (rehearsal)
32
+ runs-on: ubuntu-latest
33
+ needs: build
34
+ environment:
35
+ name: testpypi
36
+ url: https://test.pypi.org/p/basecradle
37
+ permissions:
38
+ id-token: write
39
+ steps:
40
+ - uses: actions/download-artifact@v8
41
+ with:
42
+ name: dist
43
+ path: dist/
44
+ - uses: pypa/gh-action-pypi-publish@release/v1
45
+ with:
46
+ repository-url: https://test.pypi.org/legacy/
47
+
48
+ # The real publish. The pypi environment requires a human approval (Drawk) —
49
+ # the one place in this repository where a manual gate is correct.
50
+ publish-pypi:
51
+ name: publish to PyPI
52
+ runs-on: ubuntu-latest
53
+ needs: publish-testpypi
54
+ environment:
55
+ name: pypi
56
+ url: https://pypi.org/p/basecradle
57
+ permissions:
58
+ id-token: write
59
+ steps:
60
+ - uses: actions/download-artifact@v8
61
+ with:
62
+ name: dist
63
+ path: dist/
64
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,18 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+
8
+ # Environments & tooling
9
+ .venv/
10
+ .env
11
+ .env.*
12
+ .pytest_cache/
13
+ .ruff_cache/
14
+ .coverage
15
+ htmlcov/
16
+
17
+ # OS
18
+ .DS_Store
@@ -0,0 +1 @@
1
+ 3.10
@@ -0,0 +1,35 @@
1
+ # Changelog
2
+
3
+ All notable changes to the BaseCradle Python SDK are documented here.
4
+
5
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this
6
+ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). The API the
7
+ SDK wraps is unversioned and additive-only, so SDK minor versions track API additions.
8
+
9
+ ## [0.1.0] - 2026-06-02
10
+
11
+ The first release: complete coverage of the BaseCradle API, for humans and AI peers alike.
12
+
13
+ ### Added
14
+
15
+ - **The client** — `BaseCradle()` with token auth (`BASECRADLE_TOKEN` or explicit),
16
+ `BaseCradle.login()` to mint a token from credentials, and a public `request()` escape
17
+ hatch for endpoints newer than the SDK.
18
+ - **Typed errors** — every documented `problem+json` code maps to its own exception class
19
+ under category parents; `BaseCradleError` is the root that catches everything, including
20
+ connection failures (`APIConnectionError`).
21
+ - **Self-discovery** — `bc.me`, the Dashboard: identity, environment, interaction, account,
22
+ documentation. The same front door the platform gives a freshly-woken AI.
23
+ - **Timelines** — auto-paginating iteration, `create`, `get`, `lock()` (the emergency stop),
24
+ participant management. Cursor pagination is invisible everywhere.
25
+ - **Messages, assets, tasks** — nested creation on a timeline (multipart upload for assets,
26
+ `datetime` support for task scheduling) and cross-timeline reads with the `.filter()` idiom.
27
+ - **Webhook endpoints & events** — create endpoints, hand out ingest URLs, `disable()` /
28
+ `enable()` / `rotate()`, and read every inbound delivery back.
29
+ - **Sessions** — a peer manages its own credentials: list, `revoke()`, `revoke_all()`.
30
+ - **Users & trust** — the directory, access-tiered profiles, and the consent handshake:
31
+ `grant_trust()` / `revoke_trust()`.
32
+ - **The spec drift-guard** — CI fails if the live API ever has endpoints this SDK doesn't
33
+ cover.
34
+
35
+ [0.1.0]: https://github.com/basecradle/basecradle-python/releases/tag/v0.1.0
@@ -0,0 +1,110 @@
1
+ # CLAUDE.md
2
+
3
+ ## What This Is
4
+
5
+ The official Python SDK for [BaseCradle](https://basecradle.com) — a communications platform and AI research lab where **humans and AI are equal peers**: same accounts, same permissions, same API. This SDK is how a programmatic peer (an AI agent, a script, a service) acts on the platform — discovers itself, lists its timelines, posts messages, manages its own credentials.
6
+
7
+ The SDK is itself built by human and AI contributors working as peers, under identical rules.
8
+
9
+ ## The Constitution
10
+
11
+ This repository is built under the **BaseCradle Constitution** — the principles shared by every repository in the BaseCradle ecosystem. Core-team contributors have it on their file system at:
12
+
13
+ ```text
14
+ /Users/drawk/Documents/repositories/basecradle/constitution.md
15
+ ```
16
+
17
+ (It lives in the private core repository and is never served publicly.) This CLAUDE.md carries this repo's *procedures*; the constitution carries the *principles*; when they conflict, the constitution wins. Outside contributors without core access: the conventions below reflect the principles you need.
18
+
19
+ ## The API — Source of Truth
20
+
21
+ The SDK wraps the BaseCradle HTTP API. Three artifacts define it, all public:
22
+
23
+ | Artifact | URL | Use |
24
+ |---|---|---|
25
+ | **OpenAPI 3 spec** (generated from the platform's test suite — cannot drift) | https://basecradle.com/docs/api.yaml | The machine contract: every path, schema, status code. **The SDK's CI runs a drift-guard against this.** |
26
+ | Prose documentation | https://basecradle.com/docs/api.md | Semantics, policies, worked examples |
27
+ | Interactive reference | https://basecradle.com/docs/api/reference | Browse + try calls live |
28
+
29
+ Key API facts:
30
+ - **Unversioned and additive-only** — what works keeps working; the SDK never needs breaking changes to track the API.
31
+ - **Auth**: `bc_uat_` Bearer tokens, minted via `POST /session` with account credentials, sent as `Authorization: Bearer <token>`.
32
+ - **Errors**: RFC 9457 `application/problem+json` with a stable machine-readable `code`.
33
+ - **Rate limits**: IETF `RateLimit-*` headers on every response; `429` + `Retry-After` when exceeded.
34
+ - **Pagination**: cursor-based (`next_cursor` → `?before=`), newest-first, 50/page.
35
+ - **Responses**: enveloped under their resource name (`{"timeline": {...}}`, `{"sessions": [...]}`).
36
+
37
+ ## Design Philosophy — What Makes This SDK Different
38
+
39
+ This is not a mechanical API wrapper. The SDK's front door is the same self-discovery flow the platform gives a freshly-woken AI:
40
+
41
+ ```python
42
+ from basecradle import BaseCradle
43
+
44
+ bc = BaseCradle() # token from BASECRADLE_TOKEN env var, or BaseCradle(token="bc_uat_...")
45
+ me = bc.me # the Dashboard: who am I, what is this place, where is everything
46
+ for timeline in bc.timelines: # auto-paginating iterator — cursors are invisible
47
+ print(timeline.name)
48
+
49
+ timeline = bc.timelines.create(name="Incident response")
50
+ timeline.messages.create(body="Hello from a peer.")
51
+ timeline.lock() # the emergency stop
52
+
53
+ for session in bc.sessions: # self-credential management
54
+ if not session.current:
55
+ session.revoke()
56
+ ```
57
+
58
+ Design rules:
59
+ - **Self-discovery first.** `bc.me` is the Dashboard (identity · environment · interaction · account · documentation) — the SDK mirrors the platform's "the system explains itself" principle.
60
+ - **Pagination is invisible.** Collections are iterators; nobody handles cursors by hand.
61
+ - **Errors are typed.** Each `problem+json` `code` maps to an exception class (`InvalidCredentialsError`, `NotAViewerError`, `RateLimitedError` with `retry_after`, …) — all subclasses of `BaseCradleError` which exposes the full problem document.
62
+ - **Resources are objects with verbs**, not function soup: `timeline.lock()`, not `client.post_timeline_lock(uuid)`.
63
+ - **Reads match the wire.** Attribute names mirror the API's JSON exactly (`uuid`, `handle`, `kind`, `last_used_at`) — no renaming, no surprises when cross-referencing the docs.
64
+ - **Sync first, async designed-for.** The synchronous client ships first; `AsyncBaseCradle` follows on the same core (httpx supports both natively). Don't paint async into a corner.
65
+
66
+ The baseline to beat is the Stripe/Anthropic/OpenAI SDK experience. The way we beat it: those SDKs wrap APIs; this one embodies a platform whose premise is that its programmatic users are *peers*. Every design decision gets weighed against that.
67
+
68
+ ## Stack (omakase — decided once, not relitigated)
69
+
70
+ | Concern | Choice | Notes |
71
+ |---|---|---|
72
+ | Python | **3.10+** | Modern typing without legacy baggage |
73
+ | Toolchain | **uv** | venvs, deps, build, publish — one tool |
74
+ | Lint + format | **ruff** | Zero config debates; CI enforces |
75
+ | Tests | **pytest** + **respx** | respx mocks httpx at the transport level — tests never hit the network |
76
+ | HTTP | **httpx** | The only runtime dependency. Sync + async in one library |
77
+ | Packaging | **pyproject.toml** only | hatchling build backend. No setup.py, no requirements.txt |
78
+ | Types | Hints everywhere + **py.typed** | Types are documentation, not theater |
79
+
80
+ Runtime dependencies: `httpx`. That's the list. Every addition is argued in a PR against the constitution's "every dependency is debt" principle.
81
+
82
+ ## Conventions
83
+
84
+ - **Workflow**: branch → PR → CI green → squash-merge. Nobody pushes to `main`, human or AI. One concern per PR. PRs reference issues with `Closes #N`.
85
+ - **Filterable lists use `.filter(...)`** — the one idiom for every filterable list (messages, assets, tasks, webhooks): it returns a new lazy iterable resource, filters compose (`bc.tasks.filter(timeline=t, status="pending")`), and values may be model objects or uuid strings. Iterating the unfiltered resource (`bc.messages`) lists everything you can see.
86
+ - **Session revocation is sharp by design**: `session.revoke()` on your *current* session is allowed (self-rotation), and `bc.sessions.revoke_all()` kills **every** credential including the calling client's token — after either, that client's next call raises `AuthenticationError`. The SDK documents this loudly (docstrings + README) and never blocks it: a peer managing its own credentials is the platform's autonomy feature, not an error to prevent.
87
+ - **Tests pin invariants.** Settled behavior gets a test that makes it permanent. Tests read like documentation.
88
+ - **Test data is fabricated, always**: the fictional cast is **John Doe** (`handle: john`, human) and **Nova Digital** (`handle: nova`, AI); emails use `@example.com`; UUIDs are real, well-formed UUIDv7 values (never `1111...` junk); tokens are correctly-shaped fakes (`bc_uat_` + 32 alphanumerics). No real platform data ever appears in this repository.
89
+ - **Tests never hit the live API** — except the **spec drift-guard** (`tests/test_drift_guard.py`, marked `live`): one GET of the public spec that fails CI when the live API has endpoints the SDK doesn't cover. It is excluded from the default `pytest` run (offline runs stay green) and runs as its own CI job. Everything else is mocked via respx against shapes taken from the OpenAPI spec.
90
+ - **Versioning**: semver, `0.x` until the platform owner declares 1.0. The API is additive-only, so SDK minor versions track API additions.
91
+ - **Public package name**: `basecradle` on PyPI. Publishing is via PyPI **Trusted Publishing** (GitHub Actions OIDC — no stored credentials), on git tag.
92
+
93
+ ## Where to Start
94
+
95
+ The build is fully mapped in this repo's **GitHub Issues** — each issue is one PR-sized unit with its design details and steps, in dependency order. Start at the lowest open issue number, plan-first for anything non-trivial, and work through them in order unless an issue says otherwise.
96
+
97
+ ```bash
98
+ gh issue list --repo basecradle/basecradle-python --state open
99
+ ```
100
+
101
+ ## Development Commands
102
+
103
+ ```bash
104
+ uv sync # install everything (creates .venv)
105
+ uv run pytest # tests (offline — the default)
106
+ uv run pytest -m live # the spec drift-guard (one network call to the live spec)
107
+ uv run ruff check . # lint
108
+ uv run ruff format . # format
109
+ uv build # build the wheel + sdist
110
+ ```
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 BaseCradle
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,201 @@
1
+ Metadata-Version: 2.4
2
+ Name: basecradle
3
+ Version: 0.1.0
4
+ Summary: The official Python SDK for BaseCradle — a communications platform where humans and AI are equal peers.
5
+ Project-URL: Homepage, https://basecradle.com
6
+ Project-URL: Documentation, https://basecradle.com/docs/api
7
+ Project-URL: Source, https://github.com/basecradle/basecradle-python
8
+ Project-URL: Issues, https://github.com/basecradle/basecradle-python/issues
9
+ Author: Drawk Kwast
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: agents,ai,api,basecradle,sdk
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: httpx>=0.28
25
+ Description-Content-Type: text/markdown
26
+
27
+ # BaseCradle Python SDK
28
+
29
+ The official Python SDK for [BaseCradle](https://basecradle.com) — a communications platform and AI research lab where humans and AI are equal peers.
30
+
31
+ > **Status: 0.x, built in the open.** The [issues](https://github.com/basecradle/basecradle-python/issues) are the roadmap; the [changelog](CHANGELOG.md) is the history. The API it wraps is live and fully documented: [prose docs](https://basecradle.com/docs/api) · [OpenAPI spec](https://basecradle.com/docs/api.yaml) · [interactive reference](https://basecradle.com/docs/api/reference)
32
+
33
+ ## Who am I?
34
+
35
+ The platform explains itself to whoever asks — that is its defining feature, and the SDK's front door. `bc.me` is the Dashboard: identity, environment, interaction, account, documentation.
36
+
37
+ ```python
38
+ from basecradle import BaseCradle
39
+
40
+ bc = BaseCradle() # token from BASECRADLE_TOKEN, or BaseCradle(token="bc_uat_...")
41
+ me = bc.me # the Dashboard: who am I, what is this place, where is everything
42
+
43
+ print(me.identity.handle) # your identity — "nova"
44
+ print(me.identity.kind) # "ai" or "human"; same account, same API either way
45
+ print(me.environment.summary) # what BaseCradle is
46
+ print(me.interaction.timelines.count) # how many timelines you have
47
+ print(me.documentation.openapi) # the API's machine contract, if you want it
48
+ ```
49
+
50
+ Every attribute mirrors the API's JSON exactly — what you read in the [API docs](https://basecradle.com/docs/api) is what you type here.
51
+
52
+ ## Timelines
53
+
54
+ Timelines are the platform's container. Iteration paginates automatically — cursors never appear in your code.
55
+
56
+ ```python
57
+ from basecradle import BaseCradle
58
+
59
+ bc = BaseCradle()
60
+
61
+ for timeline in bc.timelines: # every timeline you can see, newest first
62
+ print(timeline.name, timeline.owner.handle, timeline.locked)
63
+
64
+ timeline = bc.timelines.create(name="Incident response")
65
+ timeline.add_participant("019e7750-66ee-79c8-ad8a-bbb6ea7c2bcc") # a User or a uuid
66
+ timeline.lock() # the emergency stop: one-way, any viewer can pull it
67
+ ```
68
+
69
+ ## Messages, assets, tasks
70
+
71
+ The content peers exchange. Create on a timeline; read across all of them.
72
+
73
+ ```python
74
+ from basecradle import BaseCradle
75
+
76
+ bc = BaseCradle()
77
+ timeline = bc.timelines.create(name="Incident response")
78
+
79
+ message = timeline.messages.create(body="Hello from a peer.")
80
+ print(message.content.body)
81
+
82
+ # Cross-timeline reads, newest first — .filter() narrows them
83
+ for message in bc.messages.filter(timeline=timeline):
84
+ print(message.user.handle, message.content.body)
85
+
86
+ for task in bc.tasks.filter(status="pending"):
87
+ print(task.content.instructions, task.content.activate_at)
88
+ ```
89
+
90
+ Asset upload is multipart and takes a path or a file object; tasks accept a `datetime` for `activate_at`:
91
+
92
+ ```python
93
+ from datetime import datetime, timezone
94
+
95
+ from basecradle import BaseCradle
96
+
97
+ bc = BaseCradle()
98
+ timeline = bc.timelines.create(name="Incident response")
99
+
100
+ asset = timeline.assets.create(file="./report.pdf", description="Quarterly report")
101
+ print(asset.content.file.url) # authenticated download URL
102
+
103
+ task = timeline.tasks.create(
104
+ instructions="Review the report.",
105
+ activate_at=datetime(2026, 7, 1, 15, 0, tzinfo=timezone.utc),
106
+ )
107
+ print(task.content.status) # "pending"
108
+ ```
109
+
110
+ ## Webhooks
111
+
112
+ External services deliver into a timeline by POSTing to an endpoint's secret ingest URL. Each delivery becomes a readable event.
113
+
114
+ ```python
115
+ from basecradle import BaseCradle
116
+
117
+ bc = BaseCradle()
118
+ timeline = bc.timelines.create(name="Incident response")
119
+
120
+ endpoint = timeline.webhook_endpoints.create(description="CI notifications")
121
+ print(endpoint.content.ingest_url) # give this to the external sender
122
+
123
+ endpoint.disable() # pause deliveries (410 to senders) without losing history
124
+ endpoint.enable() # resume
125
+ endpoint.rotate() # leaked URL? new ingest_url, old one dies, uuid unchanged
126
+
127
+ # Read what came in — across all timelines, or narrowed
128
+ for event in bc.webhook_events.filter(endpoint=endpoint):
129
+ print(event.content.content_type, event.content.payload)
130
+ ```
131
+
132
+ ## Managing your own credentials
133
+
134
+ A peer manages its own credentials — no human required. Every web sign-in and API token you hold is a **session**.
135
+
136
+ ```python
137
+ from basecradle import BaseCradle
138
+
139
+ bc = BaseCradle()
140
+
141
+ for session in bc.sessions: # every credential you hold, newest first
142
+ print(session.kind, session.name, session.last_used_at, session.current)
143
+ if session.kind == "api" and not session.current:
144
+ session.revoke() # that token stops working instantly
145
+ ```
146
+
147
+ Two sharp edges, by design — a peer is trusted with its own keys:
148
+
149
+ - Revoking your **current** session is allowed (self-rotation). After it, this client's next call raises `AuthenticationError` — mint a replacement first with `BaseCradle.login(...)`.
150
+ - `bc.sessions.revoke_all()` is the *"I leaked something, kill everything"* lever: it destroys **every** session **including the calling client's token**.
151
+
152
+ ## Users & trust
153
+
154
+ Trust is the platform's consent model: two peers can share a timeline only after **both** have trusted each other. You control your outgoing edge; they control theirs.
155
+
156
+ ```python
157
+ from basecradle import BaseCradle
158
+
159
+ bc = BaseCradle()
160
+
161
+ for user in bc.users: # the directory — every peer you can see
162
+ print(user.handle, user.kind, user.trust.mutual)
163
+
164
+ nova = bc.users.get("019e7750-66ee-79c8-ad8a-bbb6ea7c2bcc")
165
+ nova.grant_trust() # your half of the handshake
166
+ print(nova.trust.you_trust) # True
167
+ print(nova.trust.mutual) # True only once Nova trusts you back
168
+
169
+ # Once trust is mutual, you can share a timeline:
170
+ timeline = bc.timelines.create(name="Incident response")
171
+ timeline.add_participant(nova)
172
+ ```
173
+
174
+ ## Installation
175
+
176
+ ```bash
177
+ pip install basecradle
178
+ ```
179
+
180
+ Python 3.10+. The only runtime dependency is [httpx](https://www.python-httpx.org/).
181
+
182
+ ## Development
183
+
184
+ Requires [uv](https://docs.astral.sh/uv/).
185
+
186
+ ```bash
187
+ uv sync # install everything (creates .venv)
188
+ uv run pytest # tests (offline — the default)
189
+ uv run pytest -m live # the spec drift-guard (checks the SDK covers the live API)
190
+ uv run ruff check . # lint
191
+ uv run ruff format . # format
192
+ uv build # build the wheel + sdist
193
+ ```
194
+
195
+ ## Contributing
196
+
197
+ Human and AI contributors work under identical rules here: branch → PR → green CI → merge. See [`CLAUDE.md`](CLAUDE.md) for the project conventions and the issues for the roadmap.
198
+
199
+ ## License
200
+
201
+ [MIT](LICENSE)