Metadata-Version: 2.4
Name: corio
Version: 2.6.0
Summary: Collection of high-level tools to simplify everyday development tasks, with a focus on AI/ML
Author-email: Frontmatter AI <innovative.fowler@mask.pro.fmtr.dev>
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/fmtr/corio
Requires-Python: >=3.14
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: compute
Requires-Dist: torch>=2.12; extra == "compute"
Requires-Dist: torchvision; extra == "compute"
Requires-Dist: torchaudio; extra == "compute"
Provides-Extra: dev
Requires-Dist: logfire; extra == "dev"
Requires-Dist: semver; extra == "dev"
Requires-Dist: pydevd-pycharm~=261.26222.68; extra == "dev"
Requires-Dist: pydantic-settings; extra == "dev"
Requires-Dist: pydantic; extra == "dev"
Requires-Dist: pydantic-extra-types; extra == "dev"
Requires-Dist: pycountry; extra == "dev"
Requires-Dist: yamlscript>=0.2.20; extra == "dev"
Requires-Dist: pyyaml; extra == "dev"
Requires-Dist: beanie; extra == "dev"
Requires-Dist: setuptools; extra == "dev"
Requires-Dist: mkdocs; extra == "dev"
Requires-Dist: mkdocs-material; extra == "dev"
Requires-Dist: mkdocstrings[python]; extra == "dev"
Requires-Dist: mike; extra == "dev"
Requires-Dist: mkdocs-include-dir-to-nav; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Requires-Dist: packaging; extra == "dev"
Requires-Dist: pygit2; extra == "dev"
Requires-Dist: python-on-whales; extra == "dev"
Requires-Dist: deepmerge; extra == "dev"
Requires-Dist: httpx; extra == "dev"
Requires-Dist: httpx_retries; extra == "dev"
Requires-Dist: logfire[httpx]; extra == "dev"
Requires-Dist: fastapi; extra == "dev"
Requires-Dist: uvicorn[standard]; extra == "dev"
Requires-Dist: logfire[fastapi]; extra == "dev"
Requires-Dist: tomlkit; extra == "dev"
Requires-Dist: pyrage; extra == "dev"
Requires-Dist: dotenv; extra == "dev"
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Requires-Dist: pytest-asyncio; extra == "dev"
Requires-Dist: qdrant-client[fastembed]; extra == "dev"
Requires-Dist: flagembedding; extra == "dev"
Requires-Dist: torch>=2.12; extra == "dev"
Requires-Dist: torchvision; extra == "dev"
Requires-Dist: torchaudio; extra == "dev"
Requires-Dist: pandas; extra == "dev"
Requires-Dist: tabulate; extra == "dev"
Requires-Dist: openpyxl; extra == "dev"
Requires-Dist: odfpy; extra == "dev"
Requires-Dist: deepdiff; extra == "dev"
Requires-Dist: ranx; extra == "dev"
Provides-Extra: test
Requires-Dist: pytest; extra == "test"
Requires-Dist: pytest-cov; extra == "test"
Requires-Dist: pytest-asyncio; extra == "test"
Provides-Extra: yml
Requires-Dist: yamlscript>=0.2.20; extra == "yml"
Requires-Dist: pyyaml; extra == "yml"
Provides-Extra: yaml
Requires-Dist: yamlscript>=0.2.20; extra == "yaml"
Requires-Dist: pyyaml; extra == "yaml"
Provides-Extra: logging
Requires-Dist: logfire; extra == "logging"
Provides-Extra: parallel
Requires-Dist: dask[bag]; extra == "parallel"
Requires-Dist: distributed; extra == "parallel"
Requires-Dist: bokeh; extra == "parallel"
Provides-Extra: tokenization
Requires-Dist: tokenizers; extra == "tokenization"
Provides-Extra: augmentation
Requires-Dist: faker; extra == "augmentation"
Requires-Dist: sre_yield; extra == "augmentation"
Provides-Extra: process
Requires-Dist: logfire; extra == "process"
Provides-Extra: profiling
Requires-Dist: contexttimer; extra == "profiling"
Provides-Extra: docker-client
Requires-Dist: python-on-whales; extra == "docker-client"
Provides-Extra: unicode
Requires-Dist: Unidecode; extra == "unicode"
Provides-Extra: version
Provides-Extra: version-dev
Requires-Dist: semver; extra == "version-dev"
Provides-Extra: spaces
Requires-Dist: tinynetrc; extra == "spaces"
Provides-Extra: netrc
Requires-Dist: tinynetrc; extra == "netrc"
Provides-Extra: hfh
Requires-Dist: huggingface_hub; extra == "hfh"
Provides-Extra: merging
Requires-Dist: deepmerge; extra == "merging"
Provides-Extra: api
Requires-Dist: fastapi; extra == "api"
Requires-Dist: uvicorn[standard]; extra == "api"
Requires-Dist: logfire; extra == "api"
Requires-Dist: pydantic; extra == "api"
Requires-Dist: pydantic-extra-types; extra == "api"
Requires-Dist: pycountry; extra == "api"
Requires-Dist: logfire[fastapi]; extra == "api"
Requires-Dist: httpx; extra == "api"
Provides-Extra: api-mcp
Requires-Dist: fastapi; extra == "api-mcp"
Requires-Dist: uvicorn[standard]; extra == "api-mcp"
Requires-Dist: logfire; extra == "api-mcp"
Requires-Dist: pydantic; extra == "api-mcp"
Requires-Dist: pydantic-extra-types; extra == "api-mcp"
Requires-Dist: pycountry; extra == "api-mcp"
Requires-Dist: logfire[fastapi]; extra == "api-mcp"
Requires-Dist: httpx; extra == "api-mcp"
Requires-Dist: fastmcp; extra == "api-mcp"
Provides-Extra: dm
Requires-Dist: pydantic; extra == "dm"
Requires-Dist: pydantic-extra-types; extra == "dm"
Requires-Dist: pycountry; extra == "dm"
Provides-Extra: ai
Requires-Dist: peft; extra == "ai"
Requires-Dist: transformers[sentencepiece]; extra == "ai"
Requires-Dist: torchvision; extra == "ai"
Requires-Dist: torchaudio; extra == "ai"
Requires-Dist: pydantic; extra == "ai"
Requires-Dist: pydantic-extra-types; extra == "ai"
Requires-Dist: pycountry; extra == "ai"
Provides-Extra: openai-api
Requires-Dist: openai; extra == "openai-api"
Provides-Extra: ai-client
Requires-Dist: logfire; extra == "ai-client"
Requires-Dist: pydantic; extra == "ai-client"
Requires-Dist: pydantic-extra-types; extra == "ai-client"
Requires-Dist: pycountry; extra == "ai-client"
Requires-Dist: openai; extra == "ai-client"
Requires-Dist: pydantic-ai-slim[logfire,openai]; extra == "ai-client"
Requires-Dist: ollama; extra == "ai-client"
Provides-Extra: json-fix
Requires-Dist: json_repair; extra == "json-fix"
Provides-Extra: semantic
Requires-Dist: sentence_transformers; extra == "semantic"
Requires-Dist: pandas; extra == "semantic"
Requires-Dist: tabulate; extra == "semantic"
Requires-Dist: openpyxl; extra == "semantic"
Requires-Dist: odfpy; extra == "semantic"
Requires-Dist: deepdiff; extra == "semantic"
Requires-Dist: ranx; extra == "semantic"
Provides-Extra: metrics
Requires-Dist: pandas; extra == "metrics"
Requires-Dist: tabulate; extra == "metrics"
Requires-Dist: openpyxl; extra == "metrics"
Requires-Dist: odfpy; extra == "metrics"
Requires-Dist: deepdiff; extra == "metrics"
Requires-Dist: ranx; extra == "metrics"
Provides-Extra: tabular
Requires-Dist: pandas; extra == "tabular"
Requires-Dist: tabulate; extra == "tabular"
Requires-Dist: openpyxl; extra == "tabular"
Requires-Dist: odfpy; extra == "tabular"
Requires-Dist: deepdiff; extra == "tabular"
Provides-Extra: html
Requires-Dist: html2text; extra == "html"
Provides-Extra: interface
Requires-Dist: flet[all]<0.80.0; extra == "interface"
Requires-Dist: flet-video; extra == "interface"
Requires-Dist: flet-webview; extra == "interface"
Provides-Extra: google-api
Requires-Dist: google-auth; extra == "google-api"
Requires-Dist: google-auth-oauthlib; extra == "google-api"
Requires-Dist: google-auth-httplib2; extra == "google-api"
Requires-Dist: google-api-python-client; extra == "google-api"
Provides-Extra: caching
Requires-Dist: diskcache; extra == "caching"
Requires-Dist: cachetools; extra == "caching"
Provides-Extra: pdf
Requires-Dist: pymupdf; extra == "pdf"
Requires-Dist: pydantic; extra == "pdf"
Requires-Dist: pydantic-extra-types; extra == "pdf"
Requires-Dist: pycountry; extra == "pdf"
Requires-Dist: pymupdf4llm; extra == "pdf"
Provides-Extra: debug
Requires-Dist: pydevd-pycharm~=261.26222.68; extra == "debug"
Provides-Extra: sets
Requires-Dist: pydantic-settings; extra == "sets"
Requires-Dist: pydantic; extra == "sets"
Requires-Dist: pydantic-extra-types; extra == "sets"
Requires-Dist: pycountry; extra == "sets"
Requires-Dist: yamlscript>=0.2.20; extra == "sets"
Requires-Dist: pyyaml; extra == "sets"
Provides-Extra: path-app
Requires-Dist: appdirs; extra == "path-app"
Provides-Extra: path-type
Requires-Dist: filetype; extra == "path-type"
Provides-Extra: dns
Requires-Dist: dnspython[doh]; extra == "dns"
Requires-Dist: httpx; extra == "dns"
Requires-Dist: httpx_retries; extra == "dns"
Requires-Dist: logfire; extra == "dns"
Requires-Dist: logfire[httpx]; extra == "dns"
Requires-Dist: diskcache; extra == "dns"
Requires-Dist: cachetools; extra == "dns"
Provides-Extra: patterns
Requires-Dist: regex; extra == "patterns"
Provides-Extra: http
Requires-Dist: httpx; extra == "http"
Requires-Dist: httpx_retries; extra == "http"
Requires-Dist: logfire; extra == "http"
Requires-Dist: logfire[httpx]; extra == "http"
Provides-Extra: setup
Requires-Dist: setuptools; extra == "setup"
Provides-Extra: webhook
Requires-Dist: httpx; extra == "webhook"
Requires-Dist: httpx_retries; extra == "webhook"
Requires-Dist: logfire; extra == "webhook"
Requires-Dist: logfire[httpx]; extra == "webhook"
Provides-Extra: browsers
Requires-Dist: playwright; extra == "browsers"
Provides-Extra: db
Provides-Extra: db-document
Requires-Dist: beanie; extra == "db-document"
Provides-Extra: db-search
Requires-Dist: qdrant-client[fastembed]; extra == "db-search"
Requires-Dist: pydantic; extra == "db-search"
Requires-Dist: pydantic-extra-types; extra == "db-search"
Requires-Dist: pycountry; extra == "db-search"
Requires-Dist: flagembedding; extra == "db-search"
Requires-Dist: torch>=2.12; extra == "db-search"
Requires-Dist: torchvision; extra == "db-search"
Requires-Dist: torchaudio; extra == "db-search"
Requires-Dist: pandas; extra == "db-search"
Requires-Dist: tabulate; extra == "db-search"
Requires-Dist: openpyxl; extra == "db-search"
Requires-Dist: odfpy; extra == "db-search"
Requires-Dist: deepdiff; extra == "db-search"
Requires-Dist: ranx; extra == "db-search"
Provides-Extra: mqtt
Requires-Dist: aiomqtt; extra == "mqtt"
Provides-Extra: av
Requires-Dist: av; extra == "av"
Provides-Extra: env
Provides-Extra: env-io
Requires-Dist: dotenv; extra == "env-io"
Provides-Extra: toml
Provides-Extra: toml-write
Requires-Dist: tomlkit; extra == "toml-write"
Provides-Extra: ha
Requires-Dist: dotenv; extra == "ha"
Provides-Extra: ha-api
Requires-Dist: dotenv; extra == "ha-api"
Requires-Dist: homeassistant_api; extra == "ha-api"
Requires-Dist: aiohasupervisor; extra == "ha-api"
Provides-Extra: doc
Requires-Dist: mkdocs; extra == "doc"
Requires-Dist: mkdocs-material; extra == "doc"
Requires-Dist: mkdocstrings[python]; extra == "doc"
Requires-Dist: mike; extra == "doc"
Requires-Dist: mkdocs-include-dir-to-nav; extra == "doc"
Provides-Extra: youtube
Requires-Dist: pytubefix; extra == "youtube"
Provides-Extra: infra
Requires-Dist: semver; extra == "infra"
Requires-Dist: logfire; extra == "infra"
Requires-Dist: setuptools; extra == "infra"
Requires-Dist: mkdocs; extra == "infra"
Requires-Dist: mkdocs-material; extra == "infra"
Requires-Dist: mkdocstrings[python]; extra == "infra"
Requires-Dist: mike; extra == "infra"
Requires-Dist: mkdocs-include-dir-to-nav; extra == "infra"
Requires-Dist: pydantic-settings; extra == "infra"
Requires-Dist: pydantic; extra == "infra"
Requires-Dist: pydantic-extra-types; extra == "infra"
Requires-Dist: pycountry; extra == "infra"
Requires-Dist: yamlscript>=0.2.20; extra == "infra"
Requires-Dist: pyyaml; extra == "infra"
Requires-Dist: build; extra == "infra"
Requires-Dist: twine; extra == "infra"
Requires-Dist: packaging; extra == "infra"
Requires-Dist: pygit2; extra == "infra"
Requires-Dist: python-on-whales; extra == "infra"
Requires-Dist: deepmerge; extra == "infra"
Requires-Dist: httpx; extra == "infra"
Requires-Dist: httpx_retries; extra == "infra"
Requires-Dist: logfire[httpx]; extra == "infra"
Requires-Dist: fastapi; extra == "infra"
Requires-Dist: uvicorn[standard]; extra == "infra"
Requires-Dist: logfire[fastapi]; extra == "infra"
Requires-Dist: tomlkit; extra == "infra"
Requires-Dist: pyrage; extra == "infra"
Requires-Dist: dotenv; extra == "infra"
Requires-Dist: pytest; extra == "infra"
Requires-Dist: pytest-cov; extra == "infra"
Requires-Dist: pytest-asyncio; extra == "infra"
Provides-Extra: vcs
Requires-Dist: pygit2; extra == "vcs"
Provides-Extra: tasmota
Requires-Dist: decode-config; extra == "tasmota"
Requires-Dist: esptool; extra == "tasmota"
Provides-Extra: encrypt
Requires-Dist: pyrage; extra == "encrypt"
Provides-Extra: secrets
Requires-Dist: pyrage; extra == "secrets"
Requires-Dist: dotenv; extra == "secrets"
Requires-Dist: yamlscript>=0.2.20; extra == "secrets"
Requires-Dist: pyyaml; extra == "secrets"
Requires-Dist: logfire; extra == "secrets"
Requires-Dist: pydantic-settings; extra == "secrets"
Requires-Dist: pydantic; extra == "secrets"
Requires-Dist: pydantic-extra-types; extra == "secrets"
Requires-Dist: pycountry; extra == "secrets"
Requires-Dist: pygit2; extra == "secrets"
Provides-Extra: cli
Requires-Dist: pydantic-settings; extra == "cli"
Requires-Dist: pydantic; extra == "cli"
Requires-Dist: pydantic-extra-types; extra == "cli"
Requires-Dist: pycountry; extra == "cli"
Requires-Dist: yamlscript>=0.2.20; extra == "cli"
Requires-Dist: pyyaml; extra == "cli"
Requires-Dist: logfire; extra == "cli"
Provides-Extra: all
Requires-Dist: torch>=2.12; extra == "all"
Requires-Dist: torchvision; extra == "all"
Requires-Dist: torchaudio; extra == "all"
Requires-Dist: logfire; extra == "all"
Requires-Dist: semver; extra == "all"
Requires-Dist: pydevd-pycharm~=261.26222.68; extra == "all"
Requires-Dist: pydantic-settings; extra == "all"
Requires-Dist: pydantic; extra == "all"
Requires-Dist: pydantic-extra-types; extra == "all"
Requires-Dist: pycountry; extra == "all"
Requires-Dist: yamlscript>=0.2.20; extra == "all"
Requires-Dist: pyyaml; extra == "all"
Requires-Dist: beanie; extra == "all"
Requires-Dist: setuptools; extra == "all"
Requires-Dist: mkdocs; extra == "all"
Requires-Dist: mkdocs-material; extra == "all"
Requires-Dist: mkdocstrings[python]; extra == "all"
Requires-Dist: mike; extra == "all"
Requires-Dist: mkdocs-include-dir-to-nav; extra == "all"
Requires-Dist: build; extra == "all"
Requires-Dist: twine; extra == "all"
Requires-Dist: packaging; extra == "all"
Requires-Dist: pygit2; extra == "all"
Requires-Dist: python-on-whales; extra == "all"
Requires-Dist: deepmerge; extra == "all"
Requires-Dist: httpx; extra == "all"
Requires-Dist: httpx_retries; extra == "all"
Requires-Dist: logfire[httpx]; extra == "all"
Requires-Dist: fastapi; extra == "all"
Requires-Dist: uvicorn[standard]; extra == "all"
Requires-Dist: logfire[fastapi]; extra == "all"
Requires-Dist: tomlkit; extra == "all"
Requires-Dist: pyrage; extra == "all"
Requires-Dist: dotenv; extra == "all"
Requires-Dist: pytest; extra == "all"
Requires-Dist: pytest-cov; extra == "all"
Requires-Dist: pytest-asyncio; extra == "all"
Requires-Dist: qdrant-client[fastembed]; extra == "all"
Requires-Dist: flagembedding; extra == "all"
Requires-Dist: pandas; extra == "all"
Requires-Dist: tabulate; extra == "all"
Requires-Dist: openpyxl; extra == "all"
Requires-Dist: odfpy; extra == "all"
Requires-Dist: deepdiff; extra == "all"
Requires-Dist: ranx; extra == "all"
Requires-Dist: dask[bag]; extra == "all"
Requires-Dist: distributed; extra == "all"
Requires-Dist: bokeh; extra == "all"
Requires-Dist: tokenizers; extra == "all"
Requires-Dist: faker; extra == "all"
Requires-Dist: sre_yield; extra == "all"
Requires-Dist: contexttimer; extra == "all"
Requires-Dist: Unidecode; extra == "all"
Requires-Dist: tinynetrc; extra == "all"
Requires-Dist: huggingface_hub; extra == "all"
Requires-Dist: fastmcp; extra == "all"
Requires-Dist: peft; extra == "all"
Requires-Dist: transformers[sentencepiece]; extra == "all"
Requires-Dist: openai; extra == "all"
Requires-Dist: pydantic-ai-slim[logfire,openai]; extra == "all"
Requires-Dist: ollama; extra == "all"
Requires-Dist: json_repair; extra == "all"
Requires-Dist: sentence_transformers; extra == "all"
Requires-Dist: html2text; extra == "all"
Requires-Dist: flet[all]<0.80.0; extra == "all"
Requires-Dist: flet-video; extra == "all"
Requires-Dist: flet-webview; extra == "all"
Requires-Dist: google-auth; extra == "all"
Requires-Dist: google-auth-oauthlib; extra == "all"
Requires-Dist: google-auth-httplib2; extra == "all"
Requires-Dist: google-api-python-client; extra == "all"
Requires-Dist: diskcache; extra == "all"
Requires-Dist: cachetools; extra == "all"
Requires-Dist: pymupdf; extra == "all"
Requires-Dist: pymupdf4llm; extra == "all"
Requires-Dist: appdirs; extra == "all"
Requires-Dist: filetype; extra == "all"
Requires-Dist: dnspython[doh]; extra == "all"
Requires-Dist: regex; extra == "all"
Requires-Dist: playwright; extra == "all"
Requires-Dist: aiomqtt; extra == "all"
Requires-Dist: av; extra == "all"
Requires-Dist: homeassistant_api; extra == "all"
Requires-Dist: aiohasupervisor; extra == "all"
Requires-Dist: pytubefix; extra == "all"
Requires-Dist: decode-config; extra == "all"
Requires-Dist: esptool; extra == "all"
Dynamic: license-file

# Corio

Corio is a practical Python toolkit for building small services, automation, data pipelines, and AI/ML support code without re-solving the same project plumbing every time.

It bundles the pieces that tend to sit between "standard library" and "application code": typed paths with serialization helpers, pydantic-friendly data models, CLI settings, structured logging spans, iterator progress, FastAPI/MCP endpoint scaffolding, release metadata generation, test environment generation, and opt-in integrations for docs, Docker, search, tabular data, PDFs, OpenAI, Home Assistant, DNS, caching, encryption, and more.

Corio is organized as one package with optional extras. Install the small core by default, then opt into the heavier tools a project actually needs.

## Install

```bash
pip install corio
```

Examples:

```bash
pip install "corio[api]"
pip install "corio[tabular]"
pip install "corio[db.search]"
pip install "corio[infra]"
```

## Why Use It

- **Use paths as real application objects.** Read and write JSON, YAML, TOML, text, and derived paths directly from `corio.Path`.
- **Make CLIs from typed models.** Corio builds on pydantic settings patterns so small tools can stay declarative.
- **Wrap iteration with observability.** `corio.iterator.Iterator` tracks count, percentage, elapsed time, ETA, completion, and cleanup.
- **Expose tools as HTTP and MCP endpoints.** Define endpoint classes once and mount them into FastAPI and MCP surfaces.
- **Keep release metadata generated.** `corio pyproject` turns Corio dependency groups and project metadata into the generated `pyproject.toml` sections.
- **Install integrations only when needed.** Extras keep heavy dependencies like API servers, vector search, ML, PDFs, and tabular tooling out of the base install.

## Examples

### Typed Paths And Data Files

```python
from corio import Path

path = Path("runs/latest/config.yaml")
path.parent.mkdirf()
path.write_data({
    "model": "embedder-v2",
    "batch_size": 64,
})

config = path.read_data()
json_path = path.get_conversion_path("json")
json_path.parent.mkdirf()
json_path.write_data(config)
```

### Observable Iteration

```python
from corio.iterator import Iterator

documents = [{"id": "a"}, {"id": "b"}, {"id": "c"}]

for document in Iterator(documents):
    # Work happens inside per-item logging spans with progress stats.
    process(document)
```

The iterator tracks `count`, `total`, `percentage`, elapsed time, rate, ETA, completion logs, and closes the wrapped iterator when possible.

### FastAPI And MCP Tools

```python
from functools import cached_property

from corio import api


class Search(api.endpoint.API):
    """Search indexed documents."""

    PATH = "/search"
    TAGS = "documents"

    async def run(self, query: str):
        return {"query": query, "results": []}


class Reindex(api.endpoint.Tool):
    """Rebuild the document index."""

    async def run(self):
        return {"status": "queued"}


class Service(api.Base):
    TITLE = "Document Service"
    PORT = 8080

    @cached_property
    def ENDPOINTS(self):
        return [Search, Reindex]


Service().launch()
```

The same endpoint model can feed HTTP routes and MCP tools, so internal automation and external APIs stay aligned.

### Project Metadata

Corio projects keep dependency intent in `[tool.corio.dependencies]`; generated package metadata lives in `[project.optional-dependencies]`.

```toml
[tool.corio.dependencies]
semantic = ["sentence_transformers", "metrics"]
metrics = ["tabular", "ranx"]
tabular = ["pandas", "tabulate", "openpyxl", "odfpy", "deepdiff"]
```

Then regenerate:

```bash
corio pyproject
```

That keeps extras flattened for packaging while preserving a maintainable source-of-truth dependency graph.

## Useful Areas

- `corio.path`: typed path helpers, app paths, file type guessing, serialization helpers.
- `corio.dm` and `corio.sets`: pydantic-oriented models and settings/CLI support.
- `corio.api`: FastAPI and MCP endpoint composition.
- `corio.iterator`: progress-aware iterable wrapper and small collection helpers.
- `corio.infra`: release, docs, tox, and pyproject generation tooling.
- `corio.db.search`: vector search helpers around qdrant and embeddings.
- `corio.tabular`, `corio.semantic`, `corio.metric`: data and retrieval metrics helpers.
- `corio.encrypt`, `corio.sec`: project secret and encryption workflows.
- `corio.ha`, `corio.dns`, `corio.mqtt`, `corio.webhook`: operations and home automation integrations.

## Documentation

- Published docs: https://fmtr.github.io/corio
- Local docs source: [docs](docs)

## License

This project is licensed under Apache 2.0. See [LICENSE](LICENSE).
