# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

`appliku-cli` is a Python CLI and SDK for the [Appliku](https://api.appliku.com) platform. It wraps the Appliku OpenAPI spec (`https://api.appliku.com/api/schema/`) and is published to PyPI. The project uses **UV** for all Python environment and dependency management.

## Environment & Tooling

- **Python package manager**: UV exclusively. Never use `pip install` directly. Use `uv add` for dependencies, `uv run` to execute commands within the project environment.
- **Project config**: `pyproject.toml` is the single source of truth for metadata, dependencies, scripts, and tool configuration.
- **Build backend**: `hatchling` as specified in `pyproject.toml`.

## Common Commands

```bash
# Create/sync the environment
uv sync

# Run the CLI
uv run appliku --help

# Run tests
uv run pytest

# Run a single test file or test by keyword
uv run pytest tests/test_apps.py
uv run pytest -k "test_create_app"

# Lint and format (assuming ruff is configured)
uv run ruff check .
uv run ruff format .

# Type check (if mypy is configured)
uv run mypy src/

# Build the package for PyPI
uv build

# Publish to PyPI (dry run first)
uv publish --dry-run
uv publish
```

## Architecture & Code Organization

The project follows a **src layout**:

```
src/appliku/
├── __init__.py              # Public SDK exports (Appliku class + all models + exceptions)
├── _main.py                 # Appliku class — SDK entry point with lazy resource properties
├── _client.py               # AplikuClient — HTTP client with retry, pagination, error handling
├── _auth.py                 # Token resolution (env/config/param) + login flow + config persistence
├── _exceptions.py           # Exception hierarchy: AplikuError → typed subclasses
├── _models.py               # All Pydantic v2 models (PaginatedResponse, Team, App, Deployment, etc.)
├── resources/               # Resource classes — one per API domain
│   ├── teams.py             # TeamsResource: list, get, create, update, delete
│   ├── apps.py              # AppsResource: list, get, create, update, delete, deploy, config vars
│   ├── deployments.py       # DeploymentsResource: list, get, latest, logs, stream_logs
│   ├── datastores.py        # DatastoresResource: list, get, create, delete, start, stop, restart
│   ├── domains.py           # DomainsResource: list, get, create, delete
│   ├── volumes.py           # VolumesResource: list, get, create, update, delete
│   ├── cron_jobs.py         # CronJobsResource: list, get, create, update, delete
│   ├── clusters.py          # ClustersResource: list, get, create, delete
│   ├── servers.py           # ServersResource: list, get, create_digitalocean, create_ec2, create_custom
│   ├── invites.py           # InvitesResource: list, create, delete, accept, decline
│   └── migrations.py        # MigrationsResource: list, get, run, logs
└── cli/                     # Typer CLI layer
    ├── __init__.py          # Root Typer app + shared helpers (get_client, render_output, render_table)
    ├── auth.py              # Root commands: login, logout, whoami
    ├── teams.py             # teams sub-app
    ├── apps.py              # apps sub-app (list, get, create, update, delete, deploy, config, set-config)
    ├── deployments.py       # deployments sub-app (list, latest, logs --stream)
    ├── datastores.py        # datastores sub-app (list, create, delete, start, stop, restart)
    ├── domains.py           # domains sub-app (list, create, delete)
    ├── volumes.py           # volumes sub-app (list, create, update, delete)
    ├── cron_jobs.py         # crons sub-app (list, create, update, delete)
    ├── clusters.py          # clusters sub-app (list, create, delete)
    ├── servers.py           # servers sub-app (list, create-do, create-ec2, create-custom)
    ├── invites.py           # invites sub-app (list, create, delete, accept, decline)
    └── migrations.py        # migrations sub-app (list, run, logs)
```

### Key Architectural Decisions

1. **API surface is driven by the OpenAPI spec at `https://api.appliku.com/api/schema/`**. The resource model maps to:
   - **Auth**: Token + JWT authentication, API key management
   - **Teams**: Team CRUD, member invitations, cloud credential storage (AWS, DigitalOcean)
   - **Apps**: Application lifecycle — deploy, scale processes, manage domains/SSL, config vars
   - **Infrastructure**: Clusters, build servers, EC2/DigitalOcean instance provisioning
   - **Data Services**: Datastores (databases), backups, volumes, replica promotion
   - **Monitoring**: Logs (app, nginx, deployment, DB migration), stats/metrics
   - **Integrations**: GitHub/GitLab repos, cron jobs, webhooks, one-off commands

2. **CLI commands mirror SDK client methods.** Each top-level resource maps to a Typer sub-app. The SDK client is usable independently of the CLI.

3. **Authentication flow**: Token resolution priority: explicit param → `APPLIKU_TOKEN` env → `~/.config/appliku/config.toml`. Login does a two-step flow: POST `/api/sign_up/` for JWT, then POST `/api/obtain_user_api_key/` for persistent API key.

4. **Output formatting**: CLI commands default to Rich tables. All list commands support `--output json` for scripting.

5. **Error handling**: Typed exception hierarchy with auto-retry on 429 (respect Retry-After) and 5xx (exponential backoff, up to 3 attempts).

6. **Pagination**: `AplikuClient.paginate()` for single page, `paginate_all()` for auto-following next links.

## OpenAPI Schema

The canonical schema is at `https://api.appliku.com/api/schema/`. When adding new endpoints or updating models, fetch the latest schema to verify field names and types.

## Testing

- Tests live in `tests/` and use **pytest**.
- `conftest.py` provides `mock_client` fixture using `httpx.MockTransport` — no network calls.
- Push `httpx.Response` objects onto the responses list to define mock behavior.
- `paginated_response` fixture factory wraps results in standard pagination envelope.
- Integration/e2e tests (if any) should be gated behind `@pytest.mark.integration`.
