Metadata-Version: 2.5
Name: granoladb
Version: 0.1.0
Summary: Store your logs and agent traces in Granola. The meeting-notes app. Yes, really.
Project-URL: Homepage, https://github.com/that-guy-wade/granoladb
Project-URL: Repository, https://github.com/that-guy-wade/granoladb
Author: GranolaDB
License: MIT
License-File: LICENSE
Keywords: database,granola,logging,observability,satire
Classifier: Development Status :: 4 - Beta
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: System :: Logging
Requires-Python: >=3.10
Requires-Dist: keyring>=24
Requires-Dist: pycrdt>=0.14
Provides-Extra: capture
Requires-Dist: mitmproxy>=11; extra == 'capture'
Provides-Extra: dev
Requires-Dist: cryptography>=42; extra == 'dev'
Requires-Dist: mitmproxy>=11; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Provides-Extra: login
Requires-Dist: cryptography>=42; extra == 'login'
Provides-Extra: otel
Requires-Dist: opentelemetry-sdk>=1.20; extra == 'otel'
Description-Content-Type: text/markdown

# GranolaDB

> The database that runs on your meeting notes.

Stop paying for S3, CloudWatch, and Datadog. GranolaDB stores your logs and agent
traces in [Granola](https://granola.ai) — the AI meeting-notes app — as meeting
notes. Infinitely scalable*. Fully searchable**. Your traces finally get an AI
summary.

## Install

```bash
pip install granoladb            # core
pip install granoladb[otel]      # + OpenTelemetry span exporter
pip install granoladb[capture]   # + `granoladb login --capture` (macOS)
```

Then get your Granola access token. On macOS, GranolaDB grabs it for you:

```bash
granoladb login --capture
```

This intercepts your own Granola app's traffic (via mitmproxy's local mode) and
lifts the `Authorization: Bearer` token out of it — because Granola encrypts the
token on disk and its network client ignores every proxy, so there's genuinely no
politer way to get it. You'll approve a one-time macOS network-extension prompt,
then click around a couple of notes so the app makes a request. Yes: **GranolaDB
MITMs your own Granola to obtain a token Granola won't hand out.** That is the joke,
and it is load-bearing.

The token is a short-lived session JWT (expires in a few hours) — rerun `--capture`
when it does. Or, if you have a token another way, cache it directly:

```bash
granoladb login <YOUR_TOKEN>     # store it (OS keychain)
export GRANOLA_TOKEN="..."       # or per-shell, nothing stored
granoladb logout                 # forget the stored token
```

(There's also an experimental `granoladb login --auto` that tries to decrypt the
token straight from the app's local store, but Granola's on-disk crypto changes
often, so don't count on it.)

### Exactly how `--capture` works (no hidden magic)

We think you should know precisely what this does before running it:

1. It launches **mitmproxy in "local mode"** pointed at the **Granola process** only.
   mitmproxy installs a **macOS network extension** ("Mitmproxy Redirector") that you
   approve once — it reroutes *only Granola's* traffic through mitmproxy.
2. **Why intercept at all:** Granola encrypts the token on disk and its API client
   ignores every proxy (system, env, VPN), so there is no file to read or setting to
   flip. Intercepting the app's own traffic is the only way to obtain it.
3. **What it reads:** when Granola calls `api.granola.ai`, the request carries an
   `Authorization: Bearer <token>` header. `--capture` extracts **only** that value.
4. **Where it goes:** stored **locally only** — in your **OS keychain** (macOS
   Keychain / Windows Credential Manager / Freedesktop Secret Service) via the
   `keyring` library. If no keychain backend exists, it falls back to a `0600` file
   at `~/.granoladb/token` and tells you it did so. **Nothing is sent anywhere.**
   The mitmproxy process and its temporary capture file are torn down immediately.
5. **What the token is:** your own short-lived Granola **session JWT** — not your
   password, not a refresh token. `granoladb logout` deletes it.

The whole capture flow is in [`src/granoladb/capture_macos.py`](src/granoladb/capture_macos.py)
and storage is in [`src/granoladb/auth.py`](src/granoladb/auth.py) — read them.

## Quickstart

```python
import logging, granoladb

logging.getLogger().addHandler(granoladb.GranolaHandler())
logging.error("payment failed")   # this is now a meeting
```

```python
from granoladb import Client
db = Client()
db.put({"level": "ERROR", "msg": "payment failed", "svc": "api"})
print(db.query(contains="payment"))
```

## vs. legacy databases

| Feature            | Postgres | S3   | GranolaDB               |
| ------------------ | -------- | ---- | ----------------------- |
| Durability         | Yes      | Yes  | "probably"              |
| Query latency      | ms       | ms   | one flush interval      |
| AI summary of rows | No       | No   | **Yes**                 |
| Retention policy   | You set  | You  | when Granola archives it|
| Production ready   | Yes      | Yes  | **absolutely not**      |

\* limited by Granola's rate limits, which we call horizontal scaling.
\** limited by Granola's search bar.

## How it works

Records are batched into "meetings." Each batch becomes one Granola document — JSON
lines under a `GRANOLADB v1` marker, written via Granola's internal
`/v1/create-document` endpoint and read back via `/v1/get-documents`. Reads decode
the records out. This is a joke. Do not put anything real in it.

Auth: `granoladb login --capture` (see above) stores your token in the OS keychain,
or set `GRANOLA_TOKEN`. Set `GRANOLADB_DRY_RUN=1` to print calls instead of writing
to Granola.

## License

MIT
