Metadata-Version: 2.4
Name: growth-loop-sdk
Version: 0.1.3
Summary: Growth Python SDK — drop-in product analytics for vibe-coded SaaS. Pairs with the Growth MCP server to give Claude Code an AI growth engineer for your Python app.
Project-URL: Homepage, https://growth-loop.dev
Project-URL: Repository, https://gitlab.com/AKnyaZP/metrics
Project-URL: Issues, https://gitlab.com/AKnyaZP/metrics/-/issues
Author: growth-loop.dev
License: MIT
License-File: LICENSE
Keywords: analytics,claude,claude-code,decorators,fastapi,flask,growth-loop,indie-hackers,instrumentation,product-analytics,saas
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Monitoring
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27.0
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Description-Content-Type: text/markdown

# growth-loop-sdk

Python SDK for [growth-loop.dev](https://growth-loop.dev) — Claude
Code-native product analytics for vibe-coded SaaS. The Python module name
is `growth`; the PyPI distribution is `growth-loop-sdk`.

Pairs with [`@growth-loop/mcp-server`](https://www.npmjs.com/package/@growth-loop/mcp-server)
so Claude Code can ask your funnels, retention, and revenue questions
directly with citations.

## Install

```sh
pip install growth-loop-sdk
# or
uv add growth-loop-sdk
```

Requires Python 3.10+.

## Quick start

```python
import os
from growth import Growth, GrowthOptions

growth = Growth(GrowthOptions(
    api_key=os.environ["GROWTH_KEY"],
    host=os.environ.get("GROWTH_HOST", "https://api.growth-loop.dev"),
    environment=os.environ.get("APP_ENV", "production"),
    release=os.environ.get("GIT_COMMIT_SHA"),
))

# Anywhere in your app:
growth.track("signup_completed", {"plan": "pro"})
```

The SDK runs a background worker thread; on `atexit` it flushes pending
events automatically. On serverless platforms (Lambda, Cloud Run) call
`growth.flush()` before the request handler returns.

## FastAPI

```python
from fastapi import APIRouter
from growth import growth_span, growth_step
from growth_setup import growth      # the singleton from above

router = APIRouter()

@router.post("/checkout")
@growth_span(growth, "checkout", distinct_id=lambda body: body.user_id)
async def checkout(body: CheckoutBody):
    # automatically emits checkout.started / .completed / .failed
    # with duration_ms and error metadata
    return await stripe.charge(body)

@router.post("/signup")
@growth_step(growth, "signup_completed", distinct_id=lambda body: body.user_id)
async def signup(body: SignupBody):
    return await create_user(body)
```

## Flask

```python
from flask import Blueprint
from growth import growth_span
from growth_setup import growth

bp = Blueprint("checkout", __name__)

@bp.route("/checkout", methods=["POST"])
@growth_span(growth, "checkout", distinct_id=lambda: g.user.id)
def checkout():
    return stripe.charge(request.json)
```

## Core API

### `Growth(options: GrowthOptions)`

```python
@dataclass
class GrowthOptions:
    api_key: str                       # required
    host: str = "https://api.growth-loop.dev"
    flush_interval: float = 5.0        # seconds
    batch_size: int = 50
    environment: str | None = None     # "production" | "preview" | "development"
    release: str | None = None         # git SHA — used by /diagnose for commit-level attribution
    timeout: float = 5.0               # HTTP timeout
```

| Method | What it does |
|---|---|
| `track(name, properties=None, options=None)` | Enqueue one event. Non-blocking. |
| `identify(IdentifyOptions(distinct_id=..., properties=...))` | Set the current distinct ID + emit `$identify`. |
| `set_distinct_id(id)` | Set the ID without emitting `$identify`. |
| `flush()` | Force-send queued events. **Always call before request returns on serverless.** |
| `shutdown()` | Flush + stop the worker. Called automatically on process exit. |

### `TrackOptions`

```python
@dataclass
class TrackOptions:
    distinct_id: str | None = None     # override the client's default
    timestamp: datetime | None = None  # override the auto-set UTC now
    context: EventContext | None = None
```

## Decorators

The three decorators auto-applied by `claude /init` (the MCP slash-prompt).
Each preserves your function signature; types and docs flow through `@functools.wraps`.

### `@growth_span(client, name, *, properties=None, distinct_id=None)`

Emits `<name>.started`, `<name>.completed` (with `duration_ms`), or
`<name>.failed` (with `error_message`, `error_name`). Works on sync **and**
async functions automatically. Use on anything > 500ms or with non-trivial
failure rate.

```python
@growth_span(growth, "ai.generate", distinct_id=lambda req: req.user_id)
async def generate(req: GenerateRequest):
    return await anthropic.messages.create(...)
```

`distinct_id` may be a literal string or a callable that receives the
wrapped function's args/kwargs and returns the ID.

### `@growth_step(client, funnel_step, *, properties=None, distinct_id=None)`

Single-event funnel marker. Drop on whatever step represents
*progress through your activation*.

```python
@growth_step(growth, "onboarding.invited_team")
def invite_team(team_id: str):
    ...
```

### `growth_track(client, name, properties=None)`

Imperative one-off track. Equivalent to `client.track(name, properties)`,
provided for symmetry with the decorator helpers.

```python
growth_track(growth, "pricing_viewed", {"tier": "pro"})
```

## Recommended event taxonomy

The MCP server's `/diagnose`, `/weekly`, and `/pmf` prompts know these names natively:

| Stage | Events |
|---|---|
| Identity | `signup_completed`, `login_completed`, `$identify` |
| Activation | `onboarding.started`, `onboarding.completed`, `first_<thing>_created` |
| Revenue | `checkout_started`, `checkout_completed`, `subscription_created`, `subscription_canceled`, `payment_failed` |
| Performance | `growth_span(growth, "checkout", ...)`, `growth_span(growth, "ai.generate", ...)` |

## Privacy

- **Never put PII** (email, raw IP, full address, payment details) in
  `properties`. Use `distinct_id` for identity. Backend ClickHouse never
  decrypts or displays `properties` as identity.
- ClickHouse TTL is 12 months by default — events older than that are
  dropped automatically.

## Going deeper

The SDK is the ingest layer. The agent layer (Claude Code + MCP) is what
makes growth-loop different — install the MCP server too:

```sh
claude mcp add growth-loop \
  -e GROWTH_API_KEY=pk_live_<your-key> \
  -e GROWTH_HOST=https://api.growth-loop.dev \
  -- npx -y @growth-loop/mcp-server
```

Then in Claude Code: `/growth-init` (auto-instrument), `/growth-diagnose <metric>`,
`/growth-weekly`, `/growth-pmf`, `/growth-icp`, `/growth-strategy`, `/growth-pivot`.

(The `growth-` prefix is intentional — it keeps our prompts out of the way
of Claude Code's built-in `/init` and any other editor's defaults.)

## License

MIT
