Metadata-Version: 2.5
Name: appliku
Version: 0.7.0
Summary: Python CLI and SDK for Appliku
Project-URL: Homepage, https://github.com/appliku/appliku-cli
Project-URL: Documentation, https://github.com/appliku/appliku-cli#readme
Project-URL: Issues, https://github.com/appliku/appliku-cli/issues
Author-email: Appliku <dev@appliku.com>
License-Expression: MIT
License-File: LICENSE
Keywords: appliku,cli,deployment,paas,sdk
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet
Requires-Python: >=3.10
Requires-Dist: attrs>=26.1.0
Requires-Dist: httpx>=0.27.0
Requires-Dist: pydantic[email]>=2.6.0
Requires-Dist: python-dateutil>=2.8.0
Requires-Dist: rich>=13.7.0
Requires-Dist: tomli-w>=1.0.0
Requires-Dist: tomli>=2.0.0; python_version < '3.11'
Requires-Dist: typer>=0.12.0
Provides-Extra: dev
Requires-Dist: httpx>=0.27.0; extra == 'dev'
Requires-Dist: mypy>=1.8.0; extra == 'dev'
Requires-Dist: pytest-httpx>=0.30.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: ruff>=0.3.0; extra == 'dev'
Description-Content-Type: text/markdown

# 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) platform.

This package gives you:

- a CLI for common Appliku workflows
- a Python SDK for scripting and integrations

## Install uv

This project uses [uv](https://docs.astral.sh/uv/) for local development and is easiest to use with uv for both the CLI and SDK examples below.

macOS and Linux:

```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```

Windows PowerShell:

```powershell
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
```

After installation, restart your shell or verify:

```bash
uv --version
```

## Install appliku

For CLI usage, install the package as a tool:

```bash
uv tool install appliku
```

Then verify:

```bash
appliku --help
```

For SDK usage inside a Python project:

```bash
uv add appliku
```

If you prefer `pip`, this also works:

```bash
pip install appliku
```

## Claude Code Skill

If you use [Claude Code](https://claude.ai/code), install the Appliku skill to give the agent full knowledge of every CLI command, SDK method, and common workflow — automatically loaded in every session:

```bash
npx skills add appliku/skill -g -a claude-code
```

Source: [github.com/appliku/skill](https://github.com/appliku/skill)

## Authentication

The CLI supports two login methods:

1. Browser-based device flow:

```bash
appliku login
```

2. Direct token login:

```bash
appliku login --token YOUR_API_TOKEN
```

Useful auth commands:

```bash
appliku whoami
appliku logout
```

The SDK resolves credentials in this order:

1. `Appliku(token="...")`
2. `APPLIKU_TOKEN` environment variable
3. `~/.config/appliku/config.toml`

Example environment variable setup:

```bash
export APPLIKU_TOKEN=YOUR_API_TOKEN
```

## CLI Quick Start

List your teams:

```bash
appliku teams list
```

Inspect one team:

```bash
appliku teams get my-team
```

List applications in a team:

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

Show the latest deployment for an app:

```bash
appliku deployments latest --team my-team --app 42
```

List domains for an app:

```bash
appliku domains list --team my-team --app 42
```

List datastores for an app:

```bash
appliku datastores list --team my-team --app 42
```

Every list-style command also supports JSON output:

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

## CLI Command Reference

### teams

Manage your Appliku teams.

```bash
appliku teams list
appliku teams get my-team
```

### apps

Manage applications and fetch logs.

```bash
# List apps in a team
appliku apps list --team my-team

# Create from GitHub or GitLab. Select exactly one server or cluster target.
appliku apps create github acme/example -t my-team -n example -b main --server 12
appliku apps create gitlab group/example -t my-team -n example -b main --cluster 7

# Create from custom Git. Read credentials from files instead of command arguments.
appliku apps create custom -t my-team -n example -b main --server 12 \
  --git-url-file repository-url.txt --private-key-file ~/.ssh/example_deploy_key

# Deploy (clone + build + release), or restart without a build.
appliku apps deploy --team my-team --app 42
appliku apps restart --team my-team --app 42
appliku apps restart --team my-team --app 42 --check   # is a restart possible now?

# Fetch logs — one command for any deployment mode, one or more processes.
# With no --process it returns logs for all of the app's processes.
appliku apps logs --team my-team --app 42
appliku apps logs --team my-team --app 42 --process web --process celery --tail 200

# `apps service-logs` is deprecated — use `apps logs -p <service>` instead.
# `apps delete-config-var` is deprecated — use `config unset KEY` instead.

# Fetch nginx access logs for a domain
appliku apps nginx-logs --team my-team --app 42 --domain example.com --tail 100

# Fetch load balancer logs for a domain
appliku apps load-balancer-logs --team my-team --app 42 --domain example.com --tail 100
```

Application creation requires exactly one positive `--server` or `--cluster`
ID. Add `--static-site` for a static site and `--output json` for one sanitized
JSON object. For custom Git, use `--git-url-file -` to read a credential-bearing
URL from standard input. The command never prints the URL or private key.

Create makes the application and starts the initial `appliku.yml` inspection.
It does not deploy. Run `appliku apps deploy --team TEAM --app ID` after setup is
complete.

### config

Manage an application's environment variables, Heroku style.

| Heroku                      | Appliku                                     |
|-----------------------------|---------------------------------------------|
| `heroku config`             | `appliku config list -t TEAM -a APP`        |
| `heroku config --json`      | `appliku config list -t TEAM -a APP -o json`  |
| `heroku config --shell`     | `appliku config list -t TEAM -a APP -o shell` |
| `heroku config:get KEY`     | `appliku config get KEY -t TEAM -a APP`     |
| `heroku config:set A=1 B=2` | `appliku config set A=1 B=2 -t TEAM -a APP` |
| `heroku config:unset A B`   | `appliku config unset A B -t TEAM -a APP`   |

```bash
appliku config list -t my-team -a 42             # table: name, value, source, mode
appliku config list -t my-team -a 42 -o json     # {"NAME": "value", ...}
appliku config list -t my-team -a 42 -o shell    # NAME='value' lines (POSIX shell syntax)
appliku config get DATABASE_URL -t my-team -a 42 # prints the value only
appliku config set DEBUG=false URL='p://a?b=c' -t my-team -a 42
appliku config set DEBUG=false -t my-team -a 42 --deploy   # save, then start a deployment
appliku config set DEBUG=false -t my-team -a 42 --restart  # save, then restart without a build
appliku config unset OLD_VAR OTHER -t my-team -a 42
```

- **Changes take effect on the next deployment or restart.** Nothing restarts on
  save. Pass `--deploy` (a full build) or `--restart` (no build: the running
  image starts again with the new values), or run `appliku apps deploy` /
  `appliku apps restart`. Use `--deploy` for variables read during the build
  (for example `NEXT_PUBLIC_*`): a restart keeps their old values. The two flags
  cannot be combined, and neither runs when nothing was written.
- `set` merges: variables you do not name are left alone. Values are split on
  the first `=`.
- `set` refuses an empty value (on Appliku that would delete the variable — use
  `unset`), the same name twice, and, for now, values with leading or trailing
  whitespace (the API would strip it).
- Variables defined in `appliku.yml` cannot be changed here. Database- or
  domain-linked variables are only replaced with a static value when you pass
  `--replace-links`.
- These checks before writing are advisory: they use the state at the time of
  the check. The API's answer is verified, and a key the API did not apply makes
  the command fail, but a variable that someone links to a database between the
  check and the write is replaced without warning.
- `unset` skips names that are not set, with a warning.
- `get` exits 1 when the variable is not set (Heroku prints an empty line).
- `set` and `unset` need an API token with both `env_vars` read and update
  permissions; `--deploy` and `--restart` also need `deployments` create.
- `list` shows values as they resolve now. It does not show volume variables
  (`<NAME>_ROOT`, `<NAME>_URL`) added at deployment time.
- `-o shell` output is for `eval` in a POSIX shell. It is not a dotenv format.

### deployments

Inspect deployments and their logs.

```bash
# List deployments for an app
appliku deployments list --team my-team --app 42

# Show the latest deployment
appliku deployments latest --team my-team --app 42

# Fetch logs for a specific deployment
appliku deployments logs --team my-team --id 1234
```

### domains

Manage custom domains.

```bash
appliku domains list   --team my-team --app 42
appliku domains create --team my-team --app 42 --domain example.com
appliku domains delete --team my-team --app 42 --id 7
appliku domains check-dns --team my-team --app 42 --domain example.com
```

### datastores

Manage attached datastores (databases, caches, etc.).

```bash
appliku datastores list    --team my-team --app 42
appliku datastores start   --team my-team --app 42 --id 5
appliku datastores stop    --team my-team --app 42 --id 5
appliku datastores restart --team my-team --app 42 --id 5
appliku datastores delete  --team my-team --app 42 --id 5
```

### volumes

Manage persistent volumes.

```bash
appliku volumes list   --team my-team --app 42
appliku volumes delete --team my-team --app 42 --id 3
```

### crons

Manage cron jobs.

```bash
appliku crons list   --team my-team --app 42
appliku crons delete --team my-team --app 42 --id 8
```

### clusters

Manage Docker Swarm clusters.

```bash
appliku clusters list   --team my-team
appliku clusters delete --team my-team --id 2
```

`clusters list` reports a `Ready` column: a cluster can host a new application
only when its primary manager has finished setup.

### servers

Inspect servers.

```bash
appliku servers list --team my-team
appliku servers get  --team my-team --id 10
```

Both report a `Ready` column — a server is ready when it is active and its setup
finished. `apps create` refuses a target that is not ready, so pick the ID from
here:

```bash
appliku servers list --team my-team --output json | jq '[.[] | select(.ready)]'
```

### invites

Manage team invitations.

```bash
appliku invites list   --team my-team
appliku invites delete --team my-team --id 4
```

### migrations

Manage and inspect database migrations.

```bash
appliku migrations list --team my-team
appliku migrations logs --team my-team --id 99
```

## SDK Quick Start

Create a client using a saved token or `APPLIKU_TOKEN`:

```python
from appliku import Appliku

client = Appliku()
```

Or pass the token explicitly:

```python
from appliku import Appliku

client = Appliku(token="YOUR_API_TOKEN")
```

List teams:

```python
from appliku import Appliku

client = Appliku()

for team in client.teams.list():
    print(team.team_path, team.plan)
```

Get one team and list its apps:

```python
from appliku import Appliku

client = Appliku()
team = client.teams.get("my-team")
apps = client.apps.list("my-team")

print(team.name)
for app in apps:
    print(app.id, app.name, app.last_deployment_status)
```

Get the latest deployment and fetch its logs:

```python
from appliku import Appliku

client = Appliku()
latest = client.deployments.latest("my-team", 42)
logs = client.deployments.logs("my-team", latest.id)

print(latest.status)
for entry in logs:
    print(entry.log)
```

Work with domains and datastores:

```python
from appliku import Appliku

client = Appliku()

domains = client.domains.list("my-team", 42)
datastores = client.datastores.list("my-team", 42)

print([d.domain for d in domains])
print([db.name for db in datastores])
```

`apps restart` starts the images of the running deployment again with the
current environment variables and process settings. It pulls no new code, builds
nothing and does not run the release command. Variables used during the build
(for example `NEXT_PUBLIC_*`, or anything the build command reads) keep the
values baked into the image; run `apps deploy` to change them. `--check` only
asks whether a restart is possible now and prints the reason when it is not
(exit 1). The command needs the backend with the restart endpoint (it works once
that is on production), and the first restart of an application needs one full
deployment made with that backend. Static sites have nothing to restart.

## SDK Reference

All resources are accessed as properties on the `Appliku` instance.

### apps

```python
client.apps.list("my-team")
client.apps.get("my-team", app_id=42)
client.apps.create_from_github(
    "my-team", "acme/example", name="example", branch="main", server=12
)
client.apps.create_from_gitlab(
    "my-team", "group/example", name="example", branch="main", cluster=7
)
client.apps.create_from_custom_git(
    "my-team",
    "git@example.com:team/example.git",
    name="example",
    branch="main",
    server=12,
    private_key=private_key_contents,
)
client.apps.update("my-team", app_id=42, branch="develop")
client.apps.delete("my-team", app_id=42)
client.apps.restart("my-team", app_id=42)               # {success, deployment_id}
client.apps.restart_availability("my-team", app_id=42)  # {available, reason, source_number_id}
client.apps.deploy("my-team", app_id=42)

# Config vars. get returns {"env_vars": [...]}; set takes a flat mapping.
vars = client.apps.get_config_vars("my-team", app_id=42)

# Merges: vars not named here are left alone. An empty ("" or None) value
# DELETES that variable rather than blanking it.
client.apps.set_config_vars("my-team", app_id=42, vars={"DEBUG": "false"})
client.apps.set_config_vars("my-team", app_id=42, vars={"DEBUG": ""})  # deletes DEBUG

# Logs — get_logs takes a list of processes (None = all), works on any mode.
logs = client.apps.get_logs("my-team", app_id=42, processes=["web", "celery"], tail=100)
print(logs)

# get_service_logs is a deprecated shim that delegates to get_logs.
logs = client.apps.get_service_logs("my-team", app_id=42, service="web", tail=100)

# Nginx / load balancer logs
logs = client.apps.get_nginx_logs("my-team", app_id=42, domain="example.com", tail=100)
logs = client.apps.get_load_balancer_logs("my-team", app_id=42, domain="example.com", tail=100)
```

### deployments

```python
client.deployments.list("my-team", app_id=42)
client.deployments.get("my-team", app_id=42, deployment_id=1234)
client.deployments.latest("my-team", app_id=42)
client.deployments.logs("my-team", deployment_id=1234)
```

### domains

```python
client.domains.list("my-team", app_id=42)
client.domains.get("my-team", app_id=42, domain_id=7)
client.domains.create("my-team", app_id=42, domain="example.com")
client.domains.delete("my-team", app_id=42, domain_id=7)

result = client.domains.check_dns("my-team", app_id=42, domain="example.com")
print(result.status)
```

### datastores

```python
client.datastores.list("my-team", app_id=42)
client.datastores.get("my-team", app_id=42, datastore_id=5)
client.datastores.create("my-team", app_id=42, name="mydb", kind="postgresql")
client.datastores.delete("my-team", app_id=42, datastore_id=5)
client.datastores.start("my-team", app_id=42, datastore_id=5)
client.datastores.stop("my-team", app_id=42, datastore_id=5)
client.datastores.restart("my-team", app_id=42, datastore_id=5)
```

### volumes

```python
client.volumes.list("my-team", app_id=42)
client.volumes.get("my-team", app_id=42, volume_id=3)
client.volumes.create("my-team", app_id=42, name="uploads", mount_path="/app/uploads")
client.volumes.update("my-team", app_id=42, volume_id=3, mount_path="/app/media")
client.volumes.delete("my-team", app_id=42, volume_id=3)
```

### cron_jobs

```python
client.cron_jobs.list("my-team", app_id=42)
client.cron_jobs.create("my-team", app_id=42, schedule="0 * * * *", command="python manage.py clearsessions")
client.cron_jobs.update("my-team", app_id=42, cron_id=8, schedule="30 2 * * *")
client.cron_jobs.delete("my-team", app_id=42, cron_id=8)
```

### clusters

```python
client.clusters.list("my-team")
client.clusters.get("my-team", cluster_id=2)
client.clusters.create("my-team", name="prod-cluster")
client.clusters.delete("my-team", cluster_id=2)
```

### servers

```python
client.servers.list("my-team")
client.servers.get("my-team", server_id=10)
```

### invites

```python
client.invites.list("my-team")
client.invites.create("my-team", email="colleague@example.com")
client.invites.delete("my-team", invite_id=4)
```

### migrations

```python
client.migrations.list("my-team")
client.migrations.run("my-team", app_id=42, command="python manage.py migrate")
client.migrations.logs("my-team", migration_id=99)
```

## Error Handling

The SDK raises typed exceptions:

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

client = Appliku()

try:
    client.teams.get("missing-team")
except AuthenticationError:
    print("Invalid or missing token")
except NotFoundError as exc:
    print(exc)
```

Main exception types:

- `AplikuError`
- `AuthenticationError`
- `AuthorizationError`
- `NotFoundError`
- `ValidationError`
- `RateLimitError`
- `ServerError`

Every exception carries `status_code` and `response`, the decoded JSON body. The
body is whatever the API sent, so it may be a dict, a list of messages, or a
string; `str(exc)` is always a readable message regardless of which.

On the command line these are printed as a single line and exit with status 1:

```
$ appliku apps load-balancer-logs --team my-team --app 42 --domain example.com
Error: Load balancer logs are only available for cluster-mode applications. This
application runs on a single server; use application logs or nginx logs instead.
```

Only unexpected failures print a traceback, so a traceback from the CLI is worth
reporting as a bug.

## Development

Create or sync the local environment:

```bash
uv sync
```

Run tests:

```bash
UV_CACHE_DIR=/tmp/uv-cache uv run pytest
```

Run the CLI from the repo:

```bash
UV_CACHE_DIR=/tmp/uv-cache uv run appliku --help
```

Regenerate models and generated client code from the OpenAPI schema:

```bash
./scripts/codegen.sh
```

## License

MIT
