5864d78081
README documents minimal setup, run, and config for Linux and Windows. export.py bundles just the source into a zip (no .venv, caches, or API key files) for moving to another machine; it's pure stdlib run via 'uv run export.py', so it behaves identically on Linux and Windows with no shell required. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
64 lines
2.0 KiB
Python
64 lines
2.0 KiB
Python
"""Bundle Blurt's source into a portable zip — e.g. to move it to another
|
|
machine — leaving out the 200 MB+ .venv, caches, logs, and API key files.
|
|
|
|
Cross-platform and dependency-free (stdlib only, no shell, no git), so it runs
|
|
the same way on Linux and Windows:
|
|
|
|
uv run export.py [output.zip] # default: <tempdir>/blurt-export.zip
|
|
|
|
It only reads, never modifying this directory, so it is safe to run while Blurt
|
|
is recording. On the other machine: unzip, then `uv sync` to rebuild .venv for
|
|
that OS.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import zipfile
|
|
from pathlib import Path
|
|
|
|
PROJECT_DIR = Path(__file__).resolve().parent
|
|
ARCHIVE_ROOT = "blurt" # top-level folder inside the zip
|
|
|
|
# Never worth copying. API key files are excluded by name so secrets are never
|
|
# swept in, even though they sit right next to the source.
|
|
EXCLUDE_DIRS = {
|
|
".git", ".venv", "__pycache__", ".claude",
|
|
"dist", "build", ".ruff_cache", ".pytest_cache",
|
|
}
|
|
EXCLUDE_NAMES = {"api_key.txt", "openrouter_key.txt"}
|
|
EXCLUDE_SUFFIXES = {".pyc", ".pyo"}
|
|
|
|
|
|
def _included_files():
|
|
for root, dirs, files in os.walk(PROJECT_DIR):
|
|
dirs[:] = [
|
|
d for d in dirs
|
|
if d not in EXCLUDE_DIRS and not d.endswith(".egg-info")
|
|
]
|
|
for name in files:
|
|
if name in EXCLUDE_NAMES or Path(name).suffix in EXCLUDE_SUFFIXES:
|
|
continue
|
|
yield Path(root) / name
|
|
|
|
|
|
def main():
|
|
default_out = Path(tempfile.gettempdir()) / "blurt-export.zip"
|
|
out = (Path(sys.argv[1]) if len(sys.argv) > 1 else default_out).resolve()
|
|
out.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
count = 0
|
|
with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as zf:
|
|
for path in _included_files():
|
|
if path.resolve() == out: # don't zip the output into itself
|
|
continue
|
|
arcname = Path(ARCHIVE_ROOT) / path.relative_to(PROJECT_DIR)
|
|
zf.write(path, arcname)
|
|
count += 1
|
|
|
|
print(f"Wrote {out} ({count} files, {out.stat().st_size // 1024} KB)")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|