Metadata-Version: 2.4
Name: fcloud-sdk
Version: 0.3.0
Summary: Python SDK and CLI for the fcloud GPU compute platform
Author: fcloud
License-Expression: Apache-2.0
Keywords: gpu,cloud,compute,ml,training,cli
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
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: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Distributed Computing
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Requires-Dist: aiohttp>=3.9
Requires-Dist: requests>=2.31
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-xdist>=3.5; extra == "dev"
Requires-Dist: ruff>=0.8; extra == "dev"
Requires-Dist: mypy>=1.10; extra == "dev"
Requires-Dist: types-requests; extra == "dev"
Dynamic: license-file

# fcloud

Python SDK and CLI for the fcloud GPU compute platform: provision GPU or CPU
hosts, run commands and scripts on them, keep a persistent workspace between
runs, move files in and out, and drive long-running or batch jobs — from a
terminal, from Python, or from an AI coding agent.

Requires Python 3.9+ (see [Python versions](#python-versions)).

## Install

```bash
curl -fsSL https://fcloud-home.vercel.app/install.sh | sh
```

That installs the CLI from PyPI (via `uv` or `pipx`), signs you in through
the browser, and installs the fcloud skill for coding agents on your machine.
With your own Python:

```bash
pip install fcloud-sdk
fcloud login
```

fcloud is prepaid — `fcloud credits buy 25` before your first run, and
`fcloud credits` to see the balance.

### Python versions

Every row runs the full test suite in CI on every push (the matrix in
`.github/workflows/unit-tests.yml` is the source of truth; a CI check fails
if `pyproject.toml`'s classifiers drift from it).

| Python | Status |
|--------|--------|
| 3.14   | supported |
| 3.13   | supported |
| 3.12   | supported |
| 3.11   | supported |
| 3.10   | supported |
| 3.9    | supported (macOS command-line-tools Python, Debian 11) |
| 3.8 and older | not supported; the installer script bootstraps `uv`, which fetches a supported Python |

The installer only uses your system Python (`pipx`/`pip`) when it is 3.9 or
newer; otherwise it installs `uv`, which brings its own.

## Setup

`fcloud login` opens https://fcloud-home.vercel.app to sign in (Google or
email) and add a card, then saves a key minted for this machine. Running any
`fcloud` command on a fresh install starts the same login.

```bash
fcloud login [--no-browser] [--agents auto|all|cursor|claude|codex|none]
fcloud set_token <key>  # already have a key (CI, agents, a second machine)
fcloud setup            # save a key you already have; install agent skills
fcloud health           # verify connectivity
```

`fcloud login` installs the fcloud skill file for the coding agents it finds
(Cursor, Claude Code, Codex — `--agents all` for every one) so an agent can
drive fcloud for you. `fcloud setup --agents none` skips that.

## Quick start

```bash
# Run a one-off command on a GPU
fcloud exec --sku gpu_1x_l4 nvidia-smi -L

# Upload a script and run it
fcloud run train.py --sku gpu_1x_l4

# Upload a project directory, run one script in it, pass arguments through
fcloud run . --script train.py --sku gpu_1x_l4 -- --epochs 50

# See what hardware is available
fcloud skus
```

`fcloud run <dir>` uploads the whole directory. Keep secrets, virtualenvs and
large data out of the tree you point it at.

## How the pieces fit

- **session** — a persistent `/workspace` filesystem, not a held GPU. It costs
  nothing until used; `exec`/`run`/`shell`/`upload` bring it online with its
  files intact, and `fcloud stop` halts spend but keeps the files. Address an
  existing one with `--on <SID>`.
- **volume** — a named folder you mount into a session with `--volume`; this is
  how data moves between sessions. Writes commit back as a new version when the
  session detaches or closes.
- **process** — work running inside a session. `exec`/`run` wait in the
  foreground; `spawn` returns a process id you then `wait`/`logs`/`kill`.
- **job** — a run-to-completion session with no saved filesystem; its durable
  outputs are its volumes and its logs.
- **sweep** — `fcloud map` fans one command out over many argument bindings as a
  durable batch you inspect with `fcloud sweep`.

## CLI

`fcloud help` prints the full grouped list; `fcloud help <command>` prints details.

```
Find hardware
  fcloud skus                                          List SKUs and prices
  fcloud health                                        Check API connectivity

Run code
  fcloud exec [--sku SKU] [--on SID] <cmd...>          Run a command on a host
  fcloud run <file|dir> [--sku SKU] [--on SID]         Upload and run a script
  fcloud shell [--sku SKU] [--on SID] [--volume NAME]  Interactive shell
  fcloud ssh <SID>                                     SSH into a session
  fcloud tunnel <SID> [--port PORT]                    SSH ProxyCommand tunnel

Background processes (inside a session)
  fcloud spawn --on SID <cmd...>                       Start a background process
  fcloud wait <SID> <PID> [--timeout DUR]              Wait for it; exit with its code
  fcloud logs <SID> [PID] [--follow]                   Show process output
  fcloud kill <SID> <PID>                              Kill it

Sessions (the persistent filesystem)
  fcloud create [--sku SKU] [--min-disk-gb N]          Create a session ($0 until used)
  fcloud sessions [SID] [--all] [--limit N]            List sessions
  fcloud history <SID> [--limit N]                     Session event history
  fcloud stop <SID>                                    Stop now (files kept)

Files
  fcloud upload [--on SID] <local> [remote]            Upload files
  fcloud download [--on SID] <remote> [local]          Download a file
  fcloud ls <SID> [path]                               List a session's files
  fcloud mount <SID> <mountpoint>                      Mount session files read-only (needs rclone)

Volumes (data that outlives a session)
  fcloud volume <create|list|files|download|cat|import|delete> ...

Batch
  fcloud job <run|ls|logs|wait|kill> ...               Run-to-completion jobs
  fcloud map [--sku SKU] -- <cmd {}> ::: v1,v2...      Fan a command out over bindings
  fcloud sweeps / fcloud sweep <status|logs|retry|cancel|wait> <name>

Setup
  fcloud login [--no-browser] [--agents auto|all|cursor|claude|codex|none]
  fcloud setup [--token KEY] [--agents all|cursor|claude|codex|none]
  fcloud set_token <api-key>
  fcloud --version
```

All commands accept `--json` for machine-readable output.

`fcloud exec` returns bounded stdout by default. If `--json` reports
`stdout_truncated: true`, fetch the full log instead of rerunning:

```bash
fcloud logs <session-id> <process-id> --output all
fcloud logs <session-id> <process-id> --stream stderr --output all
```

## Python SDK

```python
import fcloud

client = fcloud.Client()

image = fcloud.Image.debian_slim().pip_install(["torch", "numpy"])
project = client.project("my-run", image=image)

with project.session(sku="gpu_1x_l4") as s:
    s.upload("./data", "data/")
    result = s.run(["python3", "/workspace/data/train.py"])
    print(result.stdout)
    if result.stdout_truncated:
        print(s.logs(result.process_id, output_range="all").output)
    weights = s.download("model.pt")
```

Errors raise `fcloud.FcloudError` (or a subclass such as `PaymentOverdueError`).

## Porting from Modal

`fcloud` exposes a Modal-compatible surface, so most Modal scripts port with an
import rename:

```python
import fcloud as modal  # was: import modal

app = modal.App("demo")
image = modal.Image.debian_slim().pip_install("torch", "numpy")

@app.function(image=image, gpu="H100", timeout=600)
def train(steps: int) -> float:
    ...

@app.cls(gpu="L4", volumes={"/data": modal.Volume.from_name("weights", create_if_missing=True)})
class Model:
    @modal.enter()
    def load(self): ...
    @modal.method()
    def predict(self, x): ...

@app.local_entrypoint()
def main(steps: int = 100):
    print(train.remote(steps))
    print(Model().predict.remote(1))
```

Run it with `fcloud run demo.py [--steps 500]` (the Modal-style `demo.py::name`
picks an entrypoint or function). Each `(gpu, image, volumes)` combination gets
one warm session; `.remote()` pickles the args, runs the function on the host
and returns the pickled result, streaming stdout back live.

Supported: `App`, `@app.function` / `@app.cls` / `@app.local_entrypoint`,
`.remote()` / `.spawn()` / `.map()`, `Image.*` (varargs or list),
`Volume.from_name`, `Secret.from_dict` / `from_dotenv` / `from_local_environ`,
`gpu="H100"`, `"A100-80GB:8"`, etc. `cpu=`, `memory=`, `retries=` and similar
options are accepted and ignored with a warning.

Not supported: web endpoints, `Dict` / `Queue`, schedules, sandboxes,
`Secret.from_name` (no hosted secret store), `modal deploy`. `.map()` runs
inputs sequentially on one session; use `fcloud map` for real fan-out.

## Configuration

API key, in order of precedence:

1. `api_key=` passed to `Client()`
2. `FCLOUD_API_KEY` exported in the shell
3. Saved token in `~/.fcloud/token` (from `fcloud setup` / `fcloud set_token`)
4. `FCLOUD_API_KEY` in the nearest `.env` file (searched upward from the cwd)

API URL, in order of precedence:

1. `url=` passed to `Client()`
2. `FCLOUD_URL` exported in the shell
3. Saved URL in `~/.fcloud/url`
4. `FCLOUD_URL` in the nearest `.env` — only honoured when that same `.env` is
   also supplying the API key, so a checked-out repo can't redirect a saved
   token elsewhere
5. `https://fcloud-dispatcher.fly.dev`

Other environment switches:

| Variable | Effect |
|---|---|
| `FCLOUD_QUIET=1` | Suppress "still waiting" progress lines while a host is provisioned |
| `FCLOUD_QUEUE_TIMEOUT=<seconds>` | How long to wait for capacity before giving up (default 1200) |
| `FCLOUD_MIGRATE_RESTART=never` | Don't automatically re-run a command after a host rebuild (default `auto`) |
| `FCLOUD_CHECKPOINT=off` | Default checkpoint/restore policy for new sessions. `off`: a preempted session rebuilds cold on any available host (`/workspace` kept, processes lost) instead of restoring pinned to its checkpoint's region. Per-session: `--checkpoint`/`--no-checkpoint`; per-user: `fcloud config set checkpoint off`; per-project: `fcloud.json` `"checkpoint": false` |
| `FCLOUD_INSECURE_HTTP=1` | Allow a plaintext `http://` API URL to a non-loopback host (refused by default — the API key would travel unencrypted). Loopback URLs never need this |
| `FCLOUD_TELEMETRY=0` | Disable all client telemetry. When enabled (the default), the client reports failures the backend cannot otherwise see — an uncaught CLI error, a queue-wait timeout, exhausted connect retries — as a fixed-allowlist payload (session id, event type, error class, truncated message, SKU/timing fields); never file contents, paths from OS errors, or credentials |

A `fcloud.json` at the project root can set defaults (image build steps, default
volumes, checkpoint policy); `fcloud config` stores per-user defaults in
`~/.fcloud/config.json`. Note that fcloud will run the build steps it finds there, so treat a
cloned repo's `fcloud.json` the way you would its Dockerfile.

## Agent skill

`fcloud setup` installs `SKILL.md` by default (`--agents none` to skip) for supported coding agents. It
is the long-form, agent-oriented guide: workflow patterns, monitoring loops,
and anti-patterns.

## Development

```bash
pip install -e ".[dev]"
pytest
ruff check src tests
```

## License

Apache License 2.0 — see [LICENSE](LICENSE).
