diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8c7f6a0..428228d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -27,7 +27,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - python-version: ["3.11", "3.12", "3.13"] + python-version: ["3.10", "3.11", "3.12", "3.13"] steps: # Pinned to immutable commit SHAs (not @v4 / @v5) so a compromised tag # cannot silently swap the underlying action code on this CI runner. diff --git a/launcher.py b/launcher.py index 79b5ebb..fbfefac 100644 --- a/launcher.py +++ b/launcher.py @@ -5,12 +5,18 @@ directly in-process. """ -import webview - from app import create_app def main(): + try: + import webview + except ImportError: + raise SystemExit( + "pywebview is not installed. Install the [desktop] extra, e.g.\n" + ' pip install -e ".[desktop]"' + ) from None + app = create_app() webview.create_window( "Cursor Chat Browser", diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5f03f9f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,95 @@ +[build-system] +requires = ["hatchling>=1.21"] +build-backend = "hatchling.build" + +[project] +name = "cppa-cursor-browser" +version = "0.1.0" +description = "Flask web application for browsing and exporting Cursor AI chat histories" +readme = "README.md" +license = { file = "LICENSE" } +authors = [{ name = "C++ Alliance", email = "admin@cppalliance.org" }] +requires-python = ">=3.10" + +# Runtime dependencies — bounded on both sides so CI resolves deterministically +# and breaking major releases are caught at install time rather than at runtime. +# Upper bounds are conservative: pin to current known-compatible major version. +# See also: requirements.txt (backward-compat alias; pyproject.toml is canonical). +dependencies = [ + "flask>=3.0,<4", + "fpdf2>=2.7,<3", + # Security floor: fpdf2 allows Pillow>=8.3.2, so 9.x can still be resolved. + # CVE-2024-28219 (buffer overflow) fixed in Pillow 10.3.0 — https://nvd.nist.gov/vuln/detail/CVE-2024-28219 + "pillow>=10.3.0", +] + +[project.optional-dependencies] +# Desktop launcher (pywebview pulls in heavy system libs — GTK/Qt on Linux — +# so it is intentionally excluded from the web-server and CI installs). +desktop = ["pywebview>=5.0,<6"] + +# Development tooling: testing + type checking. +dev = [ + "pytest>=8,<9", + "mypy>=1.10,<2", +] + +[project.scripts] +# Primary CLI: export Cursor chat histories to Markdown / zip. +# Usage: cursor-chat-export [--since all|last] [--out DIR] [--no-zip] [--help] +cursor-chat-export = "scripts.export:main" + +# Desktop launcher (requires the [desktop] extra to be installed). +cursor-chat-browser = "launcher:main" + +[project.urls] +Repository = "https://github.com/cppalliance/cppa-cursor-browser" +Issues = "https://github.com/cppalliance/cppa-cursor-browser/issues" + +# ── Build configuration ──────────────────────────────────────────────────────── +# Flat layout (no src/ directory): enumerate every importable package and the +# two top-level application modules so the installed wheel has the same import +# surface as the repo checkout. +[tool.hatch.build.targets.wheel] +include = [ + "api/", + "models/", + "scripts/", + "services/", + "utils/", + "templates/", + "static/", + "app.py", + "launcher.py", +] + +# sdist includes tests + ancillary files; wheel is runtime-only. +[tool.hatch.build.targets.sdist] +include = [ + "api/", + "models/", + "scripts/", + "services/", + "utils/", + "tests/", + "templates/", + "static/", + "app.py", + "launcher.py", + "requirements.txt", + "README.md", + "LICENSE", + "cursor-browser.spec", +] + +# ── Mypy ────────────────────────────────────────────────────────────────────── +# Mirrors the flags used in .github/workflows/tests.yml so local `mypy .` +# and CI produce identical results. +[tool.mypy] +ignore_missing_imports = true +no_strict_optional = true +pretty = true +# Exclude virtual-env and build artefact directories so that `mypy .` from the +# repo root matches CI behaviour (CI runs in a clean runner without a local venv). +# Anchored regexes — unanchored `venv/` would match any path segment containing "venv/". +exclude = ["^venv/", "^\\.venv/", "^build/", "^dist/"] diff --git a/requirements.txt b/requirements.txt index f15c00c..17e3882 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,10 @@ -flask>=3.0 -fpdf2>=2.7 -pywebview>=5.0 +# Backward-compatibility shim — mirrors [project.dependencies] in pyproject.toml. +# Keep version specifiers in sync when runtime deps change. +# +# Prefer: pip install -e . (installs the package + runtime deps) +# pip install -e ".[dev]" (+ pytest and mypy) +# pip install -e ".[desktop]" (+ pywebview for the GUI launcher) +flask>=3.0,<4 +fpdf2>=2.7,<3 +pillow>=10.3.0 +# pywebview is desktop-only — install with: pip install -e ".[desktop]"