Metadata-Version: 2.4
Name: keynode
Version: 0.1.0
Summary: Official Python SDK and CLI for KeyNode — AI compute on distributed GPUs
License: MIT
Project-URL: Homepage, https://keynode.es
Project-URL: Documentation, https://docs.keynode.es
Project-URL: Repository, https://github.com/keynode-ai/keynode-python
Keywords: keynode,gpu,ai,ml,training,cloud,sdk,cli
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
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: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: httpx>=0.27.0
Requires-Dist: typer>=0.12.3
Requires-Dist: rich>=13.7.1

# KeyNode Python SDK & CLI

Official Python SDK and CLI for [KeyNode](https://keynode.es) — the AI compute platform that connects your workloads to underutilized GPUs from distributed providers. Run training jobs and inference cheaper and faster than traditional cloud.

## Requirements

- Python 3.10+

## Installation

```bash
pip install keynode
```

Or install from source:

```bash
git clone <repo_url>
cd keynode-python
pip install .

# Development (editable) install
pip install -e .
```

## Authentication

### Option 1 — CLI login (recommended)

```bash
keynode auth login
```

This prompts for your API key and saves it to `~/.keynode/config.json`.

### Option 2 — Environment variables

```bash
export KEYNODE_API_KEY="kn_xxx"
export KEYNODE_BASE_URL="https://api.keynode.es"  # optional, this is the default
```

### Option 3 — Direct SDK initialization

```python
from keynode import KeyNodeClient

client = KeyNodeClient(api_key="kn_xxx", base_url="https://api.keynode.es")
```

---

## CLI Usage

### Auth

```bash
keynode auth login     # Save API key to config
keynode auth whoami    # Verify authentication
```

### GPU Catalog

```bash
keynode gpu list                                    # List all available GPUs
keynode gpu list --min-vram 24                      # Filter by minimum VRAM
keynode gpu list --max-price 1.5 --region eu-west   # Filter by price and region
keynode gpu list --model "RTX 4090"                 # Search by model name
keynode gpu stats                                   # Platform-wide GPU stats
keynode gpu detail "NVIDIA A100" --vram 80          # Detailed info for a GPU SKU
```

### Runs

```bash
keynode run list                                    # List all runs
keynode run get <run_id>                            # Get run details
keynode run create -f payload.json                  # Create run from JSON file
keynode run cancel <run_id>                         # Cancel a run
keynode run retry <run_id>                          # Retry a run immediately
keynode run clone-retry <run_id>                    # Clone and retry a run
keynode run resume <run_id>                         # Resume a paused run
keynode run wait <run_id>                           # Block until run finishes
keynode run wait <run_id> --timeout 3600            # Wait with timeout (seconds)
keynode run metrics <run_id>                        # GPU/CPU metrics time-series
keynode run manifest <run_id>                       # Show run manifest
keynode run download-manifest <run_id> -o out.json  # Download manifest as JSON
keynode run artifacts <run_id>                      # List artifacts
keynode run download-artifacts <run_id> -o out.tar.gz  # Download artifacts tarball
```

### Backups

```bash
keynode backup list                                 # List all backups
keynode backup list --run-id 365                    # Filter by run
keynode backup list --status completed              # Filter by status
keynode backup delete <backup_id>                   # Delete a backup
```

### Datasets

```bash
keynode dataset list                                # List datasets
keynode dataset upload ./train.csv --name my-train  # Upload a dataset
keynode dataset download <dataset_id> -o data.csv   # Download a dataset
keynode dataset delete <dataset_id>                 # Delete a dataset
```

### Sources

```bash
keynode source list                                 # List sources
keynode source upload ./my_code.tar.gz              # Upload a source archive
keynode source delete <source_id>                   # Delete a source
```

### Secrets

```bash
keynode secret list                                 # List secrets
keynode secret create --name HF_TOKEN               # Create a secret (prompts for value)
keynode secret delete <secret_id>                   # Delete a secret
```

### API Keys

```bash
keynode apikey list                                 # List your API keys
keynode apikey create --name "ci-pipeline"          # Create a new API key
keynode apikey create --name "tmp" --expires-in-days 30  # Key with expiry
keynode apikey revoke <api_key_id>                  # Revoke an API key
```

---

## Python SDK Usage

### Initialize the client

```python
from keynode import KeyNodeClient

# From environment variables or ~/.keynode/config.json
client = KeyNodeClient.from_env()

# Or explicitly
client = KeyNodeClient(api_key="kn_xxx", base_url="https://api.keynode.es")
```

### Browse the GPU catalog

```python
with KeyNodeClient.from_env() as client:
    # List available GPUs
    gpus = client.gpu.list(min_vram=24, max_price=2.0)
    for g in gpus:
        print(f"{g['model']} {g['vram_gb']}GB — €{g['min_price_per_hour']:.3f}/hr ({g['units_available']} available)")

    # Platform stats
    stats = client.gpu.stats()
    print(f"{stats['gpus_available']} GPUs available across {stats['regions_active']} regions")

    # Detailed SKU info
    detail = client.gpu.detail("NVIDIA A100", vram_gb=80)
    print(detail["region_breakdown"])
```

### Create a training run and wait for completion

```python
from keynode import KeyNodeClient

payload = {
    "name": "my-training-job",
    "image": "registry.keynode.es/keynode-jupyter-scipy-ssh:v1",
    "cmd": "python train.py",
    "cpus": 4,
    "memory_gb": 16,
    "gpu_count": 1,
}

with KeyNodeClient.from_env() as client:
    run = client.runs.create(payload)
    run_id = run["run_id"]

    # Block until finished (polls every 5s)
    result = client.runs.wait(run_id, timeout=7200)
    print(f"Status: {result['status']}")

    # Download results
    client.runs.download_artifacts(run_id, output_path="./results.tar.gz")
```

### List runs

```python
with KeyNodeClient.from_env() as client:
    runs = client.runs.list()
    print(runs)
```

### Get GPU/CPU metrics for a run

```python
with KeyNodeClient.from_env() as client:
    metrics = client.runs.metrics(run_id=365, step_s=10)
    for point in metrics["items"]:
        print(f"{point['ts']}  GPU {point['gpu_util']}%  VRAM {point['gpu_mem_mb']}MB")
```

### Work with backups

```python
with KeyNodeClient.from_env() as client:
    # List all backups
    backups = client.backups.list()

    # Filter by run
    run_backups = client.backups.list(run_id=365, status="completed")

    # Delete a backup
    client.backups.delete(backups[0]["id"])
```

### Work with datasets

```python
with KeyNodeClient.from_env() as client:
    # Upload
    dataset = client.datasets.upload("./data/train.csv", dataset_name="train-v1")

    # Download
    client.datasets.download(dataset["id"], output_path="./train_downloaded.csv")

    # Delete
    client.datasets.delete(dataset["id"])
```

### Manage API keys

```python
with KeyNodeClient.from_env() as client:
    # Create a new key (raw key shown only once)
    result = client.api_keys.create(name="ci-pipeline", expires_in_days=90)
    print(result["api_key"])  # save this immediately

    # List existing keys
    keys = client.api_keys.list()

    # Revoke a key
    client.api_keys.revoke(keys[0]["api_key_id"])
```

### Manage secrets

```python
with KeyNodeClient.from_env() as client:
    client.secrets.create(name="HF_TOKEN", value="hf_xxx", description="HuggingFace token")
    secrets = client.secrets.list()
    client.secrets.delete(secrets[0]["id"])
```

### Download run artifacts

```python
with KeyNodeClient.from_env() as client:
    # List artifacts
    artifacts = client.runs.artifacts(run_id=365)

    # Download as tarball
    client.runs.download_artifacts(run_id=365, output_path="./results.tar.gz")

    # Download manifest
    client.runs.download_manifest(run_id=365, output_path="./manifest.json")
```

### Error handling

```python
from keynode import KeyNodeClient
from keynode.exceptions import KeynodeAuthError, KeynodeApiError

try:
    with KeyNodeClient.from_env() as client:
        run = client.runs.get(999)
except KeynodeAuthError:
    print("Invalid or missing API key")
except KeynodeApiError as e:
    print(f"API error {e.status_code}: {e.message}")
```

---

## Package Structure

```
keynode       → Python SDK (client, resources, config, exceptions)
keynode_cli   → CLI application
```
