# Lium — Full Reference for Agents Self-contained reference for AI agents. Includes skill overview, CLI command reference, and Python SDK reference. Source of truth: https://github.com/Datura-ai/lium-skill --- # Lium CLI & SDK Lium — decentralized GPU rental platform on Bittensor. Pods are Docker containers with root SSH access and direct GPU passthrough. - **GitHub**: https://github.com/Datura-ai/lium - **PyPI**: https://pypi.org/project/lium.io/ - **Docs**: https://docs.lium.io - **Dashboard**: https://lium.io ## Quick Install Standalone binary — no Python or dependencies required: ```bash curl -fsSL https://raw.githubusercontent.com/Datura-ai/lium/main/scripts/install.sh | bash ``` This auto-detects OS (Linux/macOS) and architecture, downloads the binary to `~/.lium/bin/lium`, and adds it to PATH. After install, authentication depends on whether the user has a Lium account: - **No account** → `lium signup --email ` — see "No Account Yet — Sign Up". Do not send the user to the web signup form. - **Has an account** → `lium init` (opens a browser) or `lium init --no-browser` for headless/agent use — see "Authentication Setup for Agents". ```bash lium signup --email ada@example.com # no account yet lium init # existing account, browser lium init --no-browser # existing account, headless ``` Verify setup: ```bash lium balance # prints a balance -> auth works; prints an error -> it does not ``` ### Alternative Install (via pip/uv) ```bash # Via uv (isolated env) curl -LsSf https://astral.sh/uv/install.sh | sh uv tool install lium.io # Via pip pip install lium.io ``` ## Agent-Specific: Non-Interactive Usage **CRITICAL**: Many lium commands are interactive by default. As an agent, always pass all parameters explicitly to avoid interactive prompts. ### No Account Yet — Sign Up `lium init` authenticates a user who **already has an account**. To create one, use `lium signup` — no browser, no dashboard, no web form. The whole cold start is four commands: ```bash lium signup --email ada@example.com # creates the account, stores the API key lium ls # browse machines lium up -y # rent lium ssh # connect ``` Only create an account when the user asks for one. Ask for their **real email** first — the confirmation link needed for renting is sent there, and the account, its balance and password recovery are tied to it. Never invent an address, never use a disposable inbox. `lium signup` prints the generated password — hand it to the user, it is their dashboard login (to choose one instead, pass `--password` or set `LIUM_SIGNUP_PASSWORD`, which keeps it off argv). Even when the command fails after the account was created — a timeout, or the API key could not be read back — the error still reports the email and password, so the account is never stranded. Add `--json` for a machine-readable `{email, password, api_key, signup_credit_granted, ssh_key_configured, next_steps}`. The key is written to `~/.lium/config.ini`, so the account is then indistinguishable from one set up with `lium init`. Older binaries do not have the command. Probe for it, and update when it is missing: ```bash lium --version # diagnostics only — probe the command itself below lium signup --help >/dev/null 2>&1 || echo "CLI too old — update it" # Binary install (installed via install.sh): auto-updates on launch, or force it curl -fsSL https://lium.io/install.sh | bash # pip / uv install uv tool upgrade lium.io # or: pip install -U lium.io ``` On an older CLI that cannot be updated, the same signup is three HTTP calls: ```bash BASE=https://lium.io/api # 1. Create the account. An API key named "Default" is minted server-side here; current # backends return it in the response — {"msg": "success", "api_key": "sk_...", # "signup_credit_granted": true|false} — older ones mint it without returning it. curl -sX POST $BASE/users -H 'Content-Type: application/json' \ -d '{"name":"Ada","email":"ada@example.com","password":"..."}' # 2-3. Only when the response had no api_key: log in for a JWT, read the key back (format sk_...). TOKEN=$(curl -sX POST $BASE/users/login -H 'Content-Type: application/json' \ -d '{"email":"ada@example.com","password":"..."}' | jq -r .token) KEY=$(curl -s $BASE/keys -H "Authorization: Bearer $TOKEN" \ | jq -r '.[] | select(.name=="Default") | .key') lium config set api.api_key "$KEY" # leave ssh.key_path unset — the CLI generates and configures the key on first use, # and setting the path to a key that does not exist yet makes it skip that ``` ### Before the First Rental — Verification and Balance `lium up` calls `POST /executors/{executor_id}/rent`, which fails with `403` until both gates are cleared. Map the error to the action: | 403 on rent | Meaning | Action | |---|---|---| | `"User is not verified"` | The confirmation link has not been clicked yet. | Ask the user to click it, then retry — see "Email confirmation" below. Do not abandon the task. | | `"Insufficient balance"` | The account balance is zero. | Fund the account — see "Funding options" below. | #### Email confirmation Registration sends **two** mails: `"Welcome to Celium!"` (no link in it) and `"Please confirm your email"` — only the second one carries the link. Point the user at that subject, and ask them to click the link. That is the normal path. Two endpoints on `https://lium.io/api` cover the cases where it does not work. Neither needs auth; both take JSON: ```bash # Mail never arrived / link expired (tokens are valid 24h) — send a fresh one curl -sX POST https://lium.io/api/auth/resend-verify-email \ -H 'Content-Type: application/json' -d '{"email":"ada@example.com"}' # 400 "User doesn't exist." or "Email is already verified." when it does not apply # User pastes the link instead of clicking it — finish verification from its ?token= curl -sX POST https://lium.io/api/auth/verify-email \ -H 'Content-Type: application/json' -d '{"token":""}' ``` #### The $5 signup credit New accounts get a $5 credit. It is granted when the platform has the credit enabled **and** no other account has signed up from this IP address — nothing about the email domain matters. Do not explain a `403 "Insufficient balance"` with the credit: that error only says the balance is zero, and the answer to it is to fund the account. Whether it landed is answered by `signup_credit_granted` in the signup response (also in `lium signup --json`): `true` → granted, `false` → not granted. When it is `null` or absent — the backend does not report it — read the balance: ```bash lium balance --json # {"balance_usd": 5.0} ``` #### Funding options - Dashboard: https://lium.io/billing - Headless invoice: `POST /tmc-pay/create-invoice` with header `X-API-Key: sk_...` and a body of `{"amount": , "crypto_currency": "...", "crypto_network": "..."}` (all three required). Valid currency/network pairs come from `GET /tmc-pay/currencies` (same API key header). The response carries `deposit_address`, `crypto_amount`, `hosted_invoice_url` and `expires_at` — give these to the user to pay from their wallet, do not move funds on their behalf. ```bash # {"currencies": [{"code": "USDT", "network": "tron", ...}, ...]} — pick a pair from here curl -s https://lium.io/api/tmc-pay/currencies -H "X-API-Key: sk_..." curl -sX POST https://lium.io/api/tmc-pay/create-invoice -H "X-API-Key: sk_..." \ -H 'Content-Type: application/json' \ -d '{"amount": 20, "crypto_currency": "USDT", "crypto_network": "tron"}' ``` - `lium fund -w default -a 10.0 -y` for users with a Bittensor wallet — here `-a` is an amount of **TAO**, not dollars. `-a` means USD only on the `--alpha` path, which moves Subnet-51 alpha the user already has staked: `lium fund --alpha -k -a 10 -y`. SSH keys need no extra registration — the public key at `ssh.key_path` is registered server-side right before renting. ### Authentication Setup for Agents For a user who already has an account (skip if you just ran the signup flow above and stored the key). **Preferred: two-step headless auth** — no API key needed, no blocking, no browser: 1. Run `lium init --no-browser` — get auth URL and session ID (exits immediately) 2. Show the URL to the user, ask them to open it and click Approve 3. Wait for user to confirm they approved 4. Run `lium init --session ` — saves API key + sets up SSH ```bash lium init --no-browser # [i] Open this URL to authenticate: # https://lium.io/cli/approve/xJinnT3Vt6... # [i] Then complete authentication with: # lium init --session abc123def456 # ... user confirms they approved ... lium init --session abc123def456 # [✓] API key saved ``` **Fallback options** (if `--no-browser` is unavailable or user already has an API key): ```bash # Option 1: Direct config lium config set api.api_key YOUR_API_KEY lium config set ssh.key_path ~/.ssh/id_ed25519 # Option 2: Environment variable (session only) export LIUM_API_KEY=YOUR_API_KEY ``` For fallback options, the user must get an API key from https://lium.io Account Settings. ### Verify Setup ```bash lium config show # check stored config lium balance # prints a balance -> auth works ``` ### Non-Interactive Pod Creation Always use `-y` flag and pass all parameters. Add `--no-ssh` too: without it a successful `lium up` ends by opening an interactive SSH session, which stalls an agent (`--image` mode streams container logs instead). ```bash # WRONG (interactive): lium up # one confirmation prompt before renting lium up 1 # same — the prompt is the acquire confirmation # RIGHT (non-interactive): lium up --gpu H100 -y --no-ssh # auto-selects node + default template lium up --gpu A100 -c 2 --country US -y --no-ssh # with filters lium up --gpu H100 --name my-pod --ttl 6h -y --no-ssh # with name and auto-termination lium up --gpu A6000 --image pytorch/pytorch:2.0 -y # custom docker image (streams logs) lium up --gpu H100 --jupyter -y --no-ssh # with Jupyter ``` ### Non-Interactive Funding ```bash # WRONG (interactive): lium fund # RIGHT: lium fund -w default -a 10.0 -y # fund 10 TAO, skip confirmation ``` User must have a verified Bittensor wallet at https://lium.io/billing. ### Agent Gotchas / Known Pitfalls #### After Install — Export PATH ```bash export PATH="$HOME/.lium/bin:$PATH" # needed in current shell session ``` #### After `lium init --session` — Verify with `lium ls` After completing the two-step auth, run `lium ls` to verify. If it returns results, auth is done. #### An Error Does Not Always Mean a Non-Zero Exit Only `lium exec`, `lium rm` and `lium up` exit non-zero when they fail. Everything else — including **`lium ls`** — can print `Error: ...` and still exit **0**: ```bash lium ssh no-such-pod-xyz # prints "No active pods", exits 0 lium ls >/dev/null && echo "auth OK" # prints "auth OK" even with a revoked key ``` So `lium ls` is **not** a usable auth check. Never treat `$?` alone as proof that a step worked. Read the output, or prefer the machine-readable modes (`lium ls --format json`, `lium ps --format json`, `lium exec --json`) and check the result there — an empty `[]` from `lium ls --format json` means "no nodes", while an error line on stderr means the call failed. Tracked as DAH-2593. #### Pod Targeting — Prefer Names Use the pod **name** (e.g. `lunar-lion-4c`) from `lium ps` output for targeting — not a numeric index; indices shift with every listing. #### `-y` Exists on the Destructive Commands `lium rm`, `lium up`, `lium fund`, `lium volumes rm`, `lium bk set/rm/restore` all take `-y, --yes`. No piped `yes` is needed: ```bash lium rm my-pod # will prompt for confirmation lium rm my-pod -y # non-interactive lium rm -a -y # remove all pods non-interactively ``` #### Templates - Without `--template_id` or `--image`, `lium up` uses default **PyTorch (CUDA)** template — fastest to start - Default Docker-in-Docker (dind) template image: `daturaai/dind` - Search templates: `lium templates pytorch` (text search, no --format json) - To use specific template: `lium up --gpu H100 -t -y` - To use custom Docker image: `lium up --gpu H100 --image pytorch/pytorch:2.0 -y` #### No User Identity Command lium CLI has no renter-side identity command — no `whoami` for your API key. (`lium provider portal whoami` exists, but it reports the *provider* portal session, not the API key you rent with.) To check the key, run `lium balance`: it prints a balance when the key works and an error when it does not. Do **not** use `lium ls` for this — it prints an error and exits 0 on an auth failure, so `lium ls && echo OK` says OK with a revoked key. #### Long-Running Commands Over SSH `lium exec` runs commands in the foreground over SSH. Commands longer than ~30-60s (e.g. `pip install vllm`, `huggingface-cli download`) may be killed by SSH drop. Wrap with `nohup` + log redirect and poll the log: ```bash # Start long command in background, detached from SSH session # (the \$ escapes for the local shell; the remote sees literal $! which expands to the backgrounded bash PID) lium exec my-pod "nohup bash -c 'pip install vllm' /tmp/install.log 2>&1 & echo PID=\$!" # Watch progress lium exec my-pod "tail -f /tmp/install.log" # or stream via the logs endpoint if the command writes to stdout of PID 1 lium logs my-pod --follow ``` For fully-detached execution (survives SSH session close, stays running after `lium exec` returns): ```bash lium exec my-pod "setsid nohup /tmp/out.log 2>&1 &" ``` #### PEP 668 on Default PyTorch Template The default `daturaai/pytorch` image is based on Ubuntu 24.04 where system `pip` is PEP 668 protected (`externally-managed-environment`). Use one of: ```bash # Option 1: allow system-wide install pip install --break-system-packages # Option 2: venv (recommended for isolation) python -m venv /opt/env && source /opt/env/bin/activate && pip install # Option 3: uv (fast, handles isolation automatically) curl -LsSf https://astral.sh/uv/install.sh | sh uv pip install --system ``` #### Missing System Libraries in Base Image The default GPU base image does not include: `jq`, `htop`, `tmux`, `screen`, `libnuma1`, `git-lfs`, `rsync`. If your workload needs them: ```bash lium exec my-pod "apt-get update && apt-get install -y libnuma1 jq tmux git-lfs" ``` Note: `libnuma1` is required by `sglang`'s `sgl_kernel` and some `vllm` configs — missing it causes cryptic "kernel not found" errors that actually mean the `.so` failed to load. #### Cold-Start Expectations Don't assume a pod is broken if it's quiet for several minutes after launch. Typical timings: - Pod provisioning + SSH ready: ~30-60s - Docker image pull: usually cached, ~0-30s - Package installs (`pip install vllm`): ~2-5 min - Model download from HuggingFace (4B-class): ~1-2 min; (70B+): ~5-10 min - vLLM engine init (4B model, single GPU): ~2-3 min - sglang + 70B+ sharded (CUDA graph capture of ~50 graphs): **15-25 min** Use `lium logs my-pod --follow` to watch progress, or poll a log file from `lium exec`. #### Verify HuggingFace Model Exists Before Deploy Before spinning up a pod for a specific model (e.g. `vllm serve `), confirm the `repo_id` exists on HuggingFace — typos like `qwen3.5-4b` (doesn't exist) vs `Qwen/Qwen3-4B` waste a full cold-start cycle. ```bash curl -s "https://huggingface.co/api/models?search=qwen+2.5+7b&limit=10" | jq -r '.[].id' ``` #### Pod Vanishes from `lium ps` Pods with internal status `DELETING` are filtered out of `lium ps`. `FAILED` pods remain visible (with `FAILED` status) — so if a pod was `RUNNING` and fully disappears, it's being deleted, not failing. To investigate: - Check the dashboard (https://lium.io) — it shows full history including deleted pods - Grab logs before the pod vanishes: `lium logs ` (while it still exists) - Known issue: the CLI does not currently surface a deletion reason. If reproducible, report to the platform team. #### Pod Creation Failures — 3-Minute Visibility Window When `lium up` fails during provisioning, the pod is kept in status `CREATION_FAILED` for ~3 minutes before being auto-cleaned up (with a 10-min safety net if the cleanup task is delayed). During this window: - `lium ps` will show the pod with status `CREATION_FAILED` - `lium logs ` may have partial output from the failed creation - After ~3 minutes the pod disappears — if your agent polled later, it will see no trace For reliable failure diagnosis, poll `lium ps` every ~10-30s for the first few minutes after `lium up`, or check both `RUNNING` and terminal failure states explicitly. #### "Executor Not Found" on `lium up ` If an executor is visible on the lium.io dashboard but `lium up ` or `lium ls` doesn't show it, the platform's availability filter rejected it. Reasons include: low free disk space, high disk utilization, unresponsive health checks, or missing verification. **`lium ls` is the source of truth for rentable machines** — prefer filtering/selecting from `lium ls` output rather than matching IDs from the website. ## CLI Quick Reference ### Discovery ```bash lium ls # all available GPUs (shows table with ★ for best price/perf) lium ls --gpu H100 # filter by GPU type (there is no positional argument) lium ls --sort download # sort by download speed (fastest first) — preferred default lium ls --sort upload # sort by upload speed lium ls --sort price_gpu # sort by price per GPU/hour lium ls --format json # machine-parseable output lium templates # list Docker templates lium templates pytorch # search templates ``` **Recommendation**: When selecting machines for the user, prefer `--sort download` to get the fastest network unless the user specifically asks to sort by price or other criteria. ### Pod Lifecycle ```bash lium up --gpu H100 -y # create pod lium ps # list active pods lium ps --format json # machine-readable pod list lium ssh my-pod # SSH into pod lium exec my-pod "nvidia-smi" # run command lium exec all "pip install torch" # batch exec on all pods lium rm my-pod -y # stop pod lium rm -a -y # stop all pods ``` ### Streaming Pod Logs ```bash lium logs my-pod # snapshot of current stdout/stderr lium logs my-pod --follow # stream logs live (Ctrl-C to stop) ``` Streams the **Docker container's PID 1 stdout/stderr** from the executor. Works for both image-mode and SSH-mode pods. Caveats: - Right after `lium up`, the endpoint may return 404 ("Pod container not deployed yet") for a few seconds — retry. - For SSH-mode pods, processes you start manually via `lium exec` are NOT PID 1, so their output won't appear here unless you redirect to `/proc/1/fd/1` (e.g. `my_server > /proc/1/fd/1 2>&1`) or tail your log files via `lium exec my-pod "tail -f /tmp/out.log"`. ### File Transfer ```bash lium scp my-pod ./train.py # upload to /root/ lium scp my-pod ./data.csv /root/data/ # specific path lium scp all ./config.json # upload to all pods lium rsync my-pod ./project # sync directory ``` ### Pod Targeting Pods accept: name, index from `lium ps`, comma-separated (`1,2,3`), or `all`. ### Output Formats Always use `--format json` when parsing output programmatically: ```bash lium ls --format json | python -c "import json,sys; print(json.load(sys.stdin))" lium ps --format json | python -c "import json,sys; print(json.load(sys.stdin))" ``` `--format [table|json]` exists on `lium ls` and `lium ps`. `--json` — a plain flag, not a format choice — is taken by `lium exec`, `lium fund`, `lium balance`, `lium signup`, `lium topup create`, `lium topup currencies`, and by the whole `lium provider` group (set it on the group: `lium provider --json node list`). `lium templates` has neither, and neither does anything else. ## End-to-End Agent Workflow Complete flow for setting up and renting a GPU pod: ```bash # 1. Install lium (if not present) if ! command -v lium >/dev/null 2>&1; then curl -fsSL https://raw.githubusercontent.com/Datura-ai/lium/main/scripts/install.sh | bash export PATH="$HOME/.lium/bin:$PATH" fi # 2a. No account yet → sign up (asks the user for their real email first) lium signup --email # 2b. Existing account → two-step headless auth instead lium init --no-browser # → parse URL and session ID from output, show URL to user # → wait for user to confirm they approved lium init --session # 3. Verify (lium ls exits 0 even on an auth failure — check balance instead) lium balance --json # 4. Find suitable GPU (sort by speed by default) lium ls --gpu H100 --sort download # 5. Create pod (non-interactive! --no-ssh returns instead of opening a session) lium up --gpu H100 --name work-pod --ttl 6h -y --no-ssh # 6. Wait and verify (read the output, not just the exit code) lium ps --format json # 7. Use the pod lium scp work-pod ./code.py lium exec work-pod "python /root/code.py" # 8. Cleanup lium rm work-pod -y ``` --- # CLI Commands — Full Reference # Lium CLI Command Reference Written against `lium --version` **0.0.29**. Every flag below appears in that binary's own `--help`; nothing here is extrapolated. When a newer CLI ships, `lium --help` is the authority, not this file. ## Table of Contents - [Global Options](#global-options) - [lium signup](#lium-signup) - [lium init](#lium-init) - [lium balance](#lium-balance) - [lium ls](#lium-ls) - [lium up](#lium-up) - [lium ps](#lium-ps) - [lium ssh](#lium-ssh) - [lium exec](#lium-exec) - [lium scp](#lium-scp) - [lium rsync](#lium-rsync) - [lium rm](#lium-rm) - [lium logs](#lium-logs) - [lium port-forward](#lium-port-forward) - [lium reboot](#lium-reboot) - [lium update](#lium-update) - [lium templates](#lium-templates) - [lium volumes](#lium-volumes) - [lium bk (backups)](#lium-bk-backups) - [lium schedules](#lium-schedules) - [lium ssh-keys](#lium-ssh-keys) - [lium config](#lium-config) - [lium theme](#lium-theme) - [lium fund](#lium-fund) - [lium topup](#lium-topup) - [lium mine](#lium-mine) - [lium provider](#lium-provider) - [lium gpu-splitting](#lium-gpu-splitting) - [Batch Operations](#batch-operations) - [Pod Targeting](#pod-targeting) - [Environment Variables](#environment-variables) - [Exit Codes](#exit-codes) ## Global Options The root command takes exactly two options: ``` --version Show the version and exit --help Show this message and exit ``` There is no `--config` and no `--debug` flag. Debug output is switched on with the `LIUM_DEBUG=1` environment variable, and the config file location comes from `lium config path`. ## lium signup Create a Lium account and store the API key it mints. Fully non-interactive — this is the command to use when the user has **no account yet**. Older CLI binaries do not have it — probe with `lium signup --help` and update the CLI when it is missing. ```bash lium signup [OPTIONS] --email EMAIL The user's real email (REQUIRED) — the confirmation link goes there --name NAME Display name (defaults to the email's local part) --password PASSWORD Account password (a strong one is generated when omitted) --json Machine-readable output ``` The password can also come from the `LIUM_SIGNUP_PASSWORD` environment variable — `--password` wins when both are set. Prefer the variable: a flag value is left behind in the shell history and in `ps` output. Whatever its origin, the password is always reported back to the caller. Ask the user for their **real** email — the account, its balance, password recovery and the confirmation link needed for renting are all tied to it. Never invent an address. The command creates the account (`POST /users`), stores the minted API key in `~/.lium/config.ini` under `api.api_key`, and sets up an SSH key. After it, `lium ls` and `lium up` work with no further setup. **Refuses to run when `api.api_key` is already configured** — it exits with an error instead of creating a second, unreachable account. To sign up anyway, drop the existing key first: ```bash lium config unset api.api_key # then: lium signup --email ... ``` **Failures never strand the account.** When the command fails after the account was created — the request timed out, or the API key could not be read back — the error still reports the email and password, so the user can log in at https://lium.io and copy an API key from the dashboard. With `--json`, that error goes to stderr as `{"ok": false, "error": {...}, "data": {"email": "...", "password": "..."}}`. Examples: ```bash lium signup --email ada@example.com lium signup --email ada@example.com --name Ada --json LIUM_SIGNUP_PASSWORD=... lium signup --email ada@example.com ``` `--json` output: ```json { "api_key": "sk_...", "email": "ada@example.com", "next_steps": ["...", "...", "..."], "password": "generated-or-supplied", "signup_credit_granted": true, "ssh_key_configured": true } ``` - `password` — the dashboard login at https://lium.io. Hand it to the user; it is not stored anywhere else. - `signup_credit_granted` — comes straight from the signup API response and is the authoritative answer to "did the $5 signup credit land?": `true` → granted; `false` → not granted (the once-per-IP gate, or the credit disabled platform-side); `null` → the backend did not report it (older backend) — read the balance instead: `lium balance --json`. - Renting stays blocked until the user clicks the link in the **"Please confirm your email"** mail (the separate "Welcome to Celium!" mail carries no link). ## lium init Initialize the CLI for a user who **already has an account** — `lium init` cannot create one, use [`lium signup`](#lium-signup) for that. Plain `lium init` opens a browser and is **not suitable for agent use**; the `--no-browser` / `--session` pair is the headless two-step. ```bash lium init [OPTIONS] --no-browser Print the auth URL + session ID instead of opening a browser (step 1) --session ID Verify the auth session and save the API key (step 2) ``` For an agent that already holds an API key, write the config directly instead: ```bash lium config set api.api_key YOUR_KEY lium config set ssh.key_path ~/.ssh/id_ed25519 ``` ## lium balance Show the current account balance. ```bash lium balance [OPTIONS] --json Print machine-readable JSON ``` ## lium ls List available GPU nodes. There is no positional argument — filter with `--gpu`. ```bash lium ls [OPTIONS] --gpu TEXT Filter by GPU type, e.g. A100 --count INTEGER Exact GPU count to match (e.g. 1, 8) --min-cuda FLOAT Minimum CUDA version, e.g. 12.4 --lat FLOAT Latitude for distance filtering --lon FLOAT Longitude for distance filtering --max-distance INTEGER Maximum distance in miles from --lat/--lon --sort FIELD price_gpu | price_total | loc | id | gpu | download | upload | price_per_gpu_hour | price_per_hour (an explicit --sort wins over the ★ optimal ordering) --limit INTEGER Limit the number of rows shown --format [table|json] Output format; 'json' goes to stdout, suitable for jq ``` Examples: ```bash lium ls # all nodes lium ls --gpu H100 # only H100 nodes lium ls --gpu H100 --count 8 # only 8×H100 nodes lium ls --format json # JSON output for parsing lium ls --sort price_per_gpu_hour --limit 10 ``` ## lium up Create a new pod. **Always pass `-y` for non-interactive (agent) usage.** ```bash lium up [OPTIONS] [NODE_ID] NODE_ID Node UUID, HUID, or index from the last `lium ls`. Optional — omit it and the filters below auto-select the best node. (`lium up --help` prints NODE_ID without brackets; the argument is optional all the same.) -n, --name TEXT Custom pod name -t, --template_id TEXT Template ID -v, --volume TEXT Volume spec: 'id:' or 'new:name=[,desc=]' -y, --yes Skip the confirmation prompt (REQUIRED for agent use) --gpu TEXT Filter nodes by GPU type (e.g. H200, A6000) -c, --count INTEGER Number of GPUs per pod --country TEXT Filter nodes by ISO country code (e.g. US, FR) -p, --ports INTEGER Minimum number of available ports required --ttl TEXT Auto-terminate after a duration (6h, 45m, 2d) --until TEXT Auto-terminate at a local time ("today 23:00", "tomorrow 01:00", "2025-10-20 15:30") --jupyter Install Jupyter Notebook (auto-selects a port) --no-ssh Create the pod and return instead of opening an SSH session --image TEXT Docker image to run (e.g. pytorch/pytorch:2.0) --internal-ports TEXT Internal ports to expose (comma-separated: 22,8000,8080) --dockerfile FILE Build the pod image from this Dockerfile (mutually exclusive with --image / --template_id) -e, --env TEXT Environment variables (KEY=VALUE), repeatable --entrypoint TEXT Container entrypoint --cmd TEXT Command to run in the container --ssh-name TEXT Name to register a new SSH key under (default: cli-@) --volume-encryption / --no-volume-encryption Encrypt the local volume when supported (on by default) ``` `--no-ssh` matters for agents: without it `lium up` ends by opening an interactive SSH session (or, with `--image`, by streaming container logs). Examples: ```bash # Non-interactive (for agents): lium up --gpu H100 -y --no-ssh # auto-select + default template lium up --gpu H200 --country US --name train -y --no-ssh lium up --gpu H100 --ttl 6h --jupyter -y --no-ssh # Docker-run style (streams logs instead of SSH): lium up --gpu A4000 --image pytorch/pytorch:2.0 -y lium up --gpu H100 --image vllm/vllm-openai:latest -e HF_TOKEN=xxx -y # Custom Dockerfile, built remotely: lium up --gpu A4000 --dockerfile ./Dockerfile -y # With volumes: lium up --gpu H100 -v id:brave-fox-3a -y # attach an existing volume lium up --gpu H100 -v new:name=data -y # create + attach a volume # Specific node: lium up 1 --name dev-pod -y # node #1 from the last ls ``` ## lium ps List active pods. The optional positional narrows the listing to one pod. ```bash lium ps [OPTIONS] [POD_ID] POD_ID Show a single pod — name, HUID or UUID only, NOT an index --format [table|json] Output format; 'json' goes to stdout, suitable for jq ``` `lium ps --format json` **is supported** and is the way an agent should read pod state. There is no `-a/--all` and no `--sort`. ## lium ssh Open an interactive SSH session to a pod. It takes no options — to run a command and exit, use [`lium exec`](#lium-exec). ```bash lium ssh TARGET TARGET Pod name/ID (eager-wolf-aa) or index from `lium ps` (1, 2, 3) ``` ## lium exec Execute commands on one or more pods. **This is the command an agent uses to run things remotely** — it exits with the remote command's exit code, so `lium exec "cmd" && next-step` behaves the way a caller expects. ```bash lium exec [OPTIONS] TARGETS [COMMAND] TARGETS Pod name/ID, index, comma-separated list, or "all" COMMAND Command to execute (quote multi-word commands) -s, --script TEXT Execute a local script file on the pod -e, --env TEXT Set environment variables (KEY=VALUE) --json Print machine-readable JSON (stdout, stderr, exit_code) ``` Examples: ```bash lium exec my-pod "python train.py" lium exec 1 "python --version" lium exec 1 "nvidia-smi" lium exec 1,2,3 "uptime" lium exec all "df -h" lium exec 1 --script setup.sh lium exec 1 -e API_KEY=xyz "python app.py" lium exec 1 --json "python train.py" ``` There is no `--timeout` and no `--output`; redirect the output in the shell (`lium exec 1 "nvidia-smi" > gpu.txt`). ## lium scp Copy files between the local machine and pods. Upload is the default; `-d` flips the direction. ```bash lium scp [OPTIONS] TARGETS SOURCE_PATH [DESTINATION_PATH] TARGETS Pod name/ID, index, comma-separated list, or "all" SOURCE_PATH Local file (upload) or remote path (download) DESTINATION_PATH Optional; for multiple pods a download destination must be a directory -d, --download Download from the pods to the local machine ``` Examples: ```bash lium scp 1 ./script.py # upload to ~/script.py on pod #1 lium scp eager-wolf-aa ./data.csv ~/data/ # upload into a directory lium scp all ./config.json # upload to every pod lium scp 2 /root/output.log ./outputs -d # download from pod #2 into ./outputs/ ``` There is no `-r/--recursive` and no `-p/--preserve`; use [`lium rsync`](#lium-rsync) for directories. ## lium rsync Sync a directory to pods with rsync. It takes no options. ```bash lium rsync TARGETS LOCAL_PATH [REMOTE_PATH] TARGETS Pod name/ID, index, comma-separated list, or "all" LOCAL_PATH Local directory to sync REMOTE_PATH Optional destination path ``` ## lium rm Remove (terminate) pods. Removal is irreversible. The command exits non-zero when nothing matched `TARGETS`, so a typo cannot look like a successful teardown. ```bash lium rm [OPTIONS] [TARGETS] TARGETS Pod name(s)/ID(s), index/indices, comma-separated list, or "all" -a, --all Remove all active pods -y, --yes Skip the confirmation prompt --in TEXT Schedule the removal after a duration (e.g. 6h) --at TEXT Schedule the removal at a time (e.g. "tomorrow 01:00") ``` `--in` and `--at` **schedule** a removal rather than filtering which pods to remove; cancel a scheduled one with [`lium schedules rm`](#lium-schedules). **Agent usage** — `-y` exists, no piped `yes` needed: ```bash lium rm my-pod -y # single pod lium rm -a -y # all pods lium rm 1,2,3 -y # several by index lium rm my-pod --in 6h # schedule removal in six hours ``` ## lium logs Stream logs from a pod. ```bash lium logs [OPTIONS] POD_ID POD_ID Pod name, HUID or UUID — NOT an index -n, --tail INTEGER Number of lines to show from the end of the logs -f, --follow Follow log output ``` Examples: ```bash lium logs abc123 # last 100 lines lium logs abc123 -n 50 # last 50 lines lium logs abc123 -f -n 10 # follow, with 10 lines of history ``` ## lium port-forward Forward a local port to a pod's internal port. Useful for Jupyter, TensorBoard and other web services. ```bash lium port-forward [OPTIONS] TARGET PORT TARGET Pod name/ID or index PORT The internal port on the pod to forward to -l, --local-port INTEGER Local port to bind (defaults to the same as PORT) ``` Examples: ```bash lium port-forward my-pod 8888 # localhost:8888 -> pod's 8888 lium port-forward 1 8000 -l 3000 # localhost:3000 -> pod's 8000 ``` ## lium reboot Reboot pods. ```bash lium reboot [OPTIONS] [TARGETS] TARGETS Pod name(s)/ID(s), index/indices, or "all" -a, --all Reboot all active pods --volume-id TEXT Volume ID to attach when rebooting ``` A reboot re-creates the pod: everything outside an attached volume is lost. ## lium update Update the configuration of a running pod. ```bash lium update [OPTIONS] TARGET TARGET Pod name/ID or index --jupyter INTEGER Install Jupyter Notebook on the given internal port ``` ## lium templates List available Docker templates and images. It takes no options. ```bash lium templates [SEARCH] SEARCH Text search to filter templates (e.g. "pytorch", "tensorflow") ``` **Notes**: - Without `--template_id`, `lium up` uses the default **PyTorch (CUDA)** template — fastest to start - Default Docker-in-Docker (dind) image: `daturaai/dind` ## lium volumes Manage persistent volumes. ```bash lium volumes list # list all volumes lium volumes new NAME [-d DESC] # create a volume (-d, --desc) lium volumes rm INDICES [-y, --yes] # remove by index from the last `lium volumes list` ``` `volumes rm` takes **indices from the previous listing**, not names or HUIDs — run `lium volumes list` first. ## lium bk (backups) Manage pod backup configurations. `POD_ID` is a pod name/ID or an index from `lium ps`. ```bash lium bk show POD_ID # show the backup config lium bk set POD_ID [OPTIONS] # set or update it --path TEXT Backup path (default: /root) --every TEXT Backup frequency (1h, 6h, 24h) --keep TEXT Retention period (1d, 7d, 30d) -y, --yes Skip the confirmation prompt lium bk now POD_ID [OPTIONS] # trigger an immediate backup -n, --name TEXT Backup name (e.g. 'pre-release') -d, --description TEXT Backup description lium bk logs [POD_ID] [--id ID] # backup logs, or details of one backup lium bk restore POD_ID --id ID # restore a backup (--id is required) --to TEXT Restore path (default: /root) -y, --yes Skip the confirmation prompt lium bk restore-logs [POD_ID] [--id ID] lium bk rm POD_ID [-y] # remove the backup config ``` `--path` on `bk set` is a flag, not a positional: `lium bk set 1 --path /root --every 6h --keep 7d`. ## lium schedules Manage scheduled pod terminations (the ones created by `lium rm --in/--at` and by `lium up --ttl/--until`). ```bash lium schedules list # list all pods with scheduled terminations lium schedules rm INDICES # cancel by index from the listing ``` ## lium ssh-keys Manage the SSH public keys registered with Lium. ```bash lium ssh-keys list # list the keys registered with Lium lium ssh-keys sync # register every local SSH pubkey that isn't on Lium yet ``` ## lium config Manage the CLI configuration (`~/.lium/config.ini`). ```bash lium config show # display the entire configuration lium config get api.api_key # get one value lium config set ssh.key_path ~/.ssh/key # set one value (interactive without VALUE) lium config unset api.api_key # remove one value lium config path # print the config file path lium config reset [--confirm] # reset to defaults lium config edit # open in the default editor ``` ## lium theme Set the CLI color theme. The argument is required and accepts only two values. ```bash lium theme {dark|light} ``` ## lium fund Fund the account with TAO — or with free Subnet-51 alpha stake — from a Bittensor wallet. **Always pass `-y` for agent use.** ```bash lium fund [OPTIONS] -w, --wallet TEXT Bittensor wallet name to fund from -a, --amount TEXT Amount to fund with (TAO; USD when --alpha) --alpha Fund with free Subnet-51 alpha stake -k, --hotkey TEXT Origin hotkey the alpha is staked under — SS58 address or wallet hotkey name (required with --alpha) --json Print machine-readable JSON -y, --yes Skip confirmation prompts ``` Examples: ```bash lium fund -w default -a 1.5 -y lium fund --alpha -k -a 25 -y --json # -a is USD when --alpha ``` ## lium topup Top up the balance with a stablecoin. ```bash lium topup currencies [OPTIONS] # list supported stablecoins and networks --refresh Bypass the cache and re-fetch --json Print machine-readable JSON lium topup create [OPTIONS] # create an invoice, print the deposit address -a, --amount FLOAT Top-up amount in USD (required) -c, --currency TEXT Stablecoin code, e.g. USDT (required) -n, --network TEXT Network, e.g. tron (required) --json Print machine-readable JSON ``` Send exactly the returned `crypto_amount` to the deposit address on that network; the balance is credited once the transfer confirms. ```bash lium topup create -a 20 -c USDT -n tron --json ``` ## lium mine Bootstrap a Subnet-51 **provider** machine: clone `Datura-ai/lium-io` into `compute-subnet`, install the executor tooling, write `neurons/executor/.env` and start the executor container. It runs on the GPU host you are contributing, not on a renter's laptop. `lium provider --help` calls this "renter workflows" — that blurb is wrong; the code clones and starts a miner executor. ```bash lium mine [OPTIONS] -k, --hotkey TEXT Miner hotkey SS58 address -d, --dir TEXT Target directory -b, --branch TEXT Branch to install from -a, --auto Run without prompting -v, --verbose Show the plan banner ``` ## lium provider Provider-side commands for Subnet 51 mining — a different persona from `lium mine`. Hotkey registration on SN51 itself is done with `btcli subnet register`, not here. ```bash lium provider [OPTIONS] COMMAND [ARGS]... -w, --coldkey TEXT Bittensor coldkey (wallet) name; falls back to LIUM_PROVIDER_COLDKEY, then `provider.coldkey` in the config -k, --hotkey TEXT Hotkey name on that coldkey; falls back to LIUM_PROVIDER_HOTKEY, then `provider.hotkey` --portal-url TEXT Override the lium-miner-portal base URL --json Machine-readable JSON (one envelope per command) --debug Error context on stderr; verbose logging -y, --yes Auto-confirm the persona gate for spend-affecting subcommands --dry-run Skip irreversible subprocess calls (e.g. ssh), report intent only ``` Sub-commands: `billing`, `config`, `machine`, `machine-request`, `node`, `portal`, `status`, `sync`. Run `lium provider --help` for their flags. ## lium gpu-splitting Prepare Docker storage on a host for LIUM GPU splitting. ```bash lium gpu-splitting check [--device PATH] # inspect the host, print the plan, change nothing lium gpu-splitting setup [--device PATH] --yes # end-to-end Docker storage setup lium gpu-splitting verify # verify the host meets the requirements ``` `setup` is the only one that changes the host, and it stops on an interactive confirmation of the plan — pass `--yes` from a script. ## Batch Operations `exec`, `scp`, `rsync`, `rm` and `reboot` take several targets at once, as a comma-separated list or `all`: ```bash lium exec 1,2,3 "apt update" lium exec all "nvidia-smi" lium scp all ./requirements.txt lium rsync all ./project lium rm 1,2,3 -y ``` ## Pod Targeting The pod argument is called `TARGET` (single) or `TARGETS` (several) in the CLI's own help; `logs`, `ps` and the `bk` sub-commands call it `POD_ID`. What each form accepts is **not** uniform: | Form | Example | Accepted by | |------|---------|-------------| | Name / HUID / UUID | `lium ssh eager-wolf-aa` | every command | | Index from the last `lium ps` | `lium ssh 1` | `ssh`, `exec`, `scp`, `rsync`, `rm`, `reboot`, `update`, `port-forward`, `bk *` — **not** `ps` and **not** `logs` | | Comma list | `lium exec 1,2,3 "cmd"` | `TARGETS` commands only | | All | `lium exec all "cmd"` | `TARGETS` commands only | `lium ps 1` and `lium logs 1` match the literal string `1` against pod names and IDs; they do not resolve indices, so they report the pod as not found unless a pod is actually named `1`. Indices come from the most recent listing and shift whenever anything is created or removed. Prefer names: read them once with `lium ps --format json` and pass those. ## Environment Variables ```bash LIUM_API_KEY=xyz lium ls # override the API key LIUM_DEBUG=1 lium up --gpu H100 -y # debug output LIUM_BASE_URL=https://staging.lium.io/api lium signup --email ada@example.com LIUM_SIGNUP_PASSWORD=pw lium signup --email ada@example.com # keeps the password off argv LIUM_PROVIDER_COLDKEY=... LIUM_PROVIDER_HOTKEY=... lium provider status ``` `LIUM_BASE_URL` (default `https://lium.io/api`, the `/api` suffix included) points the SDK **and** `lium signup` at another backend — use it to sign up against staging. `LIUM_PAY_URL` overrides the payments backend the same way. There is no `LIUM_SSH_KEY` variable — the SSH key path lives in the config (`lium config set ssh.key_path ...`). ## Exit Codes | Code | Meaning | |------|---------| | 0 | Success | | 1 | General error | | 2 | Configuration error (bad arguments, unreadable script, missing config) | | 4 | SSH error | | 5 | Pod not found | ⚠️ **The exit code alone is not proof of success.** Only three commands are reliable: - `lium exec` — exits with the remote command's code, `5` when no pod matched, `2` on a bad argument or unreadable script; - `lium rm` — `5` when nothing matched `TARGETS`; - `lium up` — `1` when node selection, renting or readiness fails, `2` on a bad argument, `4` when SSH is unavailable. **Everything else can print `Error: ...` and still exit 0** — including `lium ls`, whose API and authentication failures are swallowed the same way as in `ssh`, `logs`, `reboot`, `scp`, `rsync`, `port-forward`, `update` and the `bk` sub-commands. So `lium ls >/dev/null && echo OK` prints `OK` with a revoked API key. Read the output, or use `--format json` / `--json` where it exists, instead of branching on `$?` alone. Tracked as **DAH-2593**. Codes 3 and 6 exist in the source but no command produces them today. --- # Python SDK — Full Reference # Lium Python SDK Reference ## Table of Contents - [Installation & Auth](#installation--auth) - [High-Level SDK (lium.sdk.Lium)](#high-level-sdk-liumsdklium) - [@machine Decorator](#machine-decorator) - [Low-Level SDK (lium.Client)](#low-level-sdk-liumclient) - [Models](#models) - [Exceptions](#exceptions) ## Installation & Auth ```bash pip install lium.io # CLI + high-level SDK pip install lium-sdk # low-level SDK only ``` Authentication (auto-loaded in priority order): 1. Direct: `Lium(api_key="...")` or `Client(api_key="...")` 2. Environment: `LIUM_API_KEY` 3. Config file: `~/.lium/config.ini` (set via `lium init`) SSH keys auto-discovered from `~/.ssh/id_ed25519`, `~/.ssh/id_rsa`, `~/.ssh/id_ecdsa`. --- ## High-Level SDK (lium.sdk.Lium) Full-featured SDK included with `pip install lium.io`. Mirrors CLI capabilities. ```python from lium.sdk import Lium lium = Lium() ``` Signatures below follow Python notation: everything after `*` is keyword-only and raises `TypeError` when passed positionally. `lium.exec(pod, "nvidia-smi")` fails — it has to be `lium.exec(pod, command="nvidia-smi")`. ### Discovery | Method | Description | |--------|-------------| | `ls(*, gpu_type=, gpu_count=, lat=, lon=, max_distance_miles=)` | List available executors | | `ps()` | List active pods | | `pod(pod_id)` | Get pod details | | `get_executor(executor_id)` | Get executor details | | `templates(filter=, only_my=)` | List templates | | `gpu_types()` | List available GPU types | ### Pod Lifecycle | Method | Description | |--------|-------------| | `up(*, executor_id, name=, template_id=, volume_id=, ports=, ssh_keys=)` | Create pod | | `down(pod)` | Stop/delete pod | | `rm(pod)` | Alias for `down()` | | `reboot(pod, volume_id=)` | Reboot pod | | `wait_ready(pod, *, timeout=)` | Poll until pod is RUNNING | | `logs(pod_id, *, tail=, follow=)` | Stream pod logs | | `edit(pod_id, **kwargs)` | Edit pod template | ### Remote Execution | Method | Description | |--------|-------------| | `exec(pod, *, command, env=)` | Execute command, returns `{"stdout", "stderr", "exit_code", "success"}` | | `stream_exec(pod, *, command, env=)` | Stream execution output | | `exec_all(pods, *, command, env=, max_workers=)` | Execute on multiple pods | | `ssh(pod)` | Get SSH command string | ### File Transfer | Method | Description | |--------|-------------| | `scp(pod, *, local, remote)` | Copy file to pod | | `upload(pod, *, local, remote)` | Upload (alias for scp) | | `download(pod, *, remote, local)` | Download file from pod | | `rsync(pod, *, local, remote)` | Sync directory | ### Template Management | Method | Description | |--------|-------------| | `default_docker_template(executor_id)` | Get executor's default template | | `create_template(...)` | Create custom template | | `update_template(template_id, name=, docker_image=, ...)` | Update template | | `switch_template(pod, *, template_id)` | Change pod's template | | `wait_template_ready(template_id, timeout=)` | Wait for template build | ### Volume Management | Method | Description | |--------|-------------| | `volumes()` | List all volumes | | `volume(volume_id)` | Get volume info | | `volume_create(name, *, description=)` | Create volume | | `volume_update(volume_id, *, name=, description=)` | Update volume | | `volume_delete(volume_id)` | Delete volume | ### Backup Management | Method | Description | |--------|-------------| | `backup_create(pod, *, path=, frequency_hours=, retention_days=)` | Set up auto-backups | | `backup_now(pod, *, name, description=)` | Trigger immediate backup | | `backup_config(pod)` | Get backup config | | `backup_list()` | List all backups | | `backup_logs(pod)` | Get backup execution logs | | `backup_delete(config_id)` | Delete backup config | | `restore(pod, *, backup_id, restore_path=)` | Restore from backup | ### Pod Scheduling | Method | Description | |--------|-------------| | `schedule_termination(pod, *, termination_time)` | Auto-terminate at specific time | | `cancel_scheduled_termination(pod)` | Cancel auto-termination | ### Jupyter | Method | Description | |--------|-------------| | `install_jupyter(pod, *, jupyter_internal_port)` | Install Jupyter on pod | ### Account | Method | Description | |--------|-------------| | `balance()` | Get account balance | | `wallets()` | List connected wallets | | `add_wallet(bt_wallet)` | Add Bittensor wallet | | `get_my_user_id()` | Get current user ID | ### Complete Example ```python from lium.sdk import Lium lium = Lium() # Find and create executors = lium.ls(gpu_type="A100", gpu_count=8) pod = lium.up(executor_id=executors[0].id, name="my-pod") pod = lium.wait_ready(pod, timeout=600) # Execute result = lium.exec(pod, command="nvidia-smi") print(result["stdout"]) # Files lium.upload(pod, local="train.py", remote="/root/train.py") lium.exec(pod, command="python /root/train.py") lium.download(pod, remote="/root/model.pt", local="./model.pt") # Backups lium.backup_create(pod, path="/root/data", frequency_hours=24, retention_days=7) # Cleanup lium.down(pod) ``` --- ## @machine Decorator Simplest way to run code on a remote GPU. Automatically provisions, uploads, executes, returns result, and cleans up. ```python from lium.sdk import machine @machine(machine="A100", requirements=["torch", "transformers"]) def train_model(prompt: str) -> str: from transformers import AutoTokenizer, AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("gpt2") # ... your code runs on remote A100 return result result = train_model("Your prompt") ``` **Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `machine` | str | GPU type: `"A100"`, `"1xH200"`, `"2xA100"` | | `template_id` | str, optional | Docker template to use | | `cleanup` | bool, default True | Delete pod after execution | | `requirements` | list, optional | Pip packages to install before running | --- ## Low-Level SDK (lium.Client) Resource-based client from `pip install lium-sdk`. Context-manager pattern. ### Sync Client ```python import lium with lium.Client(api_key="optional") as client: pods = client.pods.list() ``` ### Async Client ```python import asyncio, lium async def main(): async with lium.AsyncClient() as client: pods = await client.pods.list() asyncio.run(main()) ``` ### Resources **client.pods:** | Method | Description | |--------|-------------| | `list()` → `list[PodList]` | List user's pods | | `retrieve(id, wait_until_running=False, timeout=300)` → `Pod` | Get pod, optionally wait | | `create(id_in_site, pod_name, template_id, user_public_key)` → `Pod` | Low-level create | | `delete(id_in_site)` → `None` | Delete pod | | `list_executors(filter_query=None)` → `list[Executor]` | List available machines | | `easy_deploy(machine_query, docker_image=, dockerfile=, template_id=, pod_name=)` → `Pod` | High-level deploy | **machine_query format for easy_deploy:** - `"H100"` — any H100 - `"1xA6000"` — exactly 1x A6000 - `"2xA100"` — exactly 2x A100 - `"H200,A100"` — H200 or A100 **client.templates:** | Method | Description | |--------|-------------| | `list()` → `list[Template]` | List templates | | `retrieve(template_id)` → `Template` | Get template | | `create(...)` → `Template` | Create template | | `delete(template_id)` → `None` | Delete template | **client.ssh_keys:** | Method | Description | |--------|-------------| | `list()` → `list[SSHKey]` | List uploaded SSH keys | | `create(name: str, public_key: str)` → `SSHKey` | Upload public key | | `delete(key_id: UUID)` → `None` | Remove SSH key | **client.docker_credentials:** | Method | Description | |--------|-------------| | `list()` → `list[DockerCredentials]` | List stored registry credentials | | `create(registry: str, username: str, password: str)` → `DockerCredentials` | Add registry credentials (for private images) | | `delete(cred_id: UUID)` → `None` | Remove credentials | --- ## Models ### ExecutorInfo (high-level SDK) | Field | Type | Description | |-------|------|-------------| | `id` | `UUID` | Executor identifier | | `huid` | `str` | Human-readable ID (e.g. "cosmic-hawk-f2") | | `gpu_type` | `str` | GPU model ("H100", "A100", etc.) | | `gpu_count` | `int` | Number of GPUs | | `price_per_hour` | `float` | USD per hour | | `location` | `str` | Country/region | | `specs` | `dict` | Hardware specs (RAM, storage, etc.) | | `status` | `str` | Availability status | | `docker_in_docker` | `bool` | DinD support | | `ip` | `str` | Machine IP | ### Executor (low-level SDK) | Field | Type | Description | |-------|------|-------------| | `id` | `UUID` | Executor identifier | | `gpu_type` | `str` | GPU model | | `gpu_count` | `int` | Number of GPUs | | `price` | `float` | USD per hour | | `location` | `str` | Country/region | | `driver_version` | `str` | NVIDIA driver version | | `docker_in_docker` | `bool` | DinD support | ### PodInfo / Pod | Field | Type | Description | |-------|------|-------------| | `id` | `UUID` | Pod identifier | | `name` | `str` | Pod name | | `status` | `str` | "RUNNING", "STOPPED", "PENDING", etc. | | `huid` | `str` | Human-readable ID | | `ssh_cmd` | `str` | Ready-to-use SSH command | | `ssh_ip` | `str` | SSH host | | `ssh_port` | `int` | SSH port | | `ports` | `list[dict]` | Allocated port mappings | | `executor` | `Executor` | Associated executor info | | `template` | `Template` | Docker template used | | `created_at` | `datetime` | Creation timestamp | | `removal_scheduled_at` | `datetime | None` | Scheduled termination time | | `jupyter_url` | `str | None` | Jupyter URL if enabled | ### Template | Field | Type | Description | |-------|------|-------------| | `id` | `UUID` | Template identifier | | `name` | `str` | Template name | | `huid` | `str` | Human-readable ID | | `docker_image` | `str` | Docker image name | | `docker_image_tag` | `str` | Image tag | | `category` | `str` | Template category (ml, web, etc.) | | `status` | `str` | Build status | ### VolumeInfo | Field | Type | Description | |-------|------|-------------| | `id` | `UUID` | Volume identifier | | `huid` | `str` | Human-readable ID | | `name` | `str` | Volume name | | `description` | `str` | Volume description | | `current_size_bytes` | `int` | Current storage used | | `current_file_count` | `int` | Number of files | ### BackupConfig | Field | Type | Description | |-------|------|-------------| | `id` | `UUID` | Config identifier | | `pod_executor_id` | `UUID` | Associated pod | | `backup_frequency_hours` | `int` | Backup interval in hours | | `retention_days` | `int` | Days to keep backups | | `backup_path` | `str` | Path being backed up | | `is_active` | `bool` | Whether backups are enabled | --- ## Exceptions High-level SDK (`lium.sdk`): | Exception | Trigger | |-----------|---------| | `LiumError` | Base exception | | `LiumAuthError` | Invalid API key (401) | | `LiumNotFoundError` | Resource not found (404) | | `LiumRateLimitError` | Rate limit exceeded (429) | | `LiumServerError` | Server errors (5xx) | Enable debug logging: ```python import logging logging.basicConfig(level=logging.DEBUG) ``` Or set `LIUM_DEBUG=1` environment variable.