expdeploy 0.1.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.
Files changed (88) hide show
  1. expdeploy-0.1.0/.dockerignore +20 -0
  2. expdeploy-0.1.0/.github/workflows/ci.yml +47 -0
  3. expdeploy-0.1.0/.github/workflows/container.yml +41 -0
  4. expdeploy-0.1.0/.github/workflows/docs.yml +39 -0
  5. expdeploy-0.1.0/.github/workflows/release.yml +40 -0
  6. expdeploy-0.1.0/.gitignore +43 -0
  7. expdeploy-0.1.0/.pre-commit-config.yaml +20 -0
  8. expdeploy-0.1.0/CONTRIBUTING.md +58 -0
  9. expdeploy-0.1.0/Dockerfile +32 -0
  10. expdeploy-0.1.0/LICENSE +21 -0
  11. expdeploy-0.1.0/PKG-INFO +65 -0
  12. expdeploy-0.1.0/README.md +24 -0
  13. expdeploy-0.1.0/docs/cli-reference.md +94 -0
  14. expdeploy-0.1.0/docs/container.md +37 -0
  15. expdeploy-0.1.0/docs/getting-started.md +46 -0
  16. expdeploy-0.1.0/docs/index.md +23 -0
  17. expdeploy-0.1.0/docs/manifest.md +63 -0
  18. expdeploy-0.1.0/docs/storage.md +68 -0
  19. expdeploy-0.1.0/docs/superpowers/plans/2026-05-14-expdeploy-bootstrap.md +2824 -0
  20. expdeploy-0.1.0/docs/superpowers/plans/2026-05-15-expdeploy-plan-2-storage-battery.md +3113 -0
  21. expdeploy-0.1.0/docs/superpowers/plans/2026-05-17-expdeploy-plan-3-supabase-container-docs.md +2165 -0
  22. expdeploy-0.1.0/docs/superpowers/specs/2026-05-14-expdeploy-design.md +782 -0
  23. expdeploy-0.1.0/examples/hello_world/index.js +47 -0
  24. expdeploy-0.1.0/examples/hello_world/manifest.toml +16 -0
  25. expdeploy-0.1.0/examples/hello_world/style.css +15 -0
  26. expdeploy-0.1.0/examples/mini_battery/battery.toml +11 -0
  27. expdeploy-0.1.0/examples/mini_battery/flanker/index.js +24 -0
  28. expdeploy-0.1.0/examples/mini_battery/flanker/manifest.toml +11 -0
  29. expdeploy-0.1.0/examples/mini_battery/stroop/index.js +24 -0
  30. expdeploy-0.1.0/examples/mini_battery/stroop/manifest.toml +11 -0
  31. expdeploy-0.1.0/mkdocs.yml +50 -0
  32. expdeploy-0.1.0/pyproject.toml +99 -0
  33. expdeploy-0.1.0/scripts/fetch_jspsych_assets.py +174 -0
  34. expdeploy-0.1.0/src/expdeploy/__init__.py +3 -0
  35. expdeploy-0.1.0/src/expdeploy/__main__.py +6 -0
  36. expdeploy-0.1.0/src/expdeploy/app.py +256 -0
  37. expdeploy-0.1.0/src/expdeploy/battery/__init__.py +0 -0
  38. expdeploy-0.1.0/src/expdeploy/battery/counterbalance.py +86 -0
  39. expdeploy-0.1.0/src/expdeploy/battery/orchestrator.py +48 -0
  40. expdeploy-0.1.0/src/expdeploy/cli.py +580 -0
  41. expdeploy-0.1.0/src/expdeploy/importmap.py +76 -0
  42. expdeploy-0.1.0/src/expdeploy/jspsych_assets/8.2.3/jspsych.css +524 -0
  43. expdeploy-0.1.0/src/expdeploy/jspsych_assets/8.2.3/jspsych.js +5535 -0
  44. expdeploy-0.1.0/src/expdeploy/jspsych_assets/8.2.3/manifest.json +53 -0
  45. expdeploy-0.1.0/src/expdeploy/jspsych_assets/8.2.3/plugins/call-function.js +69 -0
  46. expdeploy-0.1.0/src/expdeploy/jspsych_assets/8.2.3/plugins/fullscreen.js +164 -0
  47. expdeploy-0.1.0/src/expdeploy/jspsych_assets/8.2.3/plugins/html-button-response.js +223 -0
  48. expdeploy-0.1.0/src/expdeploy/jspsych_assets/8.2.3/plugins/html-keyboard-response.js +183 -0
  49. expdeploy-0.1.0/src/expdeploy/jspsych_assets/8.2.3/plugins/image-button-response.js +326 -0
  50. expdeploy-0.1.0/src/expdeploy/jspsych_assets/8.2.3/plugins/image-keyboard-response.js +270 -0
  51. expdeploy-0.1.0/src/expdeploy/jspsych_assets/8.2.3/plugins/instructions.js +341 -0
  52. expdeploy-0.1.0/src/expdeploy/jspsych_assets/8.2.3/plugins/preload.js +384 -0
  53. expdeploy-0.1.0/src/expdeploy/jspsych_assets/8.2.3/plugins/survey-text.js +244 -0
  54. expdeploy-0.1.0/src/expdeploy/jspsych_assets/__init__.py +1 -0
  55. expdeploy-0.1.0/src/expdeploy/loader.py +44 -0
  56. expdeploy-0.1.0/src/expdeploy/manifest.py +127 -0
  57. expdeploy-0.1.0/src/expdeploy/renderer.py +37 -0
  58. expdeploy-0.1.0/src/expdeploy/session.py +96 -0
  59. expdeploy-0.1.0/src/expdeploy/storage/__init__.py +1 -0
  60. expdeploy-0.1.0/src/expdeploy/storage/base.py +57 -0
  61. expdeploy-0.1.0/src/expdeploy/storage/fs.py +193 -0
  62. expdeploy-0.1.0/src/expdeploy/storage/sqlite.py +171 -0
  63. expdeploy-0.1.0/src/expdeploy/storage/supabase.py +104 -0
  64. expdeploy-0.1.0/src/expdeploy/storage/supabase_schema.sql +26 -0
  65. expdeploy-0.1.0/src/expdeploy/templates/deploy.html.j2 +46 -0
  66. expdeploy-0.1.0/tests/__init__.py +0 -0
  67. expdeploy-0.1.0/tests/conftest.py +1 -0
  68. expdeploy-0.1.0/tests/e2e/__init__.py +0 -0
  69. expdeploy-0.1.0/tests/e2e/test_hello_world.py +91 -0
  70. expdeploy-0.1.0/tests/e2e/test_mini_battery.py +94 -0
  71. expdeploy-0.1.0/tests/integration/__init__.py +0 -0
  72. expdeploy-0.1.0/tests/integration/test_app.py +304 -0
  73. expdeploy-0.1.0/tests/unit/__init__.py +0 -0
  74. expdeploy-0.1.0/tests/unit/test_battery_manifest.py +57 -0
  75. expdeploy-0.1.0/tests/unit/test_battery_orchestrator.py +68 -0
  76. expdeploy-0.1.0/tests/unit/test_cli.py +349 -0
  77. expdeploy-0.1.0/tests/unit/test_counterbalance.py +93 -0
  78. expdeploy-0.1.0/tests/unit/test_fs.py +209 -0
  79. expdeploy-0.1.0/tests/unit/test_importmap.py +98 -0
  80. expdeploy-0.1.0/tests/unit/test_loader.py +52 -0
  81. expdeploy-0.1.0/tests/unit/test_manifest.py +102 -0
  82. expdeploy-0.1.0/tests/unit/test_renderer.py +82 -0
  83. expdeploy-0.1.0/tests/unit/test_sanity.py +9 -0
  84. expdeploy-0.1.0/tests/unit/test_session.py +70 -0
  85. expdeploy-0.1.0/tests/unit/test_sqlite.py +105 -0
  86. expdeploy-0.1.0/tests/unit/test_supabase.py +106 -0
  87. expdeploy-0.1.0/tests/unit/test_sync.py +91 -0
  88. expdeploy-0.1.0/uv.lock +2413 -0
@@ -0,0 +1,20 @@
1
+ .git/
2
+ .venv/
3
+ .uv/
4
+ .mypy_cache/
5
+ .ruff_cache/
6
+ .pytest_cache/
7
+ **/__pycache__/
8
+ **/*.pyc
9
+ data/
10
+ state/
11
+ tests/
12
+ docs/
13
+ examples/
14
+ .github/
15
+ **/*.md
16
+ !README.md
17
+ .gitignore
18
+ .pre-commit-config.yaml
19
+ playwright-report/
20
+ test-results/
@@ -0,0 +1,47 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ${{ matrix.os }}
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ os: [ubuntu-latest, macos-latest]
16
+ python: ["3.11", "3.12"]
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - name: Install uv
22
+ uses: astral-sh/setup-uv@v3
23
+ with:
24
+ version: "0.5.x"
25
+
26
+ - name: Set up Python ${{ matrix.python }}
27
+ run: uv python install ${{ matrix.python }}
28
+
29
+ - name: Install dependencies
30
+ run: uv sync --extra dev
31
+
32
+ - name: Install Playwright browsers
33
+ run: uv run playwright install --with-deps chromium
34
+
35
+ - name: Lint (ruff)
36
+ run: |
37
+ uv run ruff check .
38
+ uv run ruff format --check .
39
+
40
+ - name: Type check (mypy)
41
+ run: uv run mypy src/expdeploy
42
+
43
+ - name: Unit + integration tests
44
+ run: uv run pytest tests/unit tests/integration -v
45
+
46
+ - name: End-to-end (Playwright)
47
+ run: uv run pytest tests/e2e -v -m e2e
@@ -0,0 +1,41 @@
1
+ name: Container
2
+
3
+ on:
4
+ push:
5
+ tags: ['v*']
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+ packages: write
11
+
12
+ jobs:
13
+ publish:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: docker/setup-qemu-action@v3
18
+ - uses: docker/setup-buildx-action@v3
19
+ - uses: docker/login-action@v3
20
+ with:
21
+ registry: ghcr.io
22
+ username: ${{ github.actor }}
23
+ password: ${{ secrets.GITHUB_TOKEN }}
24
+ - id: meta
25
+ uses: docker/metadata-action@v5
26
+ with:
27
+ images: ghcr.io/${{ github.repository_owner }}/expdeploy
28
+ tags: |
29
+ type=ref,event=tag
30
+ type=semver,pattern={{version}}
31
+ type=semver,pattern={{major}}.{{minor}}
32
+ type=raw,value=latest
33
+ - uses: docker/build-push-action@v6
34
+ with:
35
+ context: .
36
+ platforms: linux/amd64,linux/arm64
37
+ push: true
38
+ tags: ${{ steps.meta.outputs.tags }}
39
+ labels: ${{ steps.meta.outputs.labels }}
40
+ cache-from: type=gha
41
+ cache-to: type=gha,mode=max
@@ -0,0 +1,39 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+ pages: write
11
+ id-token: write
12
+
13
+ concurrency:
14
+ group: "pages"
15
+ cancel-in-progress: false
16
+
17
+ jobs:
18
+ build:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ - uses: astral-sh/setup-uv@v3
23
+ with:
24
+ version: "0.5.x"
25
+ - run: uv python install 3.12
26
+ - run: uv sync --extra docs
27
+ - run: uv run mkdocs build --strict
28
+ - uses: actions/upload-pages-artifact@v3
29
+ with:
30
+ path: site
31
+ deploy:
32
+ needs: build
33
+ runs-on: ubuntu-latest
34
+ environment:
35
+ name: github-pages
36
+ url: ${{ steps.deployment.outputs.page_url }}
37
+ steps:
38
+ - id: deployment
39
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,40 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ['v*']
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+ id-token: write
11
+
12
+ jobs:
13
+ build:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: astral-sh/setup-uv@v3
18
+ with:
19
+ version: "0.5.x"
20
+ - run: uv python install 3.12
21
+ - run: uv sync --extra dev
22
+ - run: uv build
23
+ - uses: actions/upload-artifact@v4
24
+ with:
25
+ name: dist
26
+ path: dist/
27
+ publish:
28
+ needs: build
29
+ runs-on: ubuntu-latest
30
+ environment:
31
+ name: pypi
32
+ url: https://pypi.org/p/expdeploy
33
+ steps:
34
+ - uses: actions/download-artifact@v4
35
+ with:
36
+ name: dist
37
+ path: dist/
38
+ - uses: pypa/gh-action-pypi-publish@release/v1
39
+ with:
40
+ packages-dir: dist/
@@ -0,0 +1,43 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ .eggs/
7
+ build/
8
+ dist/
9
+ .venv/
10
+ .python-version
11
+
12
+ # uv
13
+ .uv/
14
+
15
+ # pytest / coverage
16
+ .pytest_cache/
17
+ .coverage
18
+ htmlcov/
19
+ .tox/
20
+
21
+ # mypy / ruff
22
+ .mypy_cache/
23
+ .ruff_cache/
24
+
25
+ # Playwright
26
+ test-results/
27
+ playwright-report/
28
+ playwright/.cache/
29
+
30
+ # data dirs from local runs
31
+ data/
32
+ state/
33
+
34
+ # editor / OS
35
+ .DS_Store
36
+ .idea/
37
+ .vscode/
38
+
39
+ # expdeploy-specific
40
+ sessions_*/
41
+
42
+ # mkdocs build output
43
+ site/
@@ -0,0 +1,20 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.3.4
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
8
+
9
+ - repo: https://github.com/pre-commit/mirrors-mypy
10
+ rev: v1.9.0
11
+ hooks:
12
+ - id: mypy
13
+ files: ^src/
14
+ additional_dependencies:
15
+ - fastapi>=0.110
16
+ - pydantic>=2.6
17
+ - typer>=0.12
18
+ - jinja2>=3.1
19
+ - filelock>=3.13
20
+ - types-toml
@@ -0,0 +1,58 @@
1
+ # Contributing to expdeploy
2
+
3
+ Thanks for your interest in contributing.
4
+
5
+ ## Development setup
6
+
7
+ ```bash
8
+ git clone https://github.com/lobennett/expdeploy.git
9
+ cd expdeploy
10
+ uv sync --extra dev
11
+ uv run pre-commit install
12
+ uv run playwright install --with-deps chromium
13
+ ```
14
+
15
+ ## Branches
16
+
17
+ - `main` is the integration branch; releases are tagged from `main` (`v0.X.Y`).
18
+ - Feature work lands on `feat/<short-name>` branches and merges via PR.
19
+ - Each PR runs the full CI matrix (Ubuntu+macOS × Python 3.11+3.12).
20
+
21
+ ## Commit messages
22
+
23
+ Short imperative subject lines, ≤72 characters. Examples:
24
+ - `Add SupabaseAdapter.save (Postgres upsert + storage bucket upload)`
25
+ - `Drop dead tomli conditional dep (we require Python 3.11+)`
26
+
27
+ ## Tests
28
+
29
+ - TDD is the default workflow. Write a failing test before the implementation.
30
+ - Three tiers: `tests/unit/`, `tests/integration/`, `tests/e2e/` (Playwright).
31
+ - New storage adapters must satisfy the parametrized adapter contract suite (TBD).
32
+ - E2E tests are marked `@pytest.mark.e2e`. Run with `uv run pytest -m e2e`.
33
+
34
+ ## Lint + format + types
35
+
36
+ ```bash
37
+ uv run ruff check .
38
+ uv run ruff format --check .
39
+ uv run mypy src/expdeploy
40
+ ```
41
+
42
+ `pre-commit` runs all three on every commit. If a hook reformats your code, re-stage and commit again.
43
+
44
+ ## jsPsych asset re-vendoring
45
+
46
+ If you bump the jsPsych version in `scripts/fetch_jspsych_assets.py`, re-run the script:
47
+
48
+ ```bash
49
+ uv run python scripts/fetch_jspsych_assets.py
50
+ ```
51
+
52
+ Commit the regenerated `src/expdeploy/jspsych_assets/<version>/` directory.
53
+
54
+ ## Releasing
55
+
56
+ 1. Bump `version` in `pyproject.toml` and `__version__` in `src/expdeploy/__init__.py`.
57
+ 2. Open a PR titled `Release v0.X.Y`.
58
+ 3. After merge, tag `v0.X.Y`. CI publishes the multi-arch container to GHCR and the wheel to PyPI.
@@ -0,0 +1,32 @@
1
+ # syntax=docker/dockerfile:1
2
+ FROM python:3.12-slim AS runtime
3
+
4
+ # Install uv from its official image
5
+ COPY --from=ghcr.io/astral-sh/uv:0.5 /uv /usr/local/bin/uv
6
+
7
+ WORKDIR /opt/expdeploy
8
+
9
+ # Install Python deps first (cache layer)
10
+ COPY pyproject.toml uv.lock README.md ./
11
+ COPY src/ ./src/
12
+
13
+ RUN uv sync --frozen --no-dev --extra supabase \
14
+ && rm -rf /root/.cache/uv
15
+
16
+ # Non-root for HPC / Apptainer compatibility
17
+ RUN useradd -m -u 1000 expdeploy
18
+ USER expdeploy
19
+
20
+ ENV PYTHONUNBUFFERED=1 \
21
+ EXPDEPLOY_DATA_DIR=/data \
22
+ EXPDEPLOY_HOST=0.0.0.0
23
+
24
+ EXPOSE 8080
25
+ VOLUME ["/data", "/experiments"]
26
+
27
+ ENTRYPOINT ["uv", "run", "--no-sync", "expdeploy"]
28
+ CMD ["--help"]
29
+
30
+ LABEL org.opencontainers.image.title="expdeploy" \
31
+ org.opencontainers.image.source="https://github.com/lobennett/expdeploy" \
32
+ org.opencontainers.image.licenses="MIT"
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Logan Bennett
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,65 @@
1
+ Metadata-Version: 2.4
2
+ Name: expdeploy
3
+ Version: 0.1.0
4
+ Summary: Modern Python deploy tool for jsPsych v8 experiments
5
+ Author-email: Logan Bennett <logben@stanford.edu>
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Keywords: bids,cognitive-science,experiment,fmri,jspsych,psychology
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Topic :: Scientific/Engineering
15
+ Requires-Python: >=3.11
16
+ Requires-Dist: fastapi>=0.110
17
+ Requires-Dist: filelock>=3.13
18
+ Requires-Dist: jinja2>=3.1
19
+ Requires-Dist: pydantic>=2.6
20
+ Requires-Dist: python-ulid>=2.7
21
+ Requires-Dist: rich>=13.7
22
+ Requires-Dist: typer>=0.12
23
+ Requires-Dist: uvicorn[standard]>=0.27
24
+ Provides-Extra: dev
25
+ Requires-Dist: httpx>=0.27; extra == 'dev'
26
+ Requires-Dist: hypothesis>=6.98; extra == 'dev'
27
+ Requires-Dist: mypy>=1.9; extra == 'dev'
28
+ Requires-Dist: playwright>=1.42; extra == 'dev'
29
+ Requires-Dist: pre-commit>=3.6; extra == 'dev'
30
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
31
+ Requires-Dist: pytest-cov>=4.1; extra == 'dev'
32
+ Requires-Dist: pytest>=8.0; extra == 'dev'
33
+ Requires-Dist: ruff>=0.3; extra == 'dev'
34
+ Provides-Extra: docs
35
+ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
36
+ Requires-Dist: mkdocs-typer>=0.0.3; extra == 'docs'
37
+ Requires-Dist: mkdocs>=1.6; extra == 'docs'
38
+ Provides-Extra: supabase
39
+ Requires-Dist: supabase>=2.4; extra == 'supabase'
40
+ Description-Content-Type: text/markdown
41
+
42
+ # expdeploy
43
+
44
+ A modern Python deploy tool for [jsPsych v8](https://www.jspsych.org/) experiments.
45
+ Pays homage to [expfactory](https://github.com/expfactory) and expands its scope:
46
+ canonical jsPsych ESM authoring, BIDS-compliant data layout, batteries with
47
+ counterbalancing, and reproducibility via OCI containers.
48
+
49
+ **Status:** v0.1-alpha. Under active development; not yet stable.
50
+
51
+ ## Quick start
52
+
53
+ ```bash
54
+ uv tool install expdeploy
55
+ expdeploy run ./examples/hello_world --subject 01 --port 8080
56
+ # opens http://localhost:8080
57
+ ```
58
+
59
+ ## Documentation
60
+
61
+ See `docs/superpowers/specs/2026-05-14-expdeploy-design.md` for the v0.1 design spec.
62
+
63
+ ## License
64
+
65
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,24 @@
1
+ # expdeploy
2
+
3
+ A modern Python deploy tool for [jsPsych v8](https://www.jspsych.org/) experiments.
4
+ Pays homage to [expfactory](https://github.com/expfactory) and expands its scope:
5
+ canonical jsPsych ESM authoring, BIDS-compliant data layout, batteries with
6
+ counterbalancing, and reproducibility via OCI containers.
7
+
8
+ **Status:** v0.1-alpha. Under active development; not yet stable.
9
+
10
+ ## Quick start
11
+
12
+ ```bash
13
+ uv tool install expdeploy
14
+ expdeploy run ./examples/hello_world --subject 01 --port 8080
15
+ # opens http://localhost:8080
16
+ ```
17
+
18
+ ## Documentation
19
+
20
+ See `docs/superpowers/specs/2026-05-14-expdeploy-design.md` for the v0.1 design spec.
21
+
22
+ ## License
23
+
24
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,94 @@
1
+ # CLI reference
2
+
3
+ !!! note "Auto-generation not available"
4
+ `mkdocs-typer` is installed but did not expand the directive with the current Typer version.
5
+ Run `expdeploy --help` (or `expdeploy <cmd> --help`) to see full command documentation.
6
+
7
+ ## Global options
8
+
9
+ ```
10
+ expdeploy [OPTIONS] COMMAND [ARGS]...
11
+ ```
12
+
13
+ ## Commands
14
+
15
+ ### `run`
16
+
17
+ Serve a single experiment or battery.
18
+
19
+ ```bash
20
+ expdeploy run PATH [OPTIONS]
21
+ --subject TEXT Subject ID (required)
22
+ --session TEXT Session number
23
+ --run TEXT Run number
24
+ --port INTEGER Port to listen on [default: 8080]
25
+ --data-dir PATH Data output directory [default: ./data]
26
+ --no-browser Don't open browser automatically
27
+ --remote TEXT Remote adapter names to mirror writes to (e.g. supabase)
28
+ --vars TEXT JSON string of extra variables injected into window.expdeploy.vars
29
+ ```
30
+
31
+ ### `validate`
32
+
33
+ Validate a manifest.toml or battery.toml without running.
34
+
35
+ ```bash
36
+ expdeploy validate PATH
37
+ ```
38
+
39
+ ### `status`
40
+
41
+ List runs recorded in the local catalog.
42
+
43
+ ```bash
44
+ expdeploy status [OPTIONS]
45
+ --data-dir PATH Data directory [default: ./data]
46
+ ```
47
+
48
+ ### `sync`
49
+
50
+ Replay failed remote-storage writes against the configured adapter.
51
+
52
+ ```bash
53
+ expdeploy sync [OPTIONS]
54
+ --adapter TEXT Remote adapter name [default: supabase]
55
+ --dry-run List pending writes without executing
56
+ --data-dir PATH Data directory [default: ./data]
57
+ ```
58
+
59
+ ### `build`
60
+
61
+ Build a study-specific OCI image with experiments baked in.
62
+
63
+ ```bash
64
+ expdeploy build TARGET [OPTIONS]
65
+ --tag TEXT OCI image tag (required)
66
+ --base-tag TEXT Base image tag to FROM [default: ghcr.io/lobennett/expdeploy:latest]
67
+ --engine TEXT docker | podman [default: docker]
68
+ --push / --no-push Push after build [default: no-push]
69
+ --output PATH Where to write study.Dockerfile
70
+ ```
71
+
72
+ ### `supabase migrate`
73
+
74
+ Apply idempotent DDL to the configured Supabase Postgres.
75
+
76
+ ```bash
77
+ expdeploy supabase migrate
78
+ ```
79
+
80
+ ### `supabase test-connection`
81
+
82
+ Verify the configured Supabase credentials and bucket access.
83
+
84
+ ```bash
85
+ expdeploy supabase test-connection
86
+ ```
87
+
88
+ ### `supabase drop`
89
+
90
+ DROP the expdeploy schema. Test environments only.
91
+
92
+ ```bash
93
+ expdeploy supabase drop --confirm
94
+ ```
@@ -0,0 +1,37 @@
1
+ # Container
2
+
3
+ The OCI image lives at `ghcr.io/lobennett/expdeploy:<version>` (multi-arch: linux/amd64 + linux/arm64).
4
+
5
+ ## Day-to-day dev (bind-mount)
6
+
7
+ ```bash
8
+ podman run --rm -p 8080:8080 \
9
+ -v $PWD/experiments:/experiments:ro \
10
+ -v $PWD/data:/data \
11
+ ghcr.io/lobennett/expdeploy:latest \
12
+ run /experiments/flanker --subject 01 --data-dir /data
13
+ ```
14
+
15
+ ## Study image (reproducible scientific artifact)
16
+
17
+ `expdeploy build` produces an image with experiments baked in. This is the image you cite in your paper.
18
+
19
+ ```bash
20
+ expdeploy build ./battery.toml \
21
+ --tag ghcr.io/your-lab/study-2026:2026-05-17 \
22
+ --engine podman \
23
+ --push
24
+ ```
25
+
26
+ The generated `study.Dockerfile` is written next to the battery file and is gitable for transparency. The image carries OCI labels:
27
+
28
+ - `org.expdeploy.manifest_hash` — content hash of every experiment file + battery.toml
29
+ - `org.expdeploy.deploy_version` — `expdeploy` version that built the image
30
+
31
+ ## Apptainer / Singularity
32
+
33
+ ```bash
34
+ apptainer pull docker://ghcr.io/lobennett/expdeploy:latest
35
+ apptainer run --bind ./experiments:/experiments --bind ./data:/data \
36
+ expdeploy.sif run /experiments/battery.toml --subject 01
37
+ ```
@@ -0,0 +1,46 @@
1
+ # Getting started
2
+
3
+ ## Install
4
+
5
+ The package will be available on PyPI after the v0.1.0 release:
6
+
7
+ ```bash
8
+ uv tool install expdeploy
9
+ ```
10
+
11
+ For development:
12
+
13
+ ```bash
14
+ git clone https://github.com/lobennett/expdeploy.git
15
+ cd expdeploy
16
+ uv sync --extra dev
17
+ ```
18
+
19
+ ## Run the hello-world
20
+
21
+ ```bash
22
+ expdeploy run ./examples/hello_world --subject 01 --port 8080
23
+ ```
24
+
25
+ This opens `http://localhost:8080` in your browser. Press any key on the stimulus and you'll see a `Saved.` confirmation. The raw JSON lands at `./data/raw/sub-01/sub-01_task-hello_beh.json`; an entry appears in `./data/catalog.sqlite`.
26
+
27
+ ## Inspect runs
28
+
29
+ ```bash
30
+ expdeploy status --data-dir ./data
31
+ ```
32
+
33
+ ## Run a battery
34
+
35
+ ```bash
36
+ expdeploy run ./examples/mini_battery/battery.toml --subject 0
37
+ ```
38
+
39
+ Or inline:
40
+
41
+ ```bash
42
+ expdeploy run \
43
+ --exps ./flanker,./stroop,./nback \
44
+ --counterbalance latin_square \
45
+ --subject 01
46
+ ```
@@ -0,0 +1,23 @@
1
+ # expdeploy
2
+
3
+ A modern Python deploy tool for [jsPsych v8](https://www.jspsych.org/) experiments.
4
+
5
+ **Status: v0.1.0** — lab-ready.
6
+
7
+ ## What it does
8
+
9
+ - Serves jsPsych v8 experiments locally with **zero Node.js dependency** for experimenters.
10
+ - Authoring in canonical ESM (`import { initJsPsych } from 'jspsych'`) with local imports working out of the box.
11
+ - **BIDS-compatible** filesystem layout for fMRI and behavioral data.
12
+ - **Batteries** of experiments with four counterbalance schemes (fixed, Latin square, seeded random, user-supplied).
13
+ - **Local-first** storage with optional Supabase mirror, replayable via `expdeploy sync`.
14
+ - **Reproducibility**: layered OCI images via `expdeploy build` — a study image freezes deploy version, jsPsych version, every experiment file, and every Python dep.
15
+
16
+ ## Quick install
17
+
18
+ ```bash
19
+ uv tool install expdeploy
20
+ expdeploy run ./examples/hello_world --subject 01
21
+ ```
22
+
23
+ → [Getting started](getting-started.md)