fcloud-sdk 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/CHANGELOG.md +101 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/PKG-INFO +89 -11
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/README.md +85 -9
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/SKILL.md +23 -2
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/pyproject.toml +7 -4
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/SKILL.md +23 -2
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/__init__.py +21 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/__main__.py +2 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/_direct_bridge.py +92 -19
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/_legacy_env.py +5 -0
- fcloud_sdk-0.3.0/src/fcloud/_modal_runner.py +101 -0
- fcloud_sdk-0.3.0/src/fcloud/billing_types.py +241 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/__init__.py +2 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/attach.py +43 -6
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/common.py +2 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/console.py +3 -1
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/context.py +49 -0
- fcloud_sdk-0.3.0/src/fcloud/cli/credits.py +215 -0
- fcloud_sdk-0.3.0/src/fcloud/cli/credits_buy.py +97 -0
- fcloud_sdk-0.3.0/src/fcloud/cli/credits_render.py +129 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/exec_cmd.py +4 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/files.py +23 -10
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/help.py +7 -3
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/interactive.py +7 -1
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/job.py +63 -4
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/main.py +18 -1
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/migration.py +14 -1
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/processes.py +42 -2
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/run.py +16 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/sessions.py +128 -10
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/setup.py +33 -34
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/ssh.py +2 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/sweep.py +41 -4
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/sweep_harvest.py +10 -1
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/sweep_watch.py +12 -2
- fcloud_sdk-0.3.0/src/fcloud/cli/usage.py +69 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/volume.py +7 -1
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/wait.py +9 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli_args.py +11 -2
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/client.py +48 -2
- fcloud_sdk-0.3.0/src/fcloud/client_billing.py +105 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/config.py +24 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/direct.py +13 -5
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/errors.py +15 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/image.py +60 -12
- fcloud_sdk-0.3.0/src/fcloud/login.py +236 -0
- fcloud_sdk-0.3.0/src/fcloud/modal_compat.py +306 -0
- fcloud_sdk-0.3.0/src/fcloud/modal_objects.py +137 -0
- fcloud_sdk-0.3.0/src/fcloud/modal_run.py +93 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/providers/requests_http.py +9 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/setup_cmd.py +15 -3
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/sweeps.py +2 -2
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/types.py +1 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/v2_connect.py +10 -2
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud_sdk.egg-info/PKG-INFO +89 -11
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud_sdk.egg-info/SOURCES.txt +22 -0
- fcloud_sdk-0.3.0/tests/test_credit_preflight.py +103 -0
- fcloud_sdk-0.3.0/tests/test_credits_cli.py +237 -0
- fcloud_sdk-0.3.0/tests/test_credits_errors.py +106 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_direct_bridge.py +152 -4
- fcloud_sdk-0.3.0/tests/test_download_promotion_retry.py +92 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_exec_attach_retry.py +23 -1
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_first_run_login.py +18 -15
- fcloud_sdk-0.3.0/tests/test_job_ls.py +99 -0
- fcloud_sdk-0.3.0/tests/test_login.py +202 -0
- fcloud_sdk-0.3.0/tests/test_logs_unknown_pid.py +84 -0
- fcloud_sdk-0.3.0/tests/test_map_bake_flag.py +26 -0
- fcloud_sdk-0.3.0/tests/test_modal_compat.py +171 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_rate_limit_ride.py +2 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_ride_cap.py +1 -1
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_sessions_live_statuses.py +17 -0
- fcloud_sdk-0.3.0/tests/test_spend.py +203 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_sweep_harvest.py +27 -0
- fcloud_sdk-0.3.0/tests/test_v2_connect_billing.py +52 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_volume_cli_hardening.py +9 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_wait.py +26 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/CONTRIBUTING.md +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/LICENSE +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/MANIFEST.in +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/NOTICE +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/setup.cfg +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/autoresearch/SETUP.md +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/autoresearch/program.template.md +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/autoresearch.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/config_cmd.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/hardware.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/mount.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/output.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/cli/registry.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/client_projects.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/client_sessions.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/client_volumes.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/fileset.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/job.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/providers/__init__.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/py.typed +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/session.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/shell.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/tunnel.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/version.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/volume_wait.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud/volumes.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud_sdk.egg-info/dependency_links.txt +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud_sdk.egg-info/entry_points.txt +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud_sdk.egg-info/requires.txt +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/fcloud_sdk.egg-info/top_level.txt +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/src/foom/__init__.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/conftest.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_autoresearch.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_checkpoint_option.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_cli_args.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_cli_dispatch.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_cli_guards.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_cli_help.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_client.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_client_contracts_c.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_client_host_frames_contract.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_client_http_contract.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_client_telemetry.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_config.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_config_cmd.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_default_image.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_direct_fake_host.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_dotenv_precedence.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_download_volume_hint.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_emit_pid.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_fileset.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_job.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_kill_records_first.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_legacy_shim.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_map_dag.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_map_default_image.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_map_window_flags.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_no_resume_gate.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_progress_narration.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_provider_env_default.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_queued_reason_render.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_rebuild_respawn.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_resume_volume_validation.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_run_migration.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_run_volume_collision.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_run_wait_flags.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_secret_resolution.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_session_truth.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_sessions_point_lookup.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_setup_cmd.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_shell_env.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_shell_interrupt.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_shell_volumes.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_skus.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_stop_refused_classification.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_sweep_boost.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_sweep_cost_optin.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_sweep_lifecycle_ux.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_sweep_retry_and_kill_truth.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_sweep_status_view.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_sweeps_binding.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_tunnel.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_types.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_upload_s3_path.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_v2_connect.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_volume_download_delete.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_volume_preflight.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_volume_resolution.py +0 -0
- {fcloud_sdk-0.2.0 → fcloud_sdk-0.3.0}/tests/test_volume_wait.py +0 -0
|
@@ -4,6 +4,107 @@ All notable changes to the `fcloud` Python SDK and CLI are recorded here.
|
|
|
4
4
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
|
|
5
5
|
versions follow [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.3.0] - 2026-09-16
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- `exec` no longer returns empty output for a command whose last write lands
|
|
13
|
+
at process exit. The host mirrors a process's durable log on a background
|
|
14
|
+
goroutine, so a read issued the instant the process reports "exited" can
|
|
15
|
+
beat that write and come back empty; the client was answering with that
|
|
16
|
+
empty read even though it had already drained the output off the live
|
|
17
|
+
stream. It now keeps whichever of the two saw more bytes. The symptom was
|
|
18
|
+
`stdout: ""` alongside `returncode: 0` — most often for a block-buffered
|
|
19
|
+
program, which flushes everything during interpreter teardown
|
|
20
|
+
microseconds before it dies. A training run's final `print(metrics)` is
|
|
21
|
+
exactly that shape.
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- `fcloud map --no-bake` opts a sweep out of canary auto-bake. By default,
|
|
25
|
+
packages the canary pip-installs are baked into the sweep's image before
|
|
26
|
+
the fan-out; the canary pass line now names them and points at
|
|
27
|
+
`image.pip` in `fcloud.json` as the permanent home.
|
|
28
|
+
- `fcloud credits`: the prepaid wallet from the terminal. `fcloud credits`
|
|
29
|
+
shows the posted balance, what is available after unbilled usage,
|
|
30
|
+
auto-refill state and the saved card; `fcloud credits buy <usd>` opens a
|
|
31
|
+
hosted checkout and waits for the purchase to post; `fcloud credits
|
|
32
|
+
autorefill on|off|set` changes the top-up rule (turning it off warns that
|
|
33
|
+
work stops at $0 and needs a confirmation); `fcloud credits statement`
|
|
34
|
+
lists ledger rows with a running balance.
|
|
35
|
+
- `InsufficientCreditError` (HTTP 402, `code: insufficient_credit`) — raised
|
|
36
|
+
when the prepaid balance is exhausted, and mapped to **exit code 4** in
|
|
37
|
+
the CLI with the top-up command named. It is a sibling of `FcloudError`,
|
|
38
|
+
not of `PaymentOverdueError`: nothing is owed, so the remedy is to add
|
|
39
|
+
credit, not to settle a bill. Code that catches `PaymentOverdueError`
|
|
40
|
+
alone will not catch it.
|
|
41
|
+
- `Client.credit_balance()`, `credit_ledger()`, `set_autorefill()` and
|
|
42
|
+
`start_credit_checkout()`, with typed payloads in `fcloud.billing_types`.
|
|
43
|
+
- A low-balance warning before `exec`/`run`/`shell`/`create`/`job run`/`map`
|
|
44
|
+
provision anything, shown once per process and only when nothing will top
|
|
45
|
+
the account up. Best-effort: a failed billing read never blocks a run.
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
- An out-of-credit refusal is no longer reported as a missed payment. Both
|
|
49
|
+
error paths now branch on the dispatcher's `code` before falling back on
|
|
50
|
+
the bare 402 status, so `payment_overdue`, `insufficient_credit` and an
|
|
51
|
+
older dispatcher's codeless 402 are told apart.
|
|
52
|
+
- A session closed for want of credit says so ("stopped: out of credits")
|
|
53
|
+
and exits 4, instead of exiting 75 as if it were briefly unreachable.
|
|
54
|
+
- `fcloud sweep` offers the fast lane only for waits more capacity can fix.
|
|
55
|
+
Tasks blocked on billing get a top-up hint — boosting past that block
|
|
56
|
+
buys nothing.
|
|
57
|
+
- Python 3.9 is now supported (`requires-python >= 3.9`), and 3.14 is
|
|
58
|
+
tested. The README has a support grid; CI runs the suite on 3.9 through
|
|
59
|
+
3.14. The installer script falls back to `uv` instead of `pipx`/`pip`
|
|
60
|
+
when the system Python is older than 3.9.
|
|
61
|
+
- The CLI no longer prints urllib3's LibreSSL warning on macOS's bundled
|
|
62
|
+
Python.
|
|
63
|
+
|
|
64
|
+
## [0.2.1] - 2026-09-14
|
|
65
|
+
|
|
66
|
+
### Added
|
|
67
|
+
- `fcloud login`: sign in through the browser (device authorization). Shows a
|
|
68
|
+
short code, opens the approval page, and saves a key minted for this
|
|
69
|
+
machine (named `cli:<hostname>`); then installs the fcloud skill for the
|
|
70
|
+
coding agents present on the machine (`--agents auto`, the default; also
|
|
71
|
+
`all`, a list, or `none`). `--no-browser` prints the URL and code instead
|
|
72
|
+
of opening a browser. A fresh install's first command starts the same
|
|
73
|
+
login, and `fcloud setup` with no key delegates to it.
|
|
74
|
+
- Spend visibility: `fcloud sessions` shows `$/HR` (the rate billed on the
|
|
75
|
+
session's open usage interval while a host is held, 0 when cold), `SPENT`
|
|
76
|
+
(compute charged so far) and `AGE` (time on its current host) per row plus
|
|
77
|
+
a "Spending now" footer; `--json` rows carry `usd_per_hour`, `spent_usd`
|
|
78
|
+
and `host_age_seconds` (SDK: `Client.session_cost`). New `fcloud usage [--since DUR|TS] [--until TS]`
|
|
79
|
+
reports what the account was charged over a window (default 30 days) by
|
|
80
|
+
resource and session (SDK: `Client.billing_usage`).
|
|
81
|
+
|
|
82
|
+
### Changed
|
|
83
|
+
- `fcloud job ls` shows each job's COMMAND and EXIT code (from its process
|
|
84
|
+
index; `--json` rows carry `command` and `exit_code`), so rows are
|
|
85
|
+
tellable apart and failures visible without opening each job.
|
|
86
|
+
|
|
87
|
+
### Fixed
|
|
88
|
+
- `fcloud logs <sid> <pid>` with a pid the dispatcher has no record of now
|
|
89
|
+
exits 1 (`process_not_found`) like `wait` and `kill`, instead of printing
|
|
90
|
+
an empty stream with exit 0 (and `status: ""` in `--json`) that a poller
|
|
91
|
+
read as a finished, silent process. A just-spawned pid gets a few seconds
|
|
92
|
+
to reach the durable index before it is called unknown.
|
|
93
|
+
- `fcloud wait` with a `--timeout` shorter than the unknown-pid grace window
|
|
94
|
+
exits 1 for an unknown pid, not 75 (retry), so scripted callers stop
|
|
95
|
+
retrying a typo forever.
|
|
96
|
+
- Cold starts of `run`/`exec`/`job run` no longer dump every host status
|
|
97
|
+
frame (`⟵ session_status: building (phase_change) setup_timing ...`);
|
|
98
|
+
the fresh-allocation and reconnect paths now use the same quiet printer
|
|
99
|
+
as warm attaches.
|
|
100
|
+
- `fcloud sweep harvest` no longer pulls the session's virtual `_logs/`
|
|
101
|
+
directory (`_logs/processes.json`) for a results glob like `*.json`; name
|
|
102
|
+
`_logs/` in the glob to fetch logs.
|
|
103
|
+
- `fcloud sessions` sizes the HOST and SKU columns to the data (27-char AWS
|
|
104
|
+
host ids overflowed the fixed width on every row).
|
|
105
|
+
- `fcloud volume list` shows each volume's last update time.
|
|
106
|
+
- SKILL.md documents `fcloud sessions --limit N`.
|
|
107
|
+
|
|
7
108
|
## [0.2.0] - 2026-09-08
|
|
8
109
|
|
|
9
110
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fcloud-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Python SDK and CLI for the fcloud GPU compute platform
|
|
5
5
|
Author: fcloud
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -11,14 +11,16 @@ Classifier: Intended Audience :: Developers
|
|
|
11
11
|
Classifier: Intended Audience :: Science/Research
|
|
12
12
|
Classifier: Operating System :: OS Independent
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
15
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
20
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
21
|
Classifier: Topic :: System :: Distributed Computing
|
|
20
22
|
Classifier: Typing :: Typed
|
|
21
|
-
Requires-Python: >=3.
|
|
23
|
+
Requires-Python: >=3.9
|
|
22
24
|
Description-Content-Type: text/markdown
|
|
23
25
|
License-File: LICENSE
|
|
24
26
|
License-File: NOTICE
|
|
@@ -39,29 +41,61 @@ hosts, run commands and scripts on them, keep a persistent workspace between
|
|
|
39
41
|
runs, move files in and out, and drive long-running or batch jobs — from a
|
|
40
42
|
terminal, from Python, or from an AI coding agent.
|
|
41
43
|
|
|
42
|
-
Requires Python 3.
|
|
44
|
+
Requires Python 3.9+ (see [Python versions](#python-versions)).
|
|
43
45
|
|
|
44
46
|
## Install
|
|
45
47
|
|
|
48
|
+
```bash
|
|
49
|
+
curl -fsSL https://fcloud-home.vercel.app/install.sh | sh
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
That installs the CLI from PyPI (via `uv` or `pipx`), signs you in through
|
|
53
|
+
the browser, and installs the fcloud skill for coding agents on your machine.
|
|
54
|
+
With your own Python:
|
|
55
|
+
|
|
46
56
|
```bash
|
|
47
57
|
pip install fcloud-sdk
|
|
58
|
+
fcloud login
|
|
48
59
|
```
|
|
49
60
|
|
|
61
|
+
fcloud is prepaid — `fcloud credits buy 25` before your first run, and
|
|
62
|
+
`fcloud credits` to see the balance.
|
|
63
|
+
|
|
64
|
+
### Python versions
|
|
65
|
+
|
|
66
|
+
Every row runs the full test suite in CI on every push (the matrix in
|
|
67
|
+
`.github/workflows/unit-tests.yml` is the source of truth; a CI check fails
|
|
68
|
+
if `pyproject.toml`'s classifiers drift from it).
|
|
69
|
+
|
|
70
|
+
| Python | Status |
|
|
71
|
+
|--------|--------|
|
|
72
|
+
| 3.14 | supported |
|
|
73
|
+
| 3.13 | supported |
|
|
74
|
+
| 3.12 | supported |
|
|
75
|
+
| 3.11 | supported |
|
|
76
|
+
| 3.10 | supported |
|
|
77
|
+
| 3.9 | supported (macOS command-line-tools Python, Debian 11) |
|
|
78
|
+
| 3.8 and older | not supported; the installer script bootstraps `uv`, which fetches a supported Python |
|
|
79
|
+
|
|
80
|
+
The installer only uses your system Python (`pipx`/`pip`) when it is 3.9 or
|
|
81
|
+
newer; otherwise it installs `uv`, which brings its own.
|
|
82
|
+
|
|
50
83
|
## Setup
|
|
51
84
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
walks you through sign-up and saves the key you paste.
|
|
85
|
+
`fcloud login` opens https://fcloud-home.vercel.app to sign in (Google or
|
|
86
|
+
email) and add a card, then saves a key minted for this machine. Running any
|
|
87
|
+
`fcloud` command on a fresh install starts the same login.
|
|
56
88
|
|
|
57
89
|
```bash
|
|
58
|
-
fcloud
|
|
90
|
+
fcloud login [--no-browser] [--agents auto|all|cursor|claude|codex|none]
|
|
91
|
+
fcloud set_token <key> # already have a key (CI, agents, a second machine)
|
|
92
|
+
fcloud setup # save a key you already have; install agent skills
|
|
59
93
|
fcloud health # verify connectivity
|
|
60
94
|
```
|
|
61
95
|
|
|
62
|
-
`fcloud
|
|
63
|
-
|
|
64
|
-
|
|
96
|
+
`fcloud login` installs the fcloud skill file for the coding agents it finds
|
|
97
|
+
(Cursor, Claude Code, Codex — `--agents all` for every one) so an agent can
|
|
98
|
+
drive fcloud for you. `fcloud setup --agents none` skips that.
|
|
65
99
|
|
|
66
100
|
## Quick start
|
|
67
101
|
|
|
@@ -141,6 +175,7 @@ Batch
|
|
|
141
175
|
fcloud sweeps / fcloud sweep <status|logs|retry|cancel|wait> <name>
|
|
142
176
|
|
|
143
177
|
Setup
|
|
178
|
+
fcloud login [--no-browser] [--agents auto|all|cursor|claude|codex|none]
|
|
144
179
|
fcloud setup [--token KEY] [--agents all|cursor|claude|codex|none]
|
|
145
180
|
fcloud set_token <api-key>
|
|
146
181
|
fcloud --version
|
|
@@ -177,6 +212,49 @@ with project.session(sku="gpu_1x_l4") as s:
|
|
|
177
212
|
|
|
178
213
|
Errors raise `fcloud.FcloudError` (or a subclass such as `PaymentOverdueError`).
|
|
179
214
|
|
|
215
|
+
## Porting from Modal
|
|
216
|
+
|
|
217
|
+
`fcloud` exposes a Modal-compatible surface, so most Modal scripts port with an
|
|
218
|
+
import rename:
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
import fcloud as modal # was: import modal
|
|
222
|
+
|
|
223
|
+
app = modal.App("demo")
|
|
224
|
+
image = modal.Image.debian_slim().pip_install("torch", "numpy")
|
|
225
|
+
|
|
226
|
+
@app.function(image=image, gpu="H100", timeout=600)
|
|
227
|
+
def train(steps: int) -> float:
|
|
228
|
+
...
|
|
229
|
+
|
|
230
|
+
@app.cls(gpu="L4", volumes={"/data": modal.Volume.from_name("weights", create_if_missing=True)})
|
|
231
|
+
class Model:
|
|
232
|
+
@modal.enter()
|
|
233
|
+
def load(self): ...
|
|
234
|
+
@modal.method()
|
|
235
|
+
def predict(self, x): ...
|
|
236
|
+
|
|
237
|
+
@app.local_entrypoint()
|
|
238
|
+
def main(steps: int = 100):
|
|
239
|
+
print(train.remote(steps))
|
|
240
|
+
print(Model().predict.remote(1))
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Run it with `fcloud run demo.py [--steps 500]` (the Modal-style `demo.py::name`
|
|
244
|
+
picks an entrypoint or function). Each `(gpu, image, volumes)` combination gets
|
|
245
|
+
one warm session; `.remote()` pickles the args, runs the function on the host
|
|
246
|
+
and returns the pickled result, streaming stdout back live.
|
|
247
|
+
|
|
248
|
+
Supported: `App`, `@app.function` / `@app.cls` / `@app.local_entrypoint`,
|
|
249
|
+
`.remote()` / `.spawn()` / `.map()`, `Image.*` (varargs or list),
|
|
250
|
+
`Volume.from_name`, `Secret.from_dict` / `from_dotenv` / `from_local_environ`,
|
|
251
|
+
`gpu="H100"`, `"A100-80GB:8"`, etc. `cpu=`, `memory=`, `retries=` and similar
|
|
252
|
+
options are accepted and ignored with a warning.
|
|
253
|
+
|
|
254
|
+
Not supported: web endpoints, `Dict` / `Queue`, schedules, sandboxes,
|
|
255
|
+
`Secret.from_name` (no hosted secret store), `modal deploy`. `.map()` runs
|
|
256
|
+
inputs sequentially on one session; use `fcloud map` for real fan-out.
|
|
257
|
+
|
|
180
258
|
## Configuration
|
|
181
259
|
|
|
182
260
|
API key, in order of precedence:
|
|
@@ -5,29 +5,61 @@ hosts, run commands and scripts on them, keep a persistent workspace between
|
|
|
5
5
|
runs, move files in and out, and drive long-running or batch jobs — from a
|
|
6
6
|
terminal, from Python, or from an AI coding agent.
|
|
7
7
|
|
|
8
|
-
Requires Python 3.
|
|
8
|
+
Requires Python 3.9+ (see [Python versions](#python-versions)).
|
|
9
9
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
12
|
+
```bash
|
|
13
|
+
curl -fsSL https://fcloud-home.vercel.app/install.sh | sh
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
That installs the CLI from PyPI (via `uv` or `pipx`), signs you in through
|
|
17
|
+
the browser, and installs the fcloud skill for coding agents on your machine.
|
|
18
|
+
With your own Python:
|
|
19
|
+
|
|
12
20
|
```bash
|
|
13
21
|
pip install fcloud-sdk
|
|
22
|
+
fcloud login
|
|
14
23
|
```
|
|
15
24
|
|
|
25
|
+
fcloud is prepaid — `fcloud credits buy 25` before your first run, and
|
|
26
|
+
`fcloud credits` to see the balance.
|
|
27
|
+
|
|
28
|
+
### Python versions
|
|
29
|
+
|
|
30
|
+
Every row runs the full test suite in CI on every push (the matrix in
|
|
31
|
+
`.github/workflows/unit-tests.yml` is the source of truth; a CI check fails
|
|
32
|
+
if `pyproject.toml`'s classifiers drift from it).
|
|
33
|
+
|
|
34
|
+
| Python | Status |
|
|
35
|
+
|--------|--------|
|
|
36
|
+
| 3.14 | supported |
|
|
37
|
+
| 3.13 | supported |
|
|
38
|
+
| 3.12 | supported |
|
|
39
|
+
| 3.11 | supported |
|
|
40
|
+
| 3.10 | supported |
|
|
41
|
+
| 3.9 | supported (macOS command-line-tools Python, Debian 11) |
|
|
42
|
+
| 3.8 and older | not supported; the installer script bootstraps `uv`, which fetches a supported Python |
|
|
43
|
+
|
|
44
|
+
The installer only uses your system Python (`pipx`/`pip`) when it is 3.9 or
|
|
45
|
+
newer; otherwise it installs `uv`, which brings its own.
|
|
46
|
+
|
|
16
47
|
## Setup
|
|
17
48
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
walks you through sign-up and saves the key you paste.
|
|
49
|
+
`fcloud login` opens https://fcloud-home.vercel.app to sign in (Google or
|
|
50
|
+
email) and add a card, then saves a key minted for this machine. Running any
|
|
51
|
+
`fcloud` command on a fresh install starts the same login.
|
|
22
52
|
|
|
23
53
|
```bash
|
|
24
|
-
fcloud
|
|
54
|
+
fcloud login [--no-browser] [--agents auto|all|cursor|claude|codex|none]
|
|
55
|
+
fcloud set_token <key> # already have a key (CI, agents, a second machine)
|
|
56
|
+
fcloud setup # save a key you already have; install agent skills
|
|
25
57
|
fcloud health # verify connectivity
|
|
26
58
|
```
|
|
27
59
|
|
|
28
|
-
`fcloud
|
|
29
|
-
|
|
30
|
-
|
|
60
|
+
`fcloud login` installs the fcloud skill file for the coding agents it finds
|
|
61
|
+
(Cursor, Claude Code, Codex — `--agents all` for every one) so an agent can
|
|
62
|
+
drive fcloud for you. `fcloud setup --agents none` skips that.
|
|
31
63
|
|
|
32
64
|
## Quick start
|
|
33
65
|
|
|
@@ -107,6 +139,7 @@ Batch
|
|
|
107
139
|
fcloud sweeps / fcloud sweep <status|logs|retry|cancel|wait> <name>
|
|
108
140
|
|
|
109
141
|
Setup
|
|
142
|
+
fcloud login [--no-browser] [--agents auto|all|cursor|claude|codex|none]
|
|
110
143
|
fcloud setup [--token KEY] [--agents all|cursor|claude|codex|none]
|
|
111
144
|
fcloud set_token <api-key>
|
|
112
145
|
fcloud --version
|
|
@@ -143,6 +176,49 @@ with project.session(sku="gpu_1x_l4") as s:
|
|
|
143
176
|
|
|
144
177
|
Errors raise `fcloud.FcloudError` (or a subclass such as `PaymentOverdueError`).
|
|
145
178
|
|
|
179
|
+
## Porting from Modal
|
|
180
|
+
|
|
181
|
+
`fcloud` exposes a Modal-compatible surface, so most Modal scripts port with an
|
|
182
|
+
import rename:
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
import fcloud as modal # was: import modal
|
|
186
|
+
|
|
187
|
+
app = modal.App("demo")
|
|
188
|
+
image = modal.Image.debian_slim().pip_install("torch", "numpy")
|
|
189
|
+
|
|
190
|
+
@app.function(image=image, gpu="H100", timeout=600)
|
|
191
|
+
def train(steps: int) -> float:
|
|
192
|
+
...
|
|
193
|
+
|
|
194
|
+
@app.cls(gpu="L4", volumes={"/data": modal.Volume.from_name("weights", create_if_missing=True)})
|
|
195
|
+
class Model:
|
|
196
|
+
@modal.enter()
|
|
197
|
+
def load(self): ...
|
|
198
|
+
@modal.method()
|
|
199
|
+
def predict(self, x): ...
|
|
200
|
+
|
|
201
|
+
@app.local_entrypoint()
|
|
202
|
+
def main(steps: int = 100):
|
|
203
|
+
print(train.remote(steps))
|
|
204
|
+
print(Model().predict.remote(1))
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Run it with `fcloud run demo.py [--steps 500]` (the Modal-style `demo.py::name`
|
|
208
|
+
picks an entrypoint or function). Each `(gpu, image, volumes)` combination gets
|
|
209
|
+
one warm session; `.remote()` pickles the args, runs the function on the host
|
|
210
|
+
and returns the pickled result, streaming stdout back live.
|
|
211
|
+
|
|
212
|
+
Supported: `App`, `@app.function` / `@app.cls` / `@app.local_entrypoint`,
|
|
213
|
+
`.remote()` / `.spawn()` / `.map()`, `Image.*` (varargs or list),
|
|
214
|
+
`Volume.from_name`, `Secret.from_dict` / `from_dotenv` / `from_local_environ`,
|
|
215
|
+
`gpu="H100"`, `"A100-80GB:8"`, etc. `cpu=`, `memory=`, `retries=` and similar
|
|
216
|
+
options are accepted and ignored with a warning.
|
|
217
|
+
|
|
218
|
+
Not supported: web endpoints, `Dict` / `Queue`, schedules, sandboxes,
|
|
219
|
+
`Secret.from_name` (no hosted secret store), `modal deploy`. `.map()` runs
|
|
220
|
+
inputs sequentially on one session; use `fcloud map` for real fan-out.
|
|
221
|
+
|
|
146
222
|
## Configuration
|
|
147
223
|
|
|
148
224
|
API key, in order of precedence:
|
|
@@ -25,6 +25,9 @@ fcloud set_token <api_key> # one-time; saves to ~/.fcloud/token
|
|
|
25
25
|
fcloud health # verify connectivity
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
(A human sets up with `fcloud login`, which signs in through the browser and
|
|
29
|
+
mints a key for the machine; agents use `set_token` with a key they were given.)
|
|
30
|
+
|
|
28
31
|
Token resolution order:
|
|
29
32
|
|
|
30
33
|
1. `api_key=` parameter to `Client()`
|
|
@@ -85,6 +88,8 @@ ignored.
|
|
|
85
88
|
| ---------------------- | ----------------------------------------- |
|
|
86
89
|
| `fcloud health` | Check API connectivity |
|
|
87
90
|
| `fcloud set_token <key>` | Save API key |
|
|
91
|
+
| `fcloud credits` | Prepaid balance, auto-refill, card |
|
|
92
|
+
| `fcloud credits buy <usd>` | Buy credit (opens hosted checkout) |
|
|
88
93
|
|
|
89
94
|
|
|
90
95
|
### Execution
|
|
@@ -303,7 +308,8 @@ filesystem later; a **job** for batch/CI-style runs with declared outputs.
|
|
|
303
308
|
| Command | Description |
|
|
304
309
|
| ----------------------------- | ---------------------------------------------------- |
|
|
305
310
|
| `fcloud create [--sku SKU]` | Create a session (cold — $0, no host, until first used) |
|
|
306
|
-
| `fcloud sessions [--all]`
|
|
311
|
+
| `fcloud sessions [--all] [--limit N]` | List sessions with `$/HR` (billed rate), `SPENT` (compute so far) and `AGE` (time on its host) per row and a spending-now total (`--all --json` can be large; bound it with `--limit`) |
|
|
312
|
+
| `fcloud usage [--since 24h]` | What the account has been charged over a window (default 30 days), by resource and session |
|
|
307
313
|
| `fcloud stop <session-id> [--wait]` | Stop a session now: halt GPU spend (files kept). `--wait` blocks until the workspace manifest is durable |
|
|
308
314
|
|
|
309
315
|
|
|
@@ -983,6 +989,16 @@ fcloud wait s-abc123 "$PID" --timeout 0
|
|
|
983
989
|
> alive (`fcloud logs <sid>` lists it as `running`) rather than assuming `rc=75`
|
|
984
990
|
> means your job is gone.
|
|
985
991
|
>
|
|
992
|
+
> **Out of credit (`rc=4`).** A 402 with `code: insufficient_credit` means the
|
|
993
|
+
> prepaid balance is exhausted: sessions are closed and queued jobs canceled.
|
|
994
|
+
> **This is not retryable** — retrying returns the same refusal until credit
|
|
995
|
+
> lands. Distinguish it from `rc=3` (`payment_overdue`, a failed payment on a
|
|
996
|
+
> bill) and from `rc=75` (transient unreachability, which *is* retryable). The
|
|
997
|
+
> remedy is `fcloud credits buy 25` or `fcloud credits autorefill on`; the block
|
|
998
|
+
> clears about a minute after the credit posts. In the SDK it raises
|
|
999
|
+
> `fcloud.InsufficientCreditError`, a sibling of `PaymentOverdueError` — code
|
|
1000
|
+
> that catches only `PaymentOverdueError` will not catch it.
|
|
1001
|
+
>
|
|
986
1002
|
> **Migration rides honor `--wait`.** When a foreground `exec` rides a
|
|
987
1003
|
> checkpoint/restore, the ride is bounded by your `--wait` budget — it never
|
|
988
1004
|
> blocks past it (`--wait 0` disables the bound; the ride then uses the full
|
|
@@ -1076,7 +1092,10 @@ fcloud exec --on s-abc123 /workspace/venv/bin/python train.py
|
|
|
1076
1092
|
```
|
|
1077
1093
|
|
|
1078
1094
|
Better yet, put packages in the image spec (`pip_install([...])`) so every
|
|
1079
|
-
resume rebuilds them from the content-addressed image cache.
|
|
1095
|
+
resume rebuilds them from the content-addressed image cache. A sweep whose
|
|
1096
|
+
canary pip-installs packages gets them baked into its image automatically
|
|
1097
|
+
before the fan-out (the pass line names them; `--no-bake` disables) — but
|
|
1098
|
+
the pins still belong in `fcloud.json` so the *next* sweep never pays them.
|
|
1080
1099
|
|
|
1081
1100
|
---
|
|
1082
1101
|
|
|
@@ -1215,6 +1234,8 @@ host (files restored from cloud storage) the next time you use it. You never run
|
|
|
1215
1234
|
**Warmth**: `fcloud sessions` reports a session's state — `hot` (running, spending),
|
|
1216
1235
|
`warm` (idle host held, spending), `stopping` (finishing teardown), `cold`
|
|
1217
1236
|
(stopped, $0, resumes on use), or `preparing` (coming online).
|
|
1237
|
+
The `$/HR` column is the rate billed on the session's open usage interval while a
|
|
1238
|
+
host is held (0 when cold); `SPENT` is compute charged so far; the footer sums `$/HR`. `fcloud usage` reports what was actually charged.
|
|
1218
1239
|
|
|
1219
1240
|
**Image**: Declarative container spec. Layers are cached by content hash. First run
|
|
1220
1241
|
on a host triggers a build (30–120s); subsequent runs with the same image are instant.
|
|
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "fcloud-sdk"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Python SDK and CLI for the fcloud GPU compute platform"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
11
11
|
license-files = ["LICENSE", "NOTICE"]
|
|
12
12
|
authors = [{ name = "fcloud" }]
|
|
13
|
-
requires-python = ">=3.
|
|
13
|
+
requires-python = ">=3.9"
|
|
14
14
|
keywords = ["gpu", "cloud", "compute", "ml", "training", "cli"]
|
|
15
15
|
classifiers = [
|
|
16
16
|
"Development Status :: 3 - Alpha",
|
|
@@ -19,10 +19,12 @@ classifiers = [
|
|
|
19
19
|
"Intended Audience :: Science/Research",
|
|
20
20
|
"Operating System :: OS Independent",
|
|
21
21
|
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.9",
|
|
22
23
|
"Programming Language :: Python :: 3.10",
|
|
23
24
|
"Programming Language :: Python :: 3.11",
|
|
24
25
|
"Programming Language :: Python :: 3.12",
|
|
25
26
|
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Programming Language :: Python :: 3.14",
|
|
26
28
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
27
29
|
"Topic :: System :: Distributed Computing",
|
|
28
30
|
"Typing :: Typed",
|
|
@@ -61,13 +63,14 @@ testpaths = ["tests"]
|
|
|
61
63
|
|
|
62
64
|
[tool.ruff]
|
|
63
65
|
line-length = 100
|
|
64
|
-
target-version = "
|
|
66
|
+
target-version = "py39"
|
|
65
67
|
|
|
66
68
|
[tool.ruff.lint]
|
|
67
|
-
select = ["E", "F", "W", "I", "UP", "B"]
|
|
69
|
+
select = ["E", "F", "W", "I", "UP", "B", "FA"]
|
|
68
70
|
ignore = ["E741"]
|
|
69
71
|
|
|
70
72
|
[tool.mypy]
|
|
73
|
+
# mypy >= 1.19 cannot target 3.9; ruff (py39) and the CI 3.9 job cover the floor.
|
|
71
74
|
python_version = "3.10"
|
|
72
75
|
files = ["src/fcloud"]
|
|
73
76
|
ignore_missing_imports = true
|
|
@@ -25,6 +25,9 @@ fcloud set_token <api_key> # one-time; saves to ~/.fcloud/token
|
|
|
25
25
|
fcloud health # verify connectivity
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
(A human sets up with `fcloud login`, which signs in through the browser and
|
|
29
|
+
mints a key for the machine; agents use `set_token` with a key they were given.)
|
|
30
|
+
|
|
28
31
|
Token resolution order:
|
|
29
32
|
|
|
30
33
|
1. `api_key=` parameter to `Client()`
|
|
@@ -85,6 +88,8 @@ ignored.
|
|
|
85
88
|
| ---------------------- | ----------------------------------------- |
|
|
86
89
|
| `fcloud health` | Check API connectivity |
|
|
87
90
|
| `fcloud set_token <key>` | Save API key |
|
|
91
|
+
| `fcloud credits` | Prepaid balance, auto-refill, card |
|
|
92
|
+
| `fcloud credits buy <usd>` | Buy credit (opens hosted checkout) |
|
|
88
93
|
|
|
89
94
|
|
|
90
95
|
### Execution
|
|
@@ -303,7 +308,8 @@ filesystem later; a **job** for batch/CI-style runs with declared outputs.
|
|
|
303
308
|
| Command | Description |
|
|
304
309
|
| ----------------------------- | ---------------------------------------------------- |
|
|
305
310
|
| `fcloud create [--sku SKU]` | Create a session (cold — $0, no host, until first used) |
|
|
306
|
-
| `fcloud sessions [--all]`
|
|
311
|
+
| `fcloud sessions [--all] [--limit N]` | List sessions with `$/HR` (billed rate), `SPENT` (compute so far) and `AGE` (time on its host) per row and a spending-now total (`--all --json` can be large; bound it with `--limit`) |
|
|
312
|
+
| `fcloud usage [--since 24h]` | What the account has been charged over a window (default 30 days), by resource and session |
|
|
307
313
|
| `fcloud stop <session-id> [--wait]` | Stop a session now: halt GPU spend (files kept). `--wait` blocks until the workspace manifest is durable |
|
|
308
314
|
|
|
309
315
|
|
|
@@ -983,6 +989,16 @@ fcloud wait s-abc123 "$PID" --timeout 0
|
|
|
983
989
|
> alive (`fcloud logs <sid>` lists it as `running`) rather than assuming `rc=75`
|
|
984
990
|
> means your job is gone.
|
|
985
991
|
>
|
|
992
|
+
> **Out of credit (`rc=4`).** A 402 with `code: insufficient_credit` means the
|
|
993
|
+
> prepaid balance is exhausted: sessions are closed and queued jobs canceled.
|
|
994
|
+
> **This is not retryable** — retrying returns the same refusal until credit
|
|
995
|
+
> lands. Distinguish it from `rc=3` (`payment_overdue`, a failed payment on a
|
|
996
|
+
> bill) and from `rc=75` (transient unreachability, which *is* retryable). The
|
|
997
|
+
> remedy is `fcloud credits buy 25` or `fcloud credits autorefill on`; the block
|
|
998
|
+
> clears about a minute after the credit posts. In the SDK it raises
|
|
999
|
+
> `fcloud.InsufficientCreditError`, a sibling of `PaymentOverdueError` — code
|
|
1000
|
+
> that catches only `PaymentOverdueError` will not catch it.
|
|
1001
|
+
>
|
|
986
1002
|
> **Migration rides honor `--wait`.** When a foreground `exec` rides a
|
|
987
1003
|
> checkpoint/restore, the ride is bounded by your `--wait` budget — it never
|
|
988
1004
|
> blocks past it (`--wait 0` disables the bound; the ride then uses the full
|
|
@@ -1076,7 +1092,10 @@ fcloud exec --on s-abc123 /workspace/venv/bin/python train.py
|
|
|
1076
1092
|
```
|
|
1077
1093
|
|
|
1078
1094
|
Better yet, put packages in the image spec (`pip_install([...])`) so every
|
|
1079
|
-
resume rebuilds them from the content-addressed image cache.
|
|
1095
|
+
resume rebuilds them from the content-addressed image cache. A sweep whose
|
|
1096
|
+
canary pip-installs packages gets them baked into its image automatically
|
|
1097
|
+
before the fan-out (the pass line names them; `--no-bake` disables) — but
|
|
1098
|
+
the pins still belong in `fcloud.json` so the *next* sweep never pays them.
|
|
1080
1099
|
|
|
1081
1100
|
---
|
|
1082
1101
|
|
|
@@ -1215,6 +1234,8 @@ host (files restored from cloud storage) the next time you use it. You never run
|
|
|
1215
1234
|
**Warmth**: `fcloud sessions` reports a session's state — `hot` (running, spending),
|
|
1216
1235
|
`warm` (idle host held, spending), `stopping` (finishing teardown), `cold`
|
|
1217
1236
|
(stopped, $0, resumes on use), or `preparing` (coming online).
|
|
1237
|
+
The `$/HR` column is the rate billed on the session's open usage interval while a
|
|
1238
|
+
host is held (0 when cold); `SPENT` is compute charged so far; the footer sums `$/HR`. `fcloud usage` reports what was actually charged.
|
|
1218
1239
|
|
|
1219
1240
|
**Image**: Declarative container spec. Layers are cached by content hash. First run
|
|
1220
1241
|
on a host triggers a build (30–120s); subsequent runs with the same image are instant.
|
|
@@ -22,6 +22,7 @@ import fcloud._legacy_env # noqa: F401 — must run before any env read below
|
|
|
22
22
|
from fcloud.client import (
|
|
23
23
|
Client,
|
|
24
24
|
FcloudError,
|
|
25
|
+
InsufficientCreditError,
|
|
25
26
|
NoApiKeyError,
|
|
26
27
|
PaymentOverdueError,
|
|
27
28
|
SessionNotFoundError,
|
|
@@ -37,6 +38,16 @@ from fcloud.errors import (
|
|
|
37
38
|
)
|
|
38
39
|
from fcloud.image import Image
|
|
39
40
|
from fcloud.job import Job # noqa: F401
|
|
41
|
+
from fcloud.modal_compat import ( # Modal-compatible surface: `import fcloud as modal`
|
|
42
|
+
App,
|
|
43
|
+
RemoteError,
|
|
44
|
+
Secret,
|
|
45
|
+
Volume,
|
|
46
|
+
enter,
|
|
47
|
+
exit, # noqa: A004 — mirrors modal.exit
|
|
48
|
+
method,
|
|
49
|
+
parameter,
|
|
50
|
+
)
|
|
40
51
|
from fcloud.session import Project, Session
|
|
41
52
|
from fcloud.sweeps import MapSpecError, SweepHandle # noqa: F401
|
|
42
53
|
from fcloud.types import (
|
|
@@ -61,6 +72,15 @@ __all__ = [
|
|
|
61
72
|
"Image",
|
|
62
73
|
"set_token",
|
|
63
74
|
"get_token",
|
|
75
|
+
# Modal-compatible surface
|
|
76
|
+
"App",
|
|
77
|
+
"Secret",
|
|
78
|
+
"Volume",
|
|
79
|
+
"enter",
|
|
80
|
+
"exit",
|
|
81
|
+
"method",
|
|
82
|
+
"parameter",
|
|
83
|
+
"RemoteError",
|
|
64
84
|
# Handles
|
|
65
85
|
"Project",
|
|
66
86
|
"Session",
|
|
@@ -76,6 +96,7 @@ __all__ = [
|
|
|
76
96
|
"ClientTooOldError",
|
|
77
97
|
"FcloudError",
|
|
78
98
|
"FcloudRuntimeError",
|
|
99
|
+
"InsufficientCreditError",
|
|
79
100
|
"MapSpecError",
|
|
80
101
|
"NoApiKeyError",
|
|
81
102
|
"PaymentOverdueError",
|
|
@@ -50,6 +50,7 @@ from fcloud.cli.context import ( # noqa: F401
|
|
|
50
50
|
_resolve_project,
|
|
51
51
|
_resolve_volume_mounts,
|
|
52
52
|
)
|
|
53
|
+
from fcloud.cli.credits import cmd_credits # noqa: F401
|
|
53
54
|
from fcloud.cli.exec_cmd import _parse_exec_args, cmd_exec # noqa: F401
|
|
54
55
|
from fcloud.cli.files import ( # noqa: F401
|
|
55
56
|
_download_file,
|
|
@@ -119,6 +120,7 @@ from fcloud.cli.sweep import ( # noqa: F401
|
|
|
119
120
|
cmd_sweep,
|
|
120
121
|
cmd_sweeps,
|
|
121
122
|
)
|
|
123
|
+
from fcloud.cli.usage import cmd_usage # noqa: F401
|
|
122
124
|
from fcloud.cli.volume import ( # noqa: F401
|
|
123
125
|
_is_within,
|
|
124
126
|
_volume_cat,
|