Internal Datura/Lium wrapper over the Shadeform GPU API: rent, list, connect to and tear down GPU machines on demand.
One core, several thin faces (the same shape we use for Discord in lium-support):
ShadeformClient raw async REST + pydantic models (client.py)
|
ShadeformService business rules / safety invariants (service.py)
|
+--+-----------------+
CLI (`shade`) MCP tools / REST router (later, for support-bot.lium.io)
Safety rule for mutations: all machines belong to the company, but a
destructive action always shows what it will destroy and asks for
confirmation first. up always bakes the team ssh whitelist
(src/shadeform_sdk/keys/whitelist.txt) into the boot script and tags the
machine with owner: / reason: / via:; server-side auto-delete
(auto_delete) is set only on explicit --ttl / --max-spend.
uv sync
export SHADEFORM_API_KEY=... # from platform.shadeform.ai -> settings/apiuv run shade offers # rentable GPU offers, cheapest first
uv run shade offers -g H100 -n 8
uv run shade ps # the account's machines
# create: cheapest offer for the GPU, confirm price, wait for ssh
uv run shade up -g A6000 -r "sysbox canary" # name = <owner>-<reason-slug>
uv run shade up -g CPU -n 0 -r "smoke" --ttl 2h # server-side auto-delete in 2h
uv run shade up -g H100 --cloud massedcompute --max-spend 50 -y
uv run shade info <id-or-name> # one machine in full detail
uv run shade ssh <id-or-name> # open an ssh session
uv run shade rm <id-or-name> # shows the machine, asks, then deletesfrom shadeform_sdk import build_service
service = build_service() # reads SHADEFORM_API_KEY from the env
try:
offers = await service.offers(gpu_type="H100", num_gpus=8)
mine = await service.instances()
finally:
await service.aclose()- read-only core +
offers/ps -
up(create + wait active + wait ssh) with price confirmation -
ssh/info -
rm(delete) with confirm-before-destroy - MCP face (FastMCP) + REST router — lives in
lium-support(shadeform_api.py)
uv run pytest