Write a plugin.
Share what you build.

shenas is an open-source quantified-self platform. Source plugins bring data in, transforms shape it, dashboards make it visible. Every piece is a plugin you can write, share, and improve.

Welcome

shenas connects to the services people already use -- Garmin, Strava, Spotify, Lunch Money, Google Calendar -- and syncs the data into a local database. Transforms normalize it into canonical metrics. Dashboards visualize it.

Everything is a plugin. If there's a data source you care about, you can write a connector in an afternoon. If there's a way to visualize it, you can build a dashboard in a weekend.

Source plugins

Connect a new API or local file to shenas. dlt handles the pipeline; you define the tables.

Write one →

Transform plugins

SQL, geofence, geocode, regex -- or write your own transform type.

Learn more →

Dashboard plugins

Lit web components that query Arrow IPC and render charts.

Learn more →

Dataset plugins

Define canonical metric schemas that transforms write into.

Learn more →

Write a source plugin

A source plugin is a Python package that defines tables as dataclasses and extracts data from an API. dlt handles incremental loading and schema management. You focus on the API.

1

Scaffold the package

mkdir -p plugins/sources/myapi/shenas_sources/myapi
cd plugins/sources/myapi

# pyproject.toml
cat > pyproject.toml <<'EOF'
[project]
name = "shenas-source-myapi"
dependencies = ["dlt[duckdb]>=1.24.0", "shenas-source-core"]

[project.entry-points."shenas.sources"]
myapi = "shenas_sources.myapi.source:MyApiSource"
EOF
2

Define your tables

from shenas_sources.core.table import EventTable, Field
from typing import Annotated, Any, Iterator

class Activities(EventTable):
    class _Meta:
        name = "activities"
        display_name = "Activities"
        pk = ("id",)

    id: Annotated[str, Field(db_type="VARCHAR")] = ""
    name: Annotated[str, Field(db_type="VARCHAR")] = ""
    duration_s: Annotated[int, Field(db_type="INTEGER", unit="s")] = 0
    time_at: Annotated[str, Field(db_type="TIMESTAMP")] = ""

    @classmethod
    def extract(cls, client: Any, **ctx) -> Iterator[dict]:
        yield from client.get_activities()
3

Create the source class

from shenas_sources.core.source import Source

class MyApiSource(Source):
    name = "myapi"
    display_name = "My API"
    primary_table = "activities"

    def build_client(self):
        return MyApiClient(self.get_config_value("api_key"))

    def resources(self, client):
        from .tables import TABLES
        return [t.to_resource(client) for t in TABLES]
4

Install and sync

uv sync  # installs your plugin as a workspace member
shenasctl source myapi sync

Your data is now in a local DuckDB database, queryable from dashboards and transforms.

Other plugin types

Sources are the most common, but shenas has several plugin kinds. Each follows the same pattern: a Python package with an entry point.

Dataset Define canonical metric tables (daily HRV, transactions, events). Transforms write into these.
Transform SQL, geofence, geocode, regex extract, LLM categorize -- or write your own transform type.
Dashboard Lit web components that query via Arrow IPC. Charts, tables, timelines -- anything visual.
Frontend The UI shell itself is a plugin. Write your own or extend the default.
Theme CSS custom properties that style everything at once. Ship a .css file and a Python class.
Analysis Hypothesis-driven analysis modes. Define an operation vocabulary and recipe strategy.

Share your plugin

Plugins are standard Python wheels. Publish to PyPI and anyone can install with shenasctl source add myapi.

1

Build the wheel

cd plugins/sources/myapi
hatchling build
2

Publish to PyPI

twine upload dist/shenas_source_myapi-*.whl
3

Users install it

shenasctl source add myapi

The CLI verifies the Ed25519 signature (if you sign it), installs the wheel, and the plugin appears in the UI automatically.

Plugin certification

Plugins that meet our quality bar -- tests, documentation, no credential leaks, clean table schemas -- can apply for certified status. Certified plugins get a badge in the UI and are listed in the official plugin directory.

Apply for certification →