Metadata-Version: 2.4
Name: aircloud-cli
Version: 0.2.0
Summary: AirCloud CLI — endpoint management, SSH, and container exec
Author-email: AirCloud Team <support@aieev.com>
License: Proprietary
Project-URL: Homepage, https://aieev.com
Project-URL: Repository, https://github.com/aieev/aircloud-cli
Keywords: aircloud,ssh,cli,endpoint,exec
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development
Classifier: Topic :: System :: Shells
Classifier: Topic :: Utilities
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: websockets>=12.0
Requires-Dist: click>=8.0
Requires-Dist: httpx>=0.27.0
Requires-Dist: paramiko>=3.0
Requires-Dist: cryptography>=3.0

# aircloud-cli

CLI for the [AirCloud](https://aieev.com) platform — endpoint management,
SSH access, and container shell exec.

## Install

```bash
pip install aircloud-cli
```

Requires Python 3.9+.

## Quick start

```bash
# one-time setup
aircloud config set api-key <YOUR_API_KEY>
aircloud config set api-base-url <API_BASE_URL>

# inspect endpoints
aircloud endpoints list
aircloud endpoints get <endpoint_id>

# open a shell in a container
aircloud ssh <endpoint_id>            # public-key auth, sshd-based
aircloud exec <endpoint_id>           # docker exec over WS, no sshd needed
```

## Commands

### `aircloud config`

Manage CLI configuration in `~/.aircloud/config.json`.

```bash
aircloud config set api-key <key>
aircloud config set api-base-url <url>
aircloud config list
aircloud config get <key>
```

### `aircloud endpoints`

```bash
aircloud endpoints list                          # paged list
aircloud endpoints get <id>                      # detail
aircloud endpoints start <id>
aircloud endpoints stop <id>
aircloud endpoints scale <id> --replicas N
aircloud endpoints patch <id> --command "..." --port 8080 --env KEY=VALUE
aircloud endpoints replicas <id>                 # current replicas
aircloud endpoints logs <id> [--replica-id X] [--start-line N --end-line M]
```

### `aircloud ssh`

SSH into a container via WebSocket tunnel using public-key authentication.

```bash
aircloud ssh <endpoint_id>                       # auto-discover ~/.ssh/id_*
aircloud ssh <endpoint_id> -i ~/.ssh/aircloud_key
aircloud ssh <endpoint_id> -r <replica_id>       # pin to specific replica
aircloud ssh <endpoint_id> --tunnel-only         # open tunnel only
```

Auto-discovers identity files in this order: `~/.ssh/id_ed25519`,
`~/.ssh/id_ecdsa`, `~/.ssh/id_rsa`. Override with `-i / --identity-file`.

The container image must have `sshd` running and consume the
`AIRCLOUD_AUTHORIZED_KEYS` environment variable (AirCloud-provided template
images do this by default).

### `aircloud ssh-proxy` / `aircloud ssh-config` (VS Code Remote-SSH, scp, git)

`aircloud ssh` opens its own tunnel and interactive shell. To use the
system `ssh`, `scp`, `git`, or **VS Code Remote-SSH** instead — without
pre-starting a tunnel or opening a local port — register `ssh-proxy` as an
OpenSSH `ProxyCommand`.

`ssh-proxy` bridges its own stdin/stdout to the WebSocket tunnel, so `ssh`
spawns one automatically per connection and tears it down on disconnect.

Install the config block into `~/.ssh/config`:

```bash
aircloud ssh-config <endpoint_id> -i ~/.ssh/id_ed25519 --install
```

`--install` writes the block between `# >>> aircloud managed: <id> >>>`
markers, so it is idempotent (re-running replaces in place, never
duplicates). It backs up `~/.ssh/config` first, resolves the absolute path
to `aircloud` (so GUI-launched VS Code finds it regardless of `PATH`), and
prepends the block above any catch-all `Host *` so its `User` / options win
under SSH's first-match-wins rule. The installed block:

```
Host aircloud-<endpoint_id>
    HostName <endpoint_id>
    User root
    IdentityFile ~/.ssh/id_ed25519
    ProxyCommand /abs/path/to/aircloud ssh-proxy %h --stdio
    ServerAliveInterval 30
    ServerAliveCountMax 3
    StrictHostKeyChecking accept-new
```

Run without `--install` to print the block for review instead of writing
it, or with `--uninstall` to remove that endpoint's managed block.

Then connect with any SSH-based tool:

```bash
ssh aircloud-<endpoint_id>
scp file.txt aircloud-<endpoint_id>:/workspace/
# VS Code: Remote-SSH → Connect to Host → aircloud-<endpoint_id>
```

`ServerAliveInterval` keeps the session alive past L7 proxy idle timeouts.
The container image must run `sshd` (same requirement as `aircloud ssh`).

Running `aircloud ssh-proxy <endpoint_id> --stdio` directly in a terminal
only prints the server's SSH banner and then waits — it is a transport for
an SSH client, not a shell. Drive it through `ssh` / `ProxyCommand`.

#### Reconnection / transient relay slowness

When the relay is briefly slow (heavy workload on the node), the WebSocket
handshake can time out (`timed out during opening handshake`). `ssh-proxy`
retries the connect with exponential backoff so a transient blip is absorbed
instead of failing the connection. This applies to the **connect phase only**
— once a session is bridged, a mid-session drop ends it (SSH cannot resume),
and the client (VS Code, or you) reconnects, which spawns a fresh proxy that
retries again.

Tunables (env, read by `ssh-proxy`):

| Variable | Default | Meaning |
|---|---|---|
| `AIRCLOUD_SSH_OPEN_TIMEOUT` | `15` | Per-attempt WS handshake timeout (seconds) |
| `AIRCLOUD_SSH_CONNECT_RETRIES` | `3` | Retry count for transient failures (timeout / 5xx) |

Auth/routing failures (3xx/4xx) are not retried — they surface immediately.

For VS Code, raise its own connect timeout so a single attempt can span the
proxy's retries, in `settings.json`:

```json
"remote.SSH.connectTimeout": 60
```

### `aircloud exec`

Open an interactive shell in the container via `docker exec` over a
WebSocket. Works on any image — no `sshd` required.

```bash
aircloud exec <endpoint_id>
aircloud exec <endpoint_id> -r <replica_id>      # pin replica
aircloud exec <endpoint_id> -c "/bin/sh"         # custom command
```

### `aircloud whoami`

Print the identity associated with the current API key.

```bash
aircloud whoami
```

## Development

Editable install — source edits are picked up immediately by the
`aircloud` command, no rebuild required:

```bash
pip install -e .
aircloud --help
```

## Build & Release

Published to PyPI as `aircloud-cli`. Requires `build` and `twine`
(`pip install build twine`).

```bash
# 1. bump the version in pyproject.toml (PyPI rejects re-uploading a version)

# 2. clean stale artifacts
rm -rf dist

# 3. build sdist + wheel into dist/
python -m build

# 4. upload to PyPI (needs a PyPI API token in ~/.pypirc or entered at the prompt)
twine upload dist/*

# 5. tag and push the release
git commit -am "release(x.y.z): <summary>"
git tag vx.y.z
git push origin main --tags
```

Verify the published package in a clean environment:

```bash
python -m venv /tmp/ac && /tmp/ac/bin/pip install "aircloud-cli==x.y.z"
/tmp/ac/bin/aircloud --help
```

## Status

This is an early functional release. Interfaces may change before `1.0.0`.

## License

Proprietary. © AIEEV / AirCloud.
