# appliku

[![PyPI version](https://badge.fury.io/py/appliku.svg)](https://pypi.org/project/appliku/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)

Python CLI and SDK for the [Appliku](https://appliku.com) cloud deployment platform.

Appliku is a PaaS that lets you deploy applications, manage infrastructure, and scale services — all from a single unified interface. This package provides both a programmatic Python SDK and a fully-featured command-line interface.

## Installation

```bash
pip install appliku
```

UV users:

```bash
uv add appliku
```

## Quick Start (SDK)

```python
from appliku import Appliku

client = Appliku(token="your-api-token")

# List apps in a team
apps = client.apps.list("my-team")
for app in apps.results:
    print(f"{app.name} — {app.status}")

# Trigger a deployment
deployment = client.apps.deploy("my-team", app_id=42)
print(f"Deployment {deployment.id} is {deployment.status}")
```

## Authentication

There are three ways to authenticate:

1. **Constructor parameter**: `Appliku(token="tok_...")`
2. **Environment variable**: `export APPLIKU_TOKEN=tok_...`
3. **CLI login** (saves token to `~/.config/appliku/config.toml`):
   ```bash
   appliku login --email you@example.com --password secret
   ```

The SDK checks these sources in order: explicit token → `APPLIKU_TOKEN` env var → config file.

## SDK Usage

### Teams

```python
# List teams
teams = client.teams.list()

# Create a team
team = client.teams.create("my-team-slug", "My Team")

# Update and delete
client.teams.update("my-team-slug", name="Renamed Team")
client.teams.delete("my-team-slug")
```

### Apps

```python
# List apps
apps = client.apps.list("my-team")

# Create an app
app = client.apps.create(
    "my-team",
    name="web-app",
    branch="main",
    git_url="https://github.com/user/repo.git",
    build_pack="dockerfile",
)

# Deploy
deployment = client.apps.deploy("my-team", app.id)

# Config vars
vars = client.apps.get_config_vars("my-team", app.id)
client.apps.set_config_vars("my-team", app.id, {"DATABASE_URL": "postgres://...", "DEBUG": "false"})
```

### Deployments

```python
# List deployments
deployments = client.deployments.list("my-team", app_id=42)

# Get latest
latest = client.deployments.latest("my-team", app_id=42)

# Stream logs in real-time
for log in client.deployments.stream_logs("my-team", app_id=42, deployment_id=latest.id):
    print(f"[{log.timestamp}] {log.message}")
```

### Datastores

```python
# Create a database
db = client.datastores.create("my-team", app_id=42, name="primary-db", engine="postgres", version="15")

# Lifecycle management
client.datastores.stop("my-team", 42, db.id)
client.datastores.start("my-team", 42, db.id)
client.datastores.restart("my-team", 42, db.id)
```

### Domains & Volumes

```python
# Domains
domain = client.domains.create("my-team", app_id=42, domain="app.example.com")
client.domains.delete("my-team", 42, domain.id)

# Volumes
vol = client.volumes.create("my-team", app_id=42, name="data", container_path="/data")
client.volumes.update("my-team", 42, vol.id, name="user-data")
```

### Cron Jobs

```python
cron = client.cron_jobs.create(
    "my-team", app_id=42,
    name="cleanup", schedule="0 2 * * *", command="python manage.py cleanup"
)
client.cron_jobs.update("my-team", 42, cron.id, is_disabled=True)
```

### Clusters & Servers

```python
# Create a cluster
cluster = client.clusters.create("my-team", name="production")

# Provision servers
do_server = client.servers.create_digitalocean("my-team", name="web-1", region="nyc1", size="s-2vcpu-2gb")
ec2_server = client.servers.create_ec2("my-team", name="worker-1", region="us-east-1", instance_type="t3.large")
custom_server = client.servers.create_custom("my-team", name="bare-metal", host="203.0.113.1")
```

## CLI Usage

### Authentication

```bash
appliku login --email you@example.com --password secret
appliku whoami
appliku logout
```

### Apps

```bash
appliku apps list --team my-team
appliku apps create --team my-team --name web-app --branch main --git-url https://github.com/user/repo.git
appliku apps deploy --team my-team --app 42
appliku apps config --team my-team --app 42
appliku apps set-config --team my-team --app 42 --var FOO=bar --var BAZ=qux
```

### Deployments

```bash
appliku deployments list --team my-team --app 42
appliku deployments latest --team my-team --app 42
appliku deployments logs --team my-team --app 42 --deployment 7 --stream
```

### Datastores

```bash
appliku datastores create --team my-team --app 42 --name primary-db --engine postgres --version 15
appliku datastores start --team my-team --app 42 1
appliku datastores stop --team my-team --app 42 1
```

### All commands support `--output json` for scripting:

```bash
appliku apps list --team my-team --output json
```

## CLI Reference

| Command Group   | Subcommands                                      |
|-----------------|--------------------------------------------------|
| *(root)*        | `login`, `logout`, `whoami`                      |
| `teams`         | `list`, `get`, `create`, `update`, `delete`      |
| `apps`          | `list`, `get`, `create`, `update`, `delete`, `deploy`, `config`, `set-config` |
| `deployments`   | `list`, `latest`, `logs`                         |
| `datastores`    | `list`, `create`, `delete`, `start`, `stop`, `restart` |
| `domains`       | `list`, `create`, `delete`                       |
| `volumes`       | `list`, `create`, `update`, `delete`             |
| `crons`         | `list`, `create`, `update`, `delete`             |
| `clusters`      | `list`, `create`, `delete`                       |
| `servers`       | `list`, `create-do`, `create-ec2`, `create-custom` |
| `invites`       | `list`, `create`, `delete`, `accept`, `decline` |
| `migrations`    | `list`, `run`, `logs`                            |

## Error Handling

The SDK raises typed exceptions so you can handle specific failure modes:

```python
from appliku import Appliku, NotFoundError, AuthenticationError, RateLimitError

client = Appliku()

try:
    app = client.apps.get("my-team", app_id=999)
except NotFoundError as e:
    print(f"App not found: {e}")
except AuthenticationError:
    print("Invalid or expired token")
except RateLimitError:
    print("Too many requests — retried automatically")
```

Exception hierarchy:
- `AplikuError` (base)
  - `AuthenticationError` — 401
  - `AuthorizationError` — 403
  - `NotFoundError` — 404
  - `ValidationError` — 400 (field-level errors)
  - `RateLimitError` — 429 (auto-retried)
  - `ServerError` — 5xx (auto-retried with backoff)

## License

MIT
