A command line utility for interacting with runloop APIs.
- rl-cli
- Table of Contents
- Setup
- Quick reference
- Command Reference
uv tool install rl-cli
rl devbox create --env_vars HELLO=world --entrypoint 'echo $HELLO'
>
create devbox={
"id": "dbx_2xMDUOsKMiZBYKsvSRtMA",
"blueprint_id": null,
"create_time_ms": 1723229557715,
"end_time_ms": null,
"initiator_id": null,
"initiator_type": "invocation",
"name": null,
"status": "provisioning"
}
rl devbox logs --id dbx_2xMDUOsKMiZBYKsvSRtMA
>
2024-08-09 12:15:01.701 Initializing devbox...
2024-08-09 12:15:01.734 Devbox setup complete
2024-08-09 12:15:01.769 [entrypoint] -> echo $HELLO
2024-08-09 12:15:01.798 [entrypoint] world
2024-08-09 12:15:01.798 world
2024-08-09 12:15:01.800 [entrypoint] -> exit_code=0
rl devbox get --id dbx_2ws7IOtjxnJgLsBIpU9nn
>
# Note that the devbox status="shutdown" after the entrypoint completes.
devbox={
"id": "dbx_2xMDUOsKMiZBYKsvSRtMA",
"blueprint_id": null,
"create_time_ms": 1723229557715,
"end_time_ms": 1723229561620,
"initiator_id": null,
"initiator_type": "invocation",
"name": null,
"status": "shutdown"
}
To use the SCP command:
rl devbox scp local_file.txt :remote_file.txt --id <devbox_id>
To copy a file from the devbox to your local machine:
rl devbox scp :remote_file.txt local_file.txt --id <devbox_id>
To use the rsync command:
rl devbox rsync local_file.txt :remote_file.txt --id <devbox_id>
To copy a file from the devbox to your local machine:
rl devbox rsync :remote_file.txt local_file.txt --id <devbox_id>
Note that the rsync implementation will recurse by default and copy directory contents.
To use the rsync command:
rl devbox rsync local_dir :remote_dir --id <devbox_id>
To use the tunnel command:
rl devbox tunnel --id <devbox_id> <local_port>:<remote_port>
Note that this is a blocking command that will block for duration of tunnel.
rl blueprint create --name=<blueprint_name> --system_setup_commands "<setup commands>"
rl devbox snapshot create --devbox_id=<devbox_id>
rl devbox snapshot status --snapshot_id=<snapshot_id>
rl devbox create [options]
Options:
--launch_commands Devbox initialization commands (can be specified multiple times)
--entrypoint Devbox entrypoint command
--blueprint_id ID of the blueprint to use
--blueprint_name Name of the blueprint to use
--snapshot_id ID of the snapshot to use
--env_vars Environment variables in key=value format (can be specified multiple times)
--code_mounts Code mount configuration in JSON format
--idle_time Time in seconds after which idle action will be triggered
--idle_action Action to take when devbox becomes idle (shutdown/suspend)
--prebuilt Use a non-standard prebuilt image
--resources Devbox resource specification (SMALL/MEDIUM/LARGE/X_LARGE/XX_LARGE)
--architecture Architecture (arm64/x86_64)
--root Run as root
--user Run as this user (USER:UID)
rl devbox list [options]
Options:
--status Filter by devbox status (initializing/running/suspending/suspended/resuming/failure/shutdown)
rl devbox get --id <devbox_id>
rl devbox exec --id <devbox_id> --command "<command>"
# Start async execution
rl devbox exec_async --id <devbox_id> --command "<command>"
# Get execution status
rl devbox get_async --id <devbox_id> --execution_id <execution_id>
# SSH into devbox
rl devbox ssh --id <devbox_id>
# Print SSH config only
rl devbox ssh --id <devbox_id> --config-only
# Copy to devbox
rl devbox scp local_file.txt :remote_file.txt --id <devbox_id>
# Copy from devbox
rl devbox scp :remote_file.txt local_file.txt --id <devbox_id>
# Additional options
rl devbox scp --scp-options="-r" local_dir :remote_dir --id <devbox_id>
# Copy to devbox
rl devbox rsync local_dir :remote_dir --id <devbox_id>
# Copy from devbox
rl devbox rsync :remote_dir local_dir --id <devbox_id>
# Additional options
rl devbox rsync --rsync-options="-avz" local_dir :remote_dir --id <devbox_id>
# Read a file from devbox to local file
rl devbox read --id <devbox_id> --remote /path/to/remote/file --output /path/to/local/file
# Write a local file to devbox
rl devbox write --id <devbox_id> --input /path/to/local/file --remote /path/to/remote/file
# Upload a file to devbox
rl devbox upload_file --id <devbox_id> --file /path/to/local/file --path /path/to/remote/file
# Download a file from devbox
rl devbox download_file --id <devbox_id> --file_path /path/to/remote/file --output_path /path/to/local/file
rl devbox tunnel --id <devbox_id> <local_port>:<remote_port>
rl devbox suspend --id <devbox_id>
rl devbox resume --id <devbox_id>
rl devbox shutdown --id <devbox_id>
rl devbox logs --id <devbox_id>
rl blueprint create [options]
Options:
--name Blueprint name (required)
--system_setup_commands System initialization commands (can be specified multiple times)
--dockerfile Dockerfile contents as text
--dockerfile_path Path to Dockerfile
--resources Resource specification (SMALL/MEDIUM/LARGE/X_LARGE/XX_LARGE)
--available_ports List of available ports (can be specified multiple times)
--architecture Architecture (arm64/x86_64)
--root Run as root
--user Run as this user (USER:UID)
rl blueprint preview [options]
Options:
--name Blueprint name (required)
--dockerfile Dockerfile contents as text
--system_setup_commands System initialization commands (can be specified multiple times)
rl blueprint list [options]
Options:
--name Filter by blueprint name
rl blueprint get --id <blueprint_id>
rl blueprint logs --id <blueprint_id>
rl devbox snapshot create --devbox_id <devbox_id>
rl devbox snapshot status --snapshot_id <snapshot_id>
rl devbox snapshot list
Upload a local file as an Object. The CLI auto-detects the create API content type based on the filename.
# Auto-detect content type from file name
rl object upload --path ./data.txt --name data.txt
# Explicitly set content type (overrides detection)
rl object upload --path ./archive.tar.gz --name archive.tgz --content_type tgz
Notes:
- Allowed values for
--content_type
are:unspecified
,text
,gzip
,tar
,tgz
. - If the file name does not match a known pattern, the content type is set to
unspecified
. - Auto-detection rules (by extension):
*.txt
,*.json
,*.md
,*.yaml
,*.yml
,*.csv
, etc. →text
*.gz
→gzip
*.tar
→tar
*.tar.gz
,*.tgz
→tgz
- Everything else (e.g.,
*.zip
,*.zst
, images, unknown) →unspecified
Download an object to your local filesystem:
# Simple download
rl object download --id obj_123 --path ./myfile.zip
# Download and extract archive
rl object download --id obj_123 --path ./myfile.zip --extract
# Supported archive formats:
# - .zip: Standard ZIP archives
# - .tar.gz, .tgz: Gzipped tar archives
# - .zst: Zstandard compressed files
# - .tar.zst: Zstandard compressed tar archives
The --extract
flag will automatically extract supported archive formats after download. The extraction directory will be created using the archive name without the extension.
rl object list --limit 20
# Filter examples
rl object list --name sample
rl object list --content_type text
rl object list --state READ_ONLY
rl object get --id obj_123
rl object delete --id obj_123
The object create API supports the following content types:
unspecified
text
gzip
tar
tgz
The CLI maps file extensions to these values during upload. If a file doesn't match any rule, it is marked as unspecified
.
# Clone the repo
mkdir -p ~/source/ && cd ~/source/
git clone https://github.com/runloopai/rl-cli.git
cd rl-cli/
pip install -e ".[dev]"
The project uses pytest for testing. The test suite includes unit tests and an end-to-end integration test for object upload/download.
# Install dev dependencies (choose one)
uv pip install -e ".[dev]"
# or
pip install -e ".[dev]"
# Run all tests except the integration tests
pytest -q -k "not integration"
# Run only unit tests in verbose mode (equivalent to excluding integration)
pytest -v -k "not integration"
# Run only the integration tests (requires an API key)
RUNLOOP_API_KEY=<your-api-key> RUNLOOP_ENV=prod pytest -q tests/integration/test_object_e2e.py
# Run a specific unit test file
pytest -v tests/test_cli.py
# Run a specific test function
pytest -v tests/test_cli.py::test_devbox_list
# Run tests with coverage
pytest -v --cov=rl_cli
# Run tests in parallel (faster)
pytest -v -n auto
Notes:
- The integration test in
tests/integration/test_object_e2e.py
exercises live upload/download. It requiresRUNLOOP_API_KEY
in the environment. SetRUNLOOP_ENV
toprod
(ordev
if your key targets dev). - To run the full suite including integration, export your key once, then run pytest:
export RUNLOOP_API_KEY=<your-api-key>
export RUNLOOP_ENV=prod
pytest -q
CI:
- A GitHub Actions workflow runs the integration test using a secret API key. Ensure the repository secret is configured (see
.github/workflows/cli-integration.yml
).