modal-uv 0.2.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.
- modal_uv-0.2.0/.github/workflows/release.yml +167 -0
- modal_uv-0.2.0/.gitignore +12 -0
- modal_uv-0.2.0/.python-version +1 -0
- modal_uv-0.2.0/AGENTS.md +23 -0
- modal_uv-0.2.0/PKG-INFO +232 -0
- modal_uv-0.2.0/README.md +218 -0
- modal_uv-0.2.0/docs/superpowers/plans/2026-07-06-modal-uv-global-cli.md +550 -0
- modal_uv-0.2.0/docs/superpowers/plans/2026-07-09-modal-uv-exec.md +358 -0
- modal_uv-0.2.0/docs/superpowers/specs/2026-06-06-modal-uv-connection-daemon-design.md +168 -0
- modal_uv-0.2.0/docs/superpowers/specs/2026-06-06-modal-uv-direct-sync-design.md +196 -0
- modal_uv-0.2.0/docs/superpowers/specs/2026-06-06-modal-uv-preload-imports-design.md +74 -0
- modal_uv-0.2.0/docs/superpowers/specs/2026-06-06-modal-uv-python-in-process-design.md +120 -0
- modal_uv-0.2.0/docs/superpowers/specs/2026-07-06-modal-uv-global-cli-design.md +188 -0
- modal_uv-0.2.0/docs/superpowers/specs/2026-07-09-modal-uv-exec-design.md +138 -0
- modal_uv-0.2.0/example-repo/.gitignore +6 -0
- modal_uv-0.2.0/example-repo/modal-uv.yaml +20 -0
- modal_uv-0.2.0/example-repo/pyproject.toml +21 -0
- modal_uv-0.2.0/example-repo/src/example_pkg/__init__.py +1 -0
- modal_uv-0.2.0/example-repo/src/example_pkg/main.py +9 -0
- modal_uv-0.2.0/example-repo/tests/test_main.py +9 -0
- modal_uv-0.2.0/example-repo/uv.lock +79 -0
- modal_uv-0.2.0/pyproject.toml +59 -0
- modal_uv-0.2.0/skills/use-modal-uv/SKILL.md +183 -0
- modal_uv-0.2.0/src/modal_uv/__init__.py +5 -0
- modal_uv-0.2.0/src/modal_uv/__main__.py +7 -0
- modal_uv-0.2.0/src/modal_uv/app.py +147 -0
- modal_uv-0.2.0/src/modal_uv/cli.py +793 -0
- modal_uv-0.2.0/src/modal_uv/client.py +113 -0
- modal_uv-0.2.0/src/modal_uv/config.py +271 -0
- modal_uv-0.2.0/src/modal_uv/daemon.py +170 -0
- modal_uv-0.2.0/src/modal_uv/deployment.py +216 -0
- modal_uv-0.2.0/src/modal_uv/paths.py +47 -0
- modal_uv-0.2.0/src/modal_uv/skill.py +83 -0
- modal_uv-0.2.0/src/modal_uv/sync.py +216 -0
- modal_uv-0.2.0/tests/test_modal_uv_app.py +294 -0
- modal_uv-0.2.0/tests/test_modal_uv_cli.py +1161 -0
- modal_uv-0.2.0/tests/test_modal_uv_config.py +368 -0
- modal_uv-0.2.0/tests/test_modal_uv_daemon.py +242 -0
- modal_uv-0.2.0/tests/test_modal_uv_deployment.py +374 -0
- modal_uv-0.2.0/tests/test_modal_uv_skill.py +113 -0
- modal_uv-0.2.0/tests/test_modal_uv_sync.py +150 -0
- modal_uv-0.2.0/uv.lock +1350 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
checks:
|
|
14
|
+
name: Checks
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- name: Checkout
|
|
19
|
+
uses: actions/checkout@v6
|
|
20
|
+
|
|
21
|
+
- name: Install uv
|
|
22
|
+
id: setup-uv
|
|
23
|
+
uses: astral-sh/setup-uv@v8.1.0
|
|
24
|
+
with:
|
|
25
|
+
python-version: "3.12"
|
|
26
|
+
enable-cache: true
|
|
27
|
+
# Keep one stable uv cache per OS/Python so first PR runs can restore
|
|
28
|
+
# the cache populated by main. uv's cache is content-addressed; stale
|
|
29
|
+
# entries are safe, and missing artifacts are fetched during sync.
|
|
30
|
+
cache-dependency-glob: ""
|
|
31
|
+
cache-suffix: modal-uv-ci
|
|
32
|
+
restore-cache: true
|
|
33
|
+
save-cache: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
|
34
|
+
|
|
35
|
+
- name: Show uv cache status
|
|
36
|
+
run: |
|
|
37
|
+
echo "uv cache key: ${{ steps.setup-uv.outputs.cache-key }}"
|
|
38
|
+
echo "uv cache hit: ${{ steps.setup-uv.outputs.cache-hit }}"
|
|
39
|
+
|
|
40
|
+
- name: Install dependencies
|
|
41
|
+
run: uv sync --frozen --dev
|
|
42
|
+
|
|
43
|
+
- name: Check formatting
|
|
44
|
+
run: uv run ruff format --check .
|
|
45
|
+
|
|
46
|
+
- name: Type check
|
|
47
|
+
run: uv run ty check
|
|
48
|
+
|
|
49
|
+
- name: Lint
|
|
50
|
+
run: uv run ruff check .
|
|
51
|
+
|
|
52
|
+
- name: Build package
|
|
53
|
+
run: uv build
|
|
54
|
+
|
|
55
|
+
- name: Upload distributions
|
|
56
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
57
|
+
uses: actions/upload-artifact@v4
|
|
58
|
+
with:
|
|
59
|
+
name: python-distributions
|
|
60
|
+
path: dist/
|
|
61
|
+
if-no-files-found: error
|
|
62
|
+
|
|
63
|
+
release-gate:
|
|
64
|
+
name: Release gate
|
|
65
|
+
runs-on: ubuntu-latest
|
|
66
|
+
needs: checks
|
|
67
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
68
|
+
permissions:
|
|
69
|
+
contents: read
|
|
70
|
+
|
|
71
|
+
outputs:
|
|
72
|
+
should_publish: ${{ steps.release.outputs.should_publish }}
|
|
73
|
+
version: ${{ steps.release.outputs.version }}
|
|
74
|
+
tag: ${{ steps.release.outputs.tag }}
|
|
75
|
+
|
|
76
|
+
steps:
|
|
77
|
+
- name: Checkout
|
|
78
|
+
uses: actions/checkout@v6
|
|
79
|
+
with:
|
|
80
|
+
fetch-depth: 0
|
|
81
|
+
|
|
82
|
+
- name: Detect version bump
|
|
83
|
+
id: release
|
|
84
|
+
shell: bash
|
|
85
|
+
run: |
|
|
86
|
+
set -euo pipefail
|
|
87
|
+
|
|
88
|
+
version_at() {
|
|
89
|
+
local rev="$1"
|
|
90
|
+
python - "$rev" <<'PY'
|
|
91
|
+
import subprocess
|
|
92
|
+
import sys
|
|
93
|
+
import tomllib
|
|
94
|
+
|
|
95
|
+
rev = sys.argv[1]
|
|
96
|
+
data = subprocess.check_output(["git", "show", f"{rev}:pyproject.toml"])
|
|
97
|
+
print(tomllib.loads(data.decode())["project"]["version"])
|
|
98
|
+
PY
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
current_version="$(version_at HEAD)"
|
|
102
|
+
if ! git rev-parse --verify --quiet HEAD^ >/dev/null; then
|
|
103
|
+
echo "Initial commit has no parent; skipping publish."
|
|
104
|
+
echo "version=${current_version}" >> "$GITHUB_OUTPUT"
|
|
105
|
+
echo "tag=v${current_version}" >> "$GITHUB_OUTPUT"
|
|
106
|
+
echo "should_publish=false" >> "$GITHUB_OUTPUT"
|
|
107
|
+
exit 0
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
previous_version="$(version_at HEAD^)"
|
|
111
|
+
tag="v${current_version}"
|
|
112
|
+
|
|
113
|
+
echo "version=${current_version}" >> "$GITHUB_OUTPUT"
|
|
114
|
+
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
|
|
115
|
+
|
|
116
|
+
if [[ "${current_version}" == "${previous_version}" ]]; then
|
|
117
|
+
echo "Version unchanged (${current_version}); skipping publish."
|
|
118
|
+
echo "should_publish=false" >> "$GITHUB_OUTPUT"
|
|
119
|
+
exit 0
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
git fetch --tags --quiet
|
|
123
|
+
if git rev-parse --verify --quiet "refs/tags/${tag}" >/dev/null; then
|
|
124
|
+
echo "Tag ${tag} already exists; skipping publish."
|
|
125
|
+
echo "should_publish=false" >> "$GITHUB_OUTPUT"
|
|
126
|
+
exit 0
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
echo "Version changed ${previous_version} -> ${current_version}; publishing ${tag}."
|
|
130
|
+
echo "should_publish=true" >> "$GITHUB_OUTPUT"
|
|
131
|
+
|
|
132
|
+
publish:
|
|
133
|
+
name: Publish to PyPI
|
|
134
|
+
runs-on: ubuntu-latest
|
|
135
|
+
needs: release-gate
|
|
136
|
+
if: needs.release-gate.outputs.should_publish == 'true'
|
|
137
|
+
environment:
|
|
138
|
+
name: pypi
|
|
139
|
+
url: https://pypi.org/p/modal-uv
|
|
140
|
+
permissions:
|
|
141
|
+
contents: write
|
|
142
|
+
id-token: write
|
|
143
|
+
|
|
144
|
+
steps:
|
|
145
|
+
- name: Checkout
|
|
146
|
+
uses: actions/checkout@v6
|
|
147
|
+
with:
|
|
148
|
+
fetch-depth: 0
|
|
149
|
+
|
|
150
|
+
- name: Download distributions
|
|
151
|
+
uses: actions/download-artifact@v5
|
|
152
|
+
with:
|
|
153
|
+
name: python-distributions
|
|
154
|
+
path: dist/
|
|
155
|
+
|
|
156
|
+
- name: Publish package distributions to PyPI
|
|
157
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
158
|
+
|
|
159
|
+
- name: Create and push tag
|
|
160
|
+
env:
|
|
161
|
+
TAG: ${{ needs.release-gate.outputs.tag }}
|
|
162
|
+
run: |
|
|
163
|
+
set -euo pipefail
|
|
164
|
+
git config user.name "github-actions[bot]"
|
|
165
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
166
|
+
git tag -a "${TAG}" -m "Release ${TAG}"
|
|
167
|
+
git push origin "${TAG}"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
modal_uv-0.2.0/AGENTS.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
`modal-uv` exists to let coding agents and developers run ordinary `uv` workflows on Modal without turning every project into a Modal application.
|
|
6
|
+
|
|
7
|
+
The tool is for moments when local development is convenient, but local hardware is not enough: GPU tests, model experiments, long-running jobs, or artifact-producing scripts should be runnable from the same repo, with the same `uv` command shape, and without manual deployment ceremony.
|
|
8
|
+
|
|
9
|
+
## Philosophy
|
|
10
|
+
|
|
11
|
+
- Preserve the local development loop. A project should stay a normal Python project first.
|
|
12
|
+
- Make remote execution feel boring. Running on Modal should be a transport choice, not a rewrite.
|
|
13
|
+
- Prefer explicit project configuration over hidden global state.
|
|
14
|
+
- Keep generated/runtime state out of the source tree's meaningful files.
|
|
15
|
+
- Optimize for coding agents as first-class users: commands should be discoverable, repeatable, and safe to diagnose.
|
|
16
|
+
- Separate fast iteration from deployment. Source changes should sync directly; deployment should happen only when the runtime shape changes.
|
|
17
|
+
- Treat Modal authentication as user-level infrastructure, not repo-local project state.
|
|
18
|
+
|
|
19
|
+
## Problem It Solves
|
|
20
|
+
|
|
21
|
+
Without `modal-uv`, using cloud GPUs from an existing repo usually means adding Modal-specific entrypoints, managing deploy commands manually, copying files around, or teaching each coding agent a custom workflow.
|
|
22
|
+
|
|
23
|
+
`modal-uv` aims to provide one small bridge: keep the repo local, run the command remotely, sync only what changed, persist useful outputs in a Modal Volume, and return enough execution information for agents and humans to inspect logs or stop work.
|
modal_uv-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: modal-uv
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Run uv commands on Modal.com with GPU and direct file sync
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Requires-Dist: fastapi>=0.115.0
|
|
7
|
+
Requires-Dist: httpx>=0.28.0
|
|
8
|
+
Requires-Dist: modal>=1.4.3
|
|
9
|
+
Requires-Dist: pathspec>=0.12.1
|
|
10
|
+
Requires-Dist: pydantic-settings[yaml]>=2.7.0
|
|
11
|
+
Requires-Dist: typer>=0.15.0
|
|
12
|
+
Requires-Dist: uvicorn>=0.34.0
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# modal-uv
|
|
16
|
+
|
|
17
|
+
Run `uv` commands on Modal.com with GPU support, direct file sync, async execution IDs, logs, abort, and persistent Modal volumes.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
### Option 1: Coding Agent (Recommended)
|
|
22
|
+
|
|
23
|
+
Paste this prompt to your coding agent (opencode, Claude Code, Gemini CLI, etc.):
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
Install modal-uv globally and set it up:
|
|
27
|
+
1. Run: pip install modal-uv
|
|
28
|
+
2. Run: modal-uv onboard
|
|
29
|
+
- This opens a browser for Modal OAuth authentication
|
|
30
|
+
- Complete the auth flow in the browser
|
|
31
|
+
- It also installs the use-modal-uv skill to detected coding agents
|
|
32
|
+
3. In the project repo, run: modal-uv init
|
|
33
|
+
- This creates modal-uv.yaml with defaults if missing
|
|
34
|
+
- It creates .modal-uv/ for generated state and adds it to .gitignore
|
|
35
|
+
4. Edit modal-uv.yaml to set app_name, gpu, and volume.name for this project
|
|
36
|
+
5. Run: modal-uv doctor
|
|
37
|
+
- This checks modal-uv health: auth state, volume existence, app deployment, daemon status
|
|
38
|
+
- Does not wake the container
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Option 2: Manual Getting Started
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install modal-uv
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Authenticate with Modal (opens browser for OAuth):
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
modal-uv onboard
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
This also installs the `use-modal-uv` skill to detected coding agents (`~/.config/opencode/`, `~/.claude/`, `~/.agents/`).
|
|
54
|
+
|
|
55
|
+
In your project repo, initialize modal-uv files:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
modal-uv init
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
This creates `modal-uv.yaml` with defaults (using the directory name as `app_name`) if missing, and creates `.modal-uv/` for generated state with a `.gitignore` entry.
|
|
62
|
+
|
|
63
|
+
Edit `modal-uv.yaml` to configure your app:
|
|
64
|
+
|
|
65
|
+
```yaml
|
|
66
|
+
app_name: "my-project"
|
|
67
|
+
gpu: "T4"
|
|
68
|
+
work_dir: "/tmp/work"
|
|
69
|
+
|
|
70
|
+
volumes:
|
|
71
|
+
- name: "modal-uv-cache"
|
|
72
|
+
mount_path: "/mnt/volume"
|
|
73
|
+
commit_interval_seconds: 30
|
|
74
|
+
|
|
75
|
+
env: {}
|
|
76
|
+
|
|
77
|
+
runtime:
|
|
78
|
+
scaledown_window_seconds: 300
|
|
79
|
+
|
|
80
|
+
image:
|
|
81
|
+
python_version: "3.12"
|
|
82
|
+
base_image: "python:3.12-slim"
|
|
83
|
+
|
|
84
|
+
sync:
|
|
85
|
+
ignore:
|
|
86
|
+
- "data/**"
|
|
87
|
+
- "*.ckpt"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Then run commands on Modal:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
modal-uv run -- pytest
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Repository Configuration
|
|
97
|
+
|
|
98
|
+
`modal-uv.yaml` at the repository root is discovered by walking up from the current directory, similar to `git` or `uv`.
|
|
99
|
+
|
|
100
|
+
Fields:
|
|
101
|
+
|
|
102
|
+
- `app_name`: Modal app name (required)
|
|
103
|
+
- `gpu`: GPU type, such as `T4`, `A10G`, `A100`, `H100`, or `L4`
|
|
104
|
+
- `work_dir`: Working directory inside the Modal container (default: `/root/work`)
|
|
105
|
+
- `volumes`: Modal volumes to mount in the container; may be empty or omitted
|
|
106
|
+
- `volumes[].name`: Modal volume name
|
|
107
|
+
- `volumes[].mount_path`: Mount path in the container (default: `/root/.cache`)
|
|
108
|
+
- `volumes[].commit_interval_seconds`: Periodic Modal Volume commit interval while a command runs (default: `30`)
|
|
109
|
+
- `env`: Extra container environment variables merged over modal-uv defaults
|
|
110
|
+
- `runtime.scaledown_window_seconds`: Modal worker scaledown window (default: `300`)
|
|
111
|
+
- `runtime.exec`: Optional shell executable for `modal-uv exec`; if omitted, the remote Worker uses `$SHELL`, then `/bin/sh`
|
|
112
|
+
- `image.python_version`: Python version (default: `3.12`)
|
|
113
|
+
- `image.base_image`: Base Docker image (default: `python:3.12-slim`)
|
|
114
|
+
- `sync.ignore`: gitignore-style patterns excluded from direct sync
|
|
115
|
+
|
|
116
|
+
## Repo-Local State
|
|
117
|
+
|
|
118
|
+
`modal-uv` creates `.modal-uv/` at the repo root for generated/runtime files and ensures the root `.gitignore` ignores it.
|
|
119
|
+
|
|
120
|
+
Examples of generated files:
|
|
121
|
+
|
|
122
|
+
- `.modal-uv/deployment.py`
|
|
123
|
+
- `.modal-uv/daemon.pid`
|
|
124
|
+
- `.modal-uv/daemon.sock`
|
|
125
|
+
- `.modal-uv/daemon.log`
|
|
126
|
+
|
|
127
|
+
`.modal-uv/` is not normally synced to the Modal work directory.
|
|
128
|
+
|
|
129
|
+
## Sync And Deployment
|
|
130
|
+
|
|
131
|
+
`modal-uv run` and `modal-uv exec` scan local files, apply built-in ignores plus `sync.ignore`, ask the warm Modal container which files are missing or stale, upload only those files, spawn the execution, print the Modal function call ID, and return immediately.
|
|
132
|
+
|
|
133
|
+
The detached daemon lazily ensures the Modal app is deployed before running work. It generates `.modal-uv/deployment.py` and redeploys when the deployment fingerprint changes. The fingerprint includes the deployment template, Modal-relevant config values, and the repo `pyproject.toml` SHA256 when present.
|
|
134
|
+
|
|
135
|
+
During a running command, `modal-uv` periodically commits the Modal Volume every `volume.commit_interval_seconds` seconds, plus one final commit after the command exits. This persists outputs and checkpoints written under the mounted volume during long runs.
|
|
136
|
+
|
|
137
|
+
Ordinary source changes do not redeploy the app; they are handled by direct sync.
|
|
138
|
+
|
|
139
|
+
Modal authentication remains Modal's normal user-global authentication. `modal-uv` does not create repo-local auth files.
|
|
140
|
+
|
|
141
|
+
## Commands
|
|
142
|
+
|
|
143
|
+
Run `uv` commands on Modal:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
modal-uv run -- pytest
|
|
147
|
+
modal-uv run -- python -m lab
|
|
148
|
+
modal-uv run -- python train.py --epochs 10
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Tail or abort a spawned execution:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
modal-uv logs fc-...
|
|
155
|
+
modal-uv abort fc-...
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Run shell-style commands in the synced Modal work directory:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
modal-uv exec -- nvidia-smi
|
|
162
|
+
modal-uv exec -- 'ls -la && pwd'
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Open Modal's native interactive shell through the passthrough command:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
modal-uv modal -- shell
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Show Modal app status:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
modal-uv status
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Check the configured Modal volume directly:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
modal-uv modal -- volume ls modal-uv-cache
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Initialize or align modal-uv files in the current directory:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
modal-uv init
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Run any Modal CLI command through the modal-uv environment:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
modal-uv modal -- app list
|
|
193
|
+
modal-uv modal -- volume ls
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Daemon helpers:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
modal-uv daemon-status
|
|
200
|
+
modal-uv daemon-stop
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Updates
|
|
204
|
+
|
|
205
|
+
Upgrade modal-uv and refresh the skill on all detected agents:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
modal-uv update
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Install the skill to a specific agent (`opencode`, `claude`, `agents`) or an explicit directory path:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
modal-uv install-skill opencode
|
|
215
|
+
modal-uv install-skill /path/to/skills/dir
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Detected agents are based on which config directories exist (`~/.config/opencode/`, `~/.claude/`, `~/.agents/`).
|
|
219
|
+
|
|
220
|
+
Use `--config` or `-c` to specify a custom config file:
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
modal-uv run --config path/to/modal-uv.yaml -- pytest
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Checks
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
uv run ruff check .
|
|
230
|
+
uv run ruff format --check .
|
|
231
|
+
uv run pytest
|
|
232
|
+
```
|
modal_uv-0.2.0/README.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# modal-uv
|
|
2
|
+
|
|
3
|
+
Run `uv` commands on Modal.com with GPU support, direct file sync, async execution IDs, logs, abort, and persistent Modal volumes.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### Option 1: Coding Agent (Recommended)
|
|
8
|
+
|
|
9
|
+
Paste this prompt to your coding agent (opencode, Claude Code, Gemini CLI, etc.):
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Install modal-uv globally and set it up:
|
|
13
|
+
1. Run: pip install modal-uv
|
|
14
|
+
2. Run: modal-uv onboard
|
|
15
|
+
- This opens a browser for Modal OAuth authentication
|
|
16
|
+
- Complete the auth flow in the browser
|
|
17
|
+
- It also installs the use-modal-uv skill to detected coding agents
|
|
18
|
+
3. In the project repo, run: modal-uv init
|
|
19
|
+
- This creates modal-uv.yaml with defaults if missing
|
|
20
|
+
- It creates .modal-uv/ for generated state and adds it to .gitignore
|
|
21
|
+
4. Edit modal-uv.yaml to set app_name, gpu, and volume.name for this project
|
|
22
|
+
5. Run: modal-uv doctor
|
|
23
|
+
- This checks modal-uv health: auth state, volume existence, app deployment, daemon status
|
|
24
|
+
- Does not wake the container
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Option 2: Manual Getting Started
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install modal-uv
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Authenticate with Modal (opens browser for OAuth):
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
modal-uv onboard
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
This also installs the `use-modal-uv` skill to detected coding agents (`~/.config/opencode/`, `~/.claude/`, `~/.agents/`).
|
|
40
|
+
|
|
41
|
+
In your project repo, initialize modal-uv files:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
modal-uv init
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This creates `modal-uv.yaml` with defaults (using the directory name as `app_name`) if missing, and creates `.modal-uv/` for generated state with a `.gitignore` entry.
|
|
48
|
+
|
|
49
|
+
Edit `modal-uv.yaml` to configure your app:
|
|
50
|
+
|
|
51
|
+
```yaml
|
|
52
|
+
app_name: "my-project"
|
|
53
|
+
gpu: "T4"
|
|
54
|
+
work_dir: "/tmp/work"
|
|
55
|
+
|
|
56
|
+
volumes:
|
|
57
|
+
- name: "modal-uv-cache"
|
|
58
|
+
mount_path: "/mnt/volume"
|
|
59
|
+
commit_interval_seconds: 30
|
|
60
|
+
|
|
61
|
+
env: {}
|
|
62
|
+
|
|
63
|
+
runtime:
|
|
64
|
+
scaledown_window_seconds: 300
|
|
65
|
+
|
|
66
|
+
image:
|
|
67
|
+
python_version: "3.12"
|
|
68
|
+
base_image: "python:3.12-slim"
|
|
69
|
+
|
|
70
|
+
sync:
|
|
71
|
+
ignore:
|
|
72
|
+
- "data/**"
|
|
73
|
+
- "*.ckpt"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Then run commands on Modal:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
modal-uv run -- pytest
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Repository Configuration
|
|
83
|
+
|
|
84
|
+
`modal-uv.yaml` at the repository root is discovered by walking up from the current directory, similar to `git` or `uv`.
|
|
85
|
+
|
|
86
|
+
Fields:
|
|
87
|
+
|
|
88
|
+
- `app_name`: Modal app name (required)
|
|
89
|
+
- `gpu`: GPU type, such as `T4`, `A10G`, `A100`, `H100`, or `L4`
|
|
90
|
+
- `work_dir`: Working directory inside the Modal container (default: `/root/work`)
|
|
91
|
+
- `volumes`: Modal volumes to mount in the container; may be empty or omitted
|
|
92
|
+
- `volumes[].name`: Modal volume name
|
|
93
|
+
- `volumes[].mount_path`: Mount path in the container (default: `/root/.cache`)
|
|
94
|
+
- `volumes[].commit_interval_seconds`: Periodic Modal Volume commit interval while a command runs (default: `30`)
|
|
95
|
+
- `env`: Extra container environment variables merged over modal-uv defaults
|
|
96
|
+
- `runtime.scaledown_window_seconds`: Modal worker scaledown window (default: `300`)
|
|
97
|
+
- `runtime.exec`: Optional shell executable for `modal-uv exec`; if omitted, the remote Worker uses `$SHELL`, then `/bin/sh`
|
|
98
|
+
- `image.python_version`: Python version (default: `3.12`)
|
|
99
|
+
- `image.base_image`: Base Docker image (default: `python:3.12-slim`)
|
|
100
|
+
- `sync.ignore`: gitignore-style patterns excluded from direct sync
|
|
101
|
+
|
|
102
|
+
## Repo-Local State
|
|
103
|
+
|
|
104
|
+
`modal-uv` creates `.modal-uv/` at the repo root for generated/runtime files and ensures the root `.gitignore` ignores it.
|
|
105
|
+
|
|
106
|
+
Examples of generated files:
|
|
107
|
+
|
|
108
|
+
- `.modal-uv/deployment.py`
|
|
109
|
+
- `.modal-uv/daemon.pid`
|
|
110
|
+
- `.modal-uv/daemon.sock`
|
|
111
|
+
- `.modal-uv/daemon.log`
|
|
112
|
+
|
|
113
|
+
`.modal-uv/` is not normally synced to the Modal work directory.
|
|
114
|
+
|
|
115
|
+
## Sync And Deployment
|
|
116
|
+
|
|
117
|
+
`modal-uv run` and `modal-uv exec` scan local files, apply built-in ignores plus `sync.ignore`, ask the warm Modal container which files are missing or stale, upload only those files, spawn the execution, print the Modal function call ID, and return immediately.
|
|
118
|
+
|
|
119
|
+
The detached daemon lazily ensures the Modal app is deployed before running work. It generates `.modal-uv/deployment.py` and redeploys when the deployment fingerprint changes. The fingerprint includes the deployment template, Modal-relevant config values, and the repo `pyproject.toml` SHA256 when present.
|
|
120
|
+
|
|
121
|
+
During a running command, `modal-uv` periodically commits the Modal Volume every `volume.commit_interval_seconds` seconds, plus one final commit after the command exits. This persists outputs and checkpoints written under the mounted volume during long runs.
|
|
122
|
+
|
|
123
|
+
Ordinary source changes do not redeploy the app; they are handled by direct sync.
|
|
124
|
+
|
|
125
|
+
Modal authentication remains Modal's normal user-global authentication. `modal-uv` does not create repo-local auth files.
|
|
126
|
+
|
|
127
|
+
## Commands
|
|
128
|
+
|
|
129
|
+
Run `uv` commands on Modal:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
modal-uv run -- pytest
|
|
133
|
+
modal-uv run -- python -m lab
|
|
134
|
+
modal-uv run -- python train.py --epochs 10
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Tail or abort a spawned execution:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
modal-uv logs fc-...
|
|
141
|
+
modal-uv abort fc-...
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Run shell-style commands in the synced Modal work directory:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
modal-uv exec -- nvidia-smi
|
|
148
|
+
modal-uv exec -- 'ls -la && pwd'
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Open Modal's native interactive shell through the passthrough command:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
modal-uv modal -- shell
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Show Modal app status:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
modal-uv status
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Check the configured Modal volume directly:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
modal-uv modal -- volume ls modal-uv-cache
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Initialize or align modal-uv files in the current directory:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
modal-uv init
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Run any Modal CLI command through the modal-uv environment:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
modal-uv modal -- app list
|
|
179
|
+
modal-uv modal -- volume ls
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Daemon helpers:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
modal-uv daemon-status
|
|
186
|
+
modal-uv daemon-stop
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Updates
|
|
190
|
+
|
|
191
|
+
Upgrade modal-uv and refresh the skill on all detected agents:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
modal-uv update
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Install the skill to a specific agent (`opencode`, `claude`, `agents`) or an explicit directory path:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
modal-uv install-skill opencode
|
|
201
|
+
modal-uv install-skill /path/to/skills/dir
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Detected agents are based on which config directories exist (`~/.config/opencode/`, `~/.claude/`, `~/.agents/`).
|
|
205
|
+
|
|
206
|
+
Use `--config` or `-c` to specify a custom config file:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
modal-uv run --config path/to/modal-uv.yaml -- pytest
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Checks
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
uv run ruff check .
|
|
216
|
+
uv run ruff format --check .
|
|
217
|
+
uv run pytest
|
|
218
|
+
```
|