acqstore
zip -r cloudscope_20260622_v2.zip
src tests docs-dev scripts pyproject.toml
-x "/pycache/"
".pyc"
".pyo"
".pytest_cache/"
".mypy_cache/"
".ruff_cache/"
".DS_Store"
".ipynb_checkpoints/"
"docs/site/"
"site/"
"build/"
"dist/"
".venv/"
".git/"
".svg"
".png"
".jpg"
".jpeg"
".tif"
".tiff"
".oir"
".czi"
".zarr/"
find packaging
-type d ( -name build -o -name dist ) -prune -o
-type f (
-name ".md" -o
-name ".sh" -o
-name ".icns" -o
-name ".spec" -o
-name ".entitlements" -o
-name ".plist"
) -print | zip cloudscope_packaging_20260610_v1.zip -@
find docs
-type d (
-name .ipynb_checkpoints -o
-name pycache -o
-name build -o
-name site
) -prune -o
-type f ! -name '.DS_Store' ! -name '.pyc' (
-name '.md' -o
-name '.ipynb' -o
-name '.css' -o
-name '.png' -o
-name '.svg' -o
-name '.html' -o
-name '.py'
) -print | zip cloudscope_docs_20260623_v1.zip -@
find tests -type f (
-name ".py" -o
-name ".md" -o
-name ".ipynb" -o
-name ".css" -o
-name ".png" -o
-name ".svg"
) -print | zip cloudscope_tests_20260612_v1.zip -@
find docs-dev -type f (
-name ".md" -o
-name ".ipynb" -o
-name ".css" -o
-name ".png" -o
-name "*.svg"
) -print | zip cloudscope_docs_dev_20260612_v1.zip -@
find src -type f (
-name ".py" -o
-name ".md" -o
-name ".ipynb" -o
-name ".css" -o
-name ".png" -o
-name ".svg"
) -print | zip cloudscope_src_20260612_v1.zip -@
find .github/workflows -type f (
-name ".py" -o
-name ".md" -o
-name ".ipynb" -o
-name ".css" -o
-name ".png" -o
-name ".svg"
-name "*.yml"
) -print | zip cloudscope_workflows_20260611_v1.zip -@
zip -r cloudscope_src_20260513_v1.zip src/cloudscope -i '*.py' '*.md'
zip -r cloudscope_src_20260609_v1.zip src/acqstore -i '*.py' '*.md'
zip -r cloudscope_tests_20260607_v1.zip tests -i '*.py' '*.md'
zip -r cloudscope_docs_20260607_v1.zip docs -i '*.py' '*.md'
zip -r cloudscope_scripts_20260607_v1.zip scripts -i '*.py' '*.md'
zip -r cloudscope_scripts_20260525_v6.zip scripts -i '*.py' '*.md'
zip -r cloudscope_sandbox_20260525_v6.zip sandbox -i '*.py' '*.md'
zip -r cloudscope_src_20260524_v6.zip src -i '*.py' '*.md'
zip -r kymflow_20260520_diameter_v1.zip /Users/cudmore/Sites/kymflow_outer/kymflow/src/kymflow/core/analysis/diameter_analysis -i '*.py' '*.md'
/Users/cudmore/Sites/kymflow_outer/kymflow/src/kymflow/core/analysis/diameter_analysis
src/cloudscope/app.py reads its runtime configuration from environment variables (see get_run_config_from_env in that file). Set the variables on the same command line that launches uv run.
uv run python src/cloudscope/app.pyCLOUDSCOPE_NATIVE defaults to true when CLOUDSCOPE_REMOTE is unset. Local native launch uses single-window mode: NiceGUI ui.run(native=True) with the velocity pool in the resizable right splitter panel on the home page. Use Velocity Pool in the header or drag the right splitter handle to open the panel.
Main-window geometry is persisted to AppConfig on move, resize, and shutdown.
CLOUDSCOPE_SINGLE_WINDOW=0 uv run python src/cloudscope/app.pyRestores Option C multi-window desktop mode: one process, one NiceGUI server, separate pywebview windows for Home (/) and Velocity Pool (/pool). Use Open Pool in the header to open or focus the pool window. Pool window uses a fixed 1000×800 size offset from the main window unless saved geometry exists.
CLOUDSCOPE_NATIVE=false uv run python src/cloudscope/app.pyNiceGUI starts a local server and opens (or lets you open) a browser tab. Add CLOUDSCOPE_PORT to pin the port:
CLOUDSCOPE_NATIVE=false CLOUDSCOPE_PORT=8080 uv run python src/cloudscope/app.pyCLOUDSCOPE_REMOTE=true uv run python src/cloudscope/app.pyCLOUDSCOPE_REMOTE=true flips the defaults to native=false, host=0.0.0.0, and port=8080. Override any of them explicitly when needed:
CLOUDSCOPE_REMOTE=true CLOUDSCOPE_HOST=0.0.0.0 CLOUDSCOPE_PORT=9000 uv run python src/cloudscope/app.pyCLOUDSCOPE_NATIVE=false CLOUDSCOPE_RELOAD=true uv run python src/cloudscope/app.py| Variable | Purpose | Default |
|---|---|---|
CLOUDSCOPE_REMOTE |
Treat the app as running on a remote/server host. Implies native=false and binds to 0.0.0.0:8080 unless overridden. |
false |
CLOUDSCOPE_NATIVE |
Force native (pywebview) on/off. | true when CLOUDSCOPE_REMOTE is unset, otherwise false |
CLOUDSCOPE_SINGLE_WINDOW |
Use single-window ui.run(native=True) for local desktop. Set to 0 for Option C multi-window. |
true |
CLOUDSCOPE_MULTI_WINDOW |
Deprecated explicit opt-in for Option C; set CLOUDSCOPE_SINGLE_WINDOW=0 instead. |
unset |
CLOUDSCOPE_DESKTOP_LAUNCHER |
Deprecated explicit launcher alias (option_c). |
unset |
CLOUDSCOPE_URL_HOST |
Host name used in Option C pywebview URLs. | 127.0.0.1 |
CLOUDSCOPE_RELOAD |
Toggle NiceGUI reload mode. | false |
CLOUDSCOPE_HOST |
Explicit NiceGUI host. | NiceGUI default (or 0.0.0.0 when remote) |
CLOUDSCOPE_PORT |
Explicit NiceGUI port. | NiceGUI default (or 8080 when remote) |
PORT |
Platform-provided port; takes precedence over CLOUDSCOPE_PORT. |
unset |
CLOUDSCOPE_STORAGE_SECRET |
NiceGUI storage secret. | cloudscope-dev-secret |
Boolean variables accept 1/0, true/false, yes/no, y/n, on/off (case-insensitive). Any other value raises ValueError at startup.
CloudScope ships with a Dockerfile and a docker-compose.yml. Docker Compose is the recommended way to run the server-mode app locally and on remote hosts; it bakes in all the env vars CloudScope needs (CLOUDSCOPE_REMOTE=1, CLOUDSCOPE_NATIVE=0, CLOUDSCOPE_HOST=0.0.0.0, PORT=8080) and wires up an ./example-data volume mount.
The live demo deployed on Oracle Cloud is available at https://cloudscope.mapmanager.net/.
Two services are defined in docker-compose.yml:
cloudscope— production-like container. Mounts./example-datainto/dataand pointsCLOUDSCOPE_SAMPLE_DATA_DIRat/data/sample-data.cloudscope-dev— same image but also bind-mounts./srcinto/app/srcso local edits are picked up inside the container.
Build and run the production-like container in the foreground:
docker compose up --build cloudscope
# then visit http://localhost:8080Build and run the dev container (live src/ mount) in the foreground:
docker compose up --build cloudscope-dev
# then visit http://localhost:8080Stop and remove the containers:
docker compose downFor long-running deployments, use detached mode with -d so the server keeps running after you log out of the host:
docker compose up --build -d cloudscopeUseful follow-ups on a remote host:
docker compose ps # show running services
docker compose logs -f cloudscope # tail logs
docker compose down # stop and removeThe compose file binds to 0.0.0.0:8080 inside the container and publishes it on host port 8080. On Oracle Cloud, make sure the VCN security list / network security group allows inbound TCP 8080 (or front the container with a reverse proxy / TLS terminator that maps 443 → 8080). The live demo at https://cloudscope.mapmanager.net/ runs from this same compose definition behind a TLS proxy.
The Dockerfile already sets the server-mode env vars and EXPOSE 8080, so a manual docker run is straightforward:
docker build -t cloudscope:latest .
docker run --rm -p 8080:8080 cloudscope:latest
# then visit http://localhost:8080Add a data mount when you want to load files from disk:
docker run --rm -p 8080:8080 -v "$PWD/example-data:/data" cloudscope:latestFor long-running deployments without Compose, use -d (detached) and a restart policy:
docker run -d --name cloudscope --restart unless-stopped -p 8080:8080 cloudscope:latestPrefer docker compose over raw docker run for anything beyond a quick smoke test — it keeps the env vars, ports, and volumes consistent with the deployed configuration.
This section is a short reminder for the local release workflow. It is intentionally small and practical.
Work on main is acceptable for solo development.
Pushes to main run:
- tests
- docs build/deploy
A normal development push does not create a GitHub Release.
Before tagging, update:
pyproject.tomlversion, for exampleversion = "0.1.0"CHANGELOG.md, moving completed notes from[Unreleased]into the release section
Example release section:
## [0.1.0] - 2026-06-10
### Added
- Added first official release workflow.Run this before creating a tag:
uv run python scripts/check_release.py v0.1.0The script checks:
- current branch is
main - working tree is clean
- tag format looks like
vX.Y.Z - local tag does not already exist
- origin tag does not already exist
pyproject.tomlversion matches the tagCHANGELOG.mdhas a section for the version
Use explicit git paths when committing release edits. Do not use git add ..
Example:
git add pyproject.toml CHANGELOG.md README-DEV.md scripts/check_release.py .github/workflows/release.yml .github/workflows/tests.yml .github/workflows/docs.yml
git commit -m "Prepare v0.1.0 release"
python scripts/check_release.py v0.1.0
git tag v0.1.0
git push origin main
git push origin v0.1.0Pushing the tag triggers .github/workflows/release.yml.
On tag pushes matching v*.*.*, the release workflow:
- installs Python 3.12 and uv
- validates tag/version/changelog consistency
- runs pytest
- builds MkDocs with
--strict - builds Python package artifacts with
uv build - creates a source archive from the tagged commit
- creates a zipped docs archive
- creates a GitHub Release
- uploads the package, source, and docs artifacts
Windows and macOS desktop artifacts are intentionally not part of this first release workflow.
Later phases can add:
- Windows build workflow on
windows-latest - unsigned Windows app zip attached to releases
- macOS app zip, likely still signed/notarized locally until Apple credentials are intentionally moved into GitHub Actions secrets