diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json old mode 100644 new mode 100755 index 3ac321e..ca9a5d3 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -31,7 +31,7 @@ "ms-python.pylint" ], "settings": { - "python.defaultInterpreterPath": "/venv/bin/python" + "python.defaultInterpreterPath": "/app/.venv/bin/python" } } }, diff --git a/.devcontainer/post_start.sh b/.devcontainer/post_start.sh old mode 100644 new mode 100755 index 8bf87d1..3b1369c --- a/.devcontainer/post_start.sh +++ b/.devcontainer/post_start.sh @@ -16,7 +16,6 @@ echo "Setup completed for workspace: $workspace_folder_basename" -uv sync uvx prek install --install-hooks uv run tailwindcss -i ./src/clepsy/frontend/css/app.css -o ./static/app.css diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml old mode 100644 new mode 100755 index c0b7b90..fcf0ecf --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -46,8 +46,7 @@ jobs: uses: docker/build-push-action@v5 with: context: . - file: Dockerfile - target: production + file: Dockerfile.prod tags: clepsy:ci load: true push: false diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml old mode 100644 new mode 100755 index c20f0ef..dd46ea2 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -51,6 +51,7 @@ jobs: uses: docker/build-push-action@v5 with: context: . + file: ./Dockerfile.prod push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml old mode 100644 new mode 100755 index 3b9d6ae..45d8577 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,6 +13,7 @@ repos: hooks: - id: check-added-large-files args: [--maxkb=1024] + exclude: ^test_images/ - id: check-merge-conflict - id: debug-statements - id: end-of-file-fixer diff --git a/.python-version b/.python-version old mode 100644 new mode 100755 diff --git a/.vscode/extensions.json b/.vscode/extensions.json old mode 100644 new mode 100755 diff --git a/.vscode/launch.json b/.vscode/launch.json old mode 100644 new mode 100755 diff --git a/.vscode/settings.json b/.vscode/settings.json old mode 100644 new mode 100755 diff --git a/.vscode/tasks.json b/.vscode/tasks.json old mode 100644 new mode 100755 diff --git a/Caddyfile b/Caddyfile old mode 100644 new mode 100755 diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 579df27..0000000 --- a/Dockerfile +++ /dev/null @@ -1,109 +0,0 @@ -ARG APP_UID=1000 -ARG APP_GID=1000 - -# ---------- Base toolchain (root) ---------- -FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS base -ENV DEBIAN_FRONTEND=noninteractive \ - LANG=C.UTF-8 \ - LC_ALL=C.UTF-8 \ - LC_CTYPE=C.utf8 \ - UV_PROJECT_ENVIRONMENT="/venv" \ - UV_PYTHON_PREFERENCE=system \ - PATH="/venv/bin:$PATH" -ENV PYTHONDONTWRITEBYTECODE=1 -# Reproducible installs (no upgrade) -RUN --mount=type=cache,target=/var/cache/apt,id=apt-cache,sharing=locked \ - --mount=type=cache,target=/var/lib/apt,id=apt-lib,sharing=locked \ - mkdir -p /var/lib/apt/lists/partial && \ - apt-get update && \ - apt-get install --no-install-recommends -y \ - tzdata \ - curl build-essential sqlite3 libgl1 libglib2.0-0 libgl1-mesa-glx libglib2.0-0 gosu adduser && \ - rm -rf /var/lib/apt/lists/* -# Goose (db migrations) -RUN curl -fsSL https://raw.githubusercontent.com/pressly/goose/master/install.sh | sh - -WORKDIR /app - -## Deprecated static user stage removed; dynamic user handled at runtime - -# ---------- 1) Cache PROD deps ---------- -FROM base AS prod-deps -COPY pyproject.toml uv.lock ./ -RUN --mount=type=cache,target=/root/.cache/uv,id=uv-cache \ - uv sync --frozen --no-dev --no-install-project - -# ---------- 2) Cache DEV deps ---------- -FROM base AS dev-deps -COPY pyproject.toml uv.lock ./ -RUN --mount=type=cache,target=/root/.cache/uv,id=uv-cache \ - uv sync --frozen --group dev --no-install-project - -# ---------- 3) Builder (root) ---------- -FROM base AS builder -ENV ENVIRONMENT=dev -COPY --from=dev-deps /venv /venv -COPY src/ ./src/ -COPY migrations/ ./migrations/ -COPY baml_src/ ./baml_src/ -COPY static/ ./static/ -COPY pyproject.toml uv.lock ./ -RUN --mount=type=cache,target=/root/.cache/uv,id=uv-cache \ - uv sync --frozen --group dev - -# Assets / codegen -RUN --mount=type=cache,target=/root/.cache/uv,id=uv-cache \ - uv run tailwindcss -i ./src/clepsy/frontend/css/app.css -o ./static/app.css && \ - uv run baml-cli generate - -# prepare writable paths for the unprivileged dev user -## No user switch; runtime entrypoint will create and chown dynamically - - -# Optional compile checks -# RUN uv run python -m compileall src/clepsy - -# ---------- 4) Production runtime (non-root) ---------- -FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS production -ARG APP_UID -ARG APP_GID - -ENV DEBIAN_FRONTEND=noninteractive \ - LANG=C.UTF-8 \ - LC_ALL=C.UTF-8 \ - LC_CTYPE=C.utf8 \ - UV_PROJECT_ENVIRONMENT="/venv" \ - UV_PYTHON_PREFERENCE=system \ - PATH="/venv/bin:$PATH" \ - ENVIRONMENT=prod - -# Minimal runtime deps only (no sudo) -RUN --mount=type=cache,target=/var/cache/apt,id=apt-cache,sharing=locked \ - --mount=type=cache,target=/var/lib/apt,id=apt-lib,sharing=locked \ - mkdir -p /var/lib/apt/lists/partial && \ - apt-get update && \ - apt-get install --no-install-recommends -y \ - tzdata ca-certificates libgomp1 locales sqlite3 rsync gosu && \ - rm -rf /var/lib/apt/lists/* - -WORKDIR /app - -## Dynamic user creation & chown is handled at runtime inside entrypoint.sh - -# Bring in venv and app -COPY --from=prod-deps /venv /venv -COPY --from=base /usr/local/bin/goose /usr/local/bin/goose -COPY pyproject.toml uv.lock entrypoint.sh worker_entrypoint.sh ./ -COPY scripts/fix_permissions.sh ./scripts/ -COPY --from=builder /app/src/ ./src/ -COPY --from=builder /app/migrations/ ./migrations/ -COPY --from=builder /app/static/ ./static/ - -RUN chmod +x /app/entrypoint.sh /app/worker_entrypoint.sh /app/scripts/fix_permissions.sh - -# Install the project into the prebuilt venv without re-resolving deps -RUN --mount=type=cache,target=/root/.cache/uv,id=uv-cache \ - uv pip install --no-deps . - -ENV DEBIAN_FRONTEND= -ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100755 index 0000000..b3304f5 --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,20 @@ +FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim +ENV DEBIAN_FRONTEND=noninteractive \ + LC_CTYPE=C.utf8 \ + UV_PROJECT_ENVIRONMENT="/app/.venv" \ + UV_PYTHON_PREFERENCE=system \ + PATH="/app/.venv/bin:$PATH" \ + ENVIRONMENT=dev \ + PYTHONDONTWRITEBYTECODE=1 + +RUN --mount=type=cache,target=/var/cache/apt,id=apt-cache,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,id=apt-lib,sharing=locked \ + mkdir -p /var/lib/apt/lists/partial && \ + apt-get update && \ + apt-get install --no-install-recommends -y \ + tzdata curl build-essential sqlite3 gosu adduser libgl1 libglib2.0-0 && \ + rm -rf /var/lib/apt/lists/* + +RUN curl -fsSL https://raw.githubusercontent.com/pressly/goose/master/install.sh | sh + +WORKDIR /app diff --git a/Dockerfile.prod b/Dockerfile.prod new file mode 100755 index 0000000..1b07d01 --- /dev/null +++ b/Dockerfile.prod @@ -0,0 +1,49 @@ +FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS production + + +ENV DEBIAN_FRONTEND=noninteractive \ + LC_CTYPE=C.utf8 \ + UV_PROJECT_ENVIRONMENT="/venv" \ + UV_PYTHON_PREFERENCE=system \ + PATH="/venv/bin:$PATH" \ + UV_COMPILE_BYTECODE=0 \ + ENVIRONMENT=prod \ + UV_LINK_MODE=copy + +# Minimal runtime deps only (no sudo) +RUN --mount=type=cache,target=/var/cache/apt,id=apt-cache,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,id=apt-lib,sharing=locked \ + mkdir -p /var/lib/apt/lists/partial && \ + apt-get update && \ + apt-get install --no-install-recommends -y \ + rsync tzdata curl build-essential sqlite3 gosu adduser libgl1 libglib2.0-0 && \ + rm -rf /var/lib/apt/lists/* + +RUN curl -fsSL https://raw.githubusercontent.com/pressly/goose/master/install.sh | sh + + +WORKDIR /app + +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv sync --locked --no-install-project --no-dev + +COPY pyproject.toml uv.lock ./ +COPY src/ ./src/ +COPY migrations/ ./migrations/ +COPY static/ ./static/ +COPY scripts/ ./scripts/ +COPY baml_src/ ./baml_src/ + + +COPY scripts/ ./ +RUN chmod +x /app/scripts/entrypoint.sh /app/scripts/worker_entrypoint.sh /app/scripts/fix_permissions.sh + +# Install the project into the prebuilt venv without re-resolving deps +RUN --mount=type=cache,target=/root/.cache/uv,id=uv-cache \ + uv sync --locked --no-dev --compile-bytecode + +RUN baml-cli generate + +ENV DEBIAN_FRONTEND= diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/baml_src/aggregation.baml b/baml_src/aggregation.baml old mode 100644 new mode 100755 diff --git a/baml_src/clients.baml b/baml_src/clients.baml old mode 100644 new mode 100755 diff --git a/baml_src/desktop_check.baml b/baml_src/desktop_check.baml old mode 100644 new mode 100755 diff --git a/baml_src/desktop_check_validation.baml b/baml_src/desktop_check_validation.baml old mode 100644 new mode 100755 diff --git a/baml_src/entities.baml b/baml_src/entities.baml old mode 100644 new mode 100755 diff --git a/baml_src/generators.baml b/baml_src/generators.baml old mode 100644 new mode 100755 diff --git a/baml_src/interpret_image_ocr.baml b/baml_src/interpret_image_ocr.baml old mode 100644 new mode 100755 diff --git a/baml_src/isolated_timeline_validation.baml b/baml_src/isolated_timeline_validation.baml old mode 100644 new mode 100755 diff --git a/baml_src/manual_activity_reconciliation.baml b/baml_src/manual_activity_reconciliation.baml old mode 100644 new mode 100755 diff --git a/baml_src/productivity_classification.baml b/baml_src/productivity_classification.baml old mode 100644 new mode 100755 diff --git a/baml_src/qc_timeline.baml b/baml_src/qc_timeline.baml old mode 100644 new mode 100755 diff --git a/baml_src/retry_policies.baml b/baml_src/retry_policies.baml old mode 100644 new mode 100755 diff --git a/baml_src/sessions.baml b/baml_src/sessions.baml old mode 100644 new mode 100755 diff --git a/baml_src/stitch.baml b/baml_src/stitch.baml old mode 100644 new mode 100755 diff --git a/baml_src/tag_classification.baml b/baml_src/tag_classification.baml old mode 100644 new mode 100755 diff --git a/baml_src/test_model_config.baml b/baml_src/test_model_config.baml old mode 100644 new mode 100755 diff --git a/docker-compose.ci.override.yml b/docker-compose.ci.override.yml old mode 100644 new mode 100755 diff --git a/docker-compose.prod.gateway.yml b/docker-compose.prod.gateway.yml old mode 100644 new mode 100755 diff --git a/docker-compose.prod.local.yml b/docker-compose.prod.local.yml new file mode 100755 index 0000000..006ac13 --- /dev/null +++ b/docker-compose.prod.local.yml @@ -0,0 +1,18 @@ +# Local override for docker-compose.prod.yml +# Build production images locally instead of pulling from registry +# +# Usage: +# docker compose -f docker-compose.prod.yml -f docker-compose.prod.local.yml up --build + +services: + clepsy: + build: + context: . + dockerfile: Dockerfile.prod + image: clepsy:local + + dramatiq-worker: + build: + context: . + dockerfile: Dockerfile.prod + image: clepsy:local diff --git a/docker-compose.prod.monitoring.yml b/docker-compose.prod.monitoring.yml old mode 100644 new mode 100755 index ef33855..b256ba6 --- a/docker-compose.prod.monitoring.yml +++ b/docker-compose.prod.monitoring.yml @@ -1,7 +1,7 @@ services: clepsy: environment: - GRAFANA_ENABLED: "true" + MONITORING_ENABLED: "true" valkey-exporter: image: oliver006/redis_exporter:latest diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml old mode 100644 new mode 100755 index ba9dbbf..e95f221 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -18,7 +18,7 @@ services: - static_assets:/shared-static - caches:/var/lib/clepsy-caches - paddlex-cache:/root/.paddlex - entrypoint: ["/app/entrypoint.sh"] + entrypoint: ["/app/scripts/entrypoint.sh"] ports: - "${CLEPSY_PORT:-8000}:8000" environment: @@ -55,7 +55,7 @@ services: image: ghcr.io/samgalanakis/clepsy:latest container_name: dramatiq-worker networks: [web] - entrypoint: ["/app/worker_entrypoint.sh"] + entrypoint: ["/app/scripts/worker_entrypoint.sh"] environment: CLEPSY_UID: "${CLEPSY_UID:-1000}" CLEPSY_GID: "${CLEPSY_GID:-1000}" diff --git a/docker-compose.yml b/docker-compose.yml old mode 100644 new mode 100755 index 2c44ea8..ba8ad98 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,16 +7,17 @@ services: image: clepsy-dev build: context: . - dockerfile: Dockerfile - target: builder + dockerfile: Dockerfile.dev entrypoint: ["sh", "-c", "sleep infinity & wait"] environment: GOOSE_DRIVER: sqlite3 GOOSE_DBSTRING: /var/lib/clepsy/db.sqlite3 GOOSE_MIGRATION_DIR: ./migrations VALKEY_URL: redis://valkey:6379/0 - GRAFANA_ENABLED: "true" + MONITORING_ENABLED: "true" CLEPSY_MODE: dev + CLEPSY_GID : "0" + CLEPSY_UID : "0" volumes: - .:/app @@ -47,20 +48,23 @@ services: dramatiq-worker: image: clepsy-dev - entrypoint: ["sh", "-c", "sleep infinity & wait"] + entrypoint: ["/app/scripts/worker_entrypoint.sh"] environment: + CLEPSY_UID: "0" + CLEPSY_GID: "0" VALKEY_URL: redis://valkey:6379/0 dramatiq_prom_host: 0.0.0.0 dramatiq_prom_port: "9191" prometheus_multiproc_dir: /tmp/dramatiq-prometheus dramatiq_prom_db: /tmp/dramatiq-prometheus CLEPSY_MODE: dev - command: bash -lc "mkdir -p /tmp/dramatiq-prometheus && rm -rf /tmp/dramatiq-prometheus/* && uv run dramatiq clepsy.jobs.desktop clepsy.jobs.mobile clepsy.jobs.aggregation clepsy.jobs.sessions clepsy.jobs.goals --processes 2 --threads 4" volumes: - .:/app - db_data_dev:/var/lib/clepsy restart: unless-stopped depends_on: + clepsy: + condition: service_started valkey: condition: service_healthy diff --git a/docs/SETUP.md b/docs/SETUP.md old mode 100644 new mode 100755 diff --git a/docs/media/architecture.png b/docs/media/architecture.png old mode 100644 new mode 100755 diff --git a/docs/media/logo.svg b/docs/media/logo.svg old mode 100644 new mode 100755 diff --git a/migrations/00001_first.sql b/migrations/00001_first.sql old mode 100644 new mode 100755 diff --git a/ops/grafana/provisioning/dashboards/11835_Redis_Dashboard.json b/ops/grafana/provisioning/dashboards/11835_Redis_Dashboard.json old mode 100644 new mode 100755 diff --git a/ops/grafana/provisioning/dashboards/18739_FastAPI_Observability.json b/ops/grafana/provisioning/dashboards/18739_FastAPI_Observability.json old mode 100644 new mode 100755 diff --git a/ops/grafana/provisioning/dashboards/18993_Dramatiq.json b/ops/grafana/provisioning/dashboards/18993_Dramatiq.json old mode 100644 new mode 100755 diff --git a/ops/grafana/provisioning/dashboards/provider.yml b/ops/grafana/provisioning/dashboards/provider.yml old mode 100644 new mode 100755 diff --git a/ops/grafana/provisioning/datasources/prometheus.yml b/ops/grafana/provisioning/datasources/prometheus.yml old mode 100644 new mode 100755 diff --git a/ops/prometheus.yml b/ops/prometheus.yml old mode 100644 new mode 100755 diff --git a/pyproject.toml b/pyproject.toml old mode 100644 new mode 100755 index 4ea8d2a..80c9765 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ dependencies = [ "prometheus-fastapi-instrumentator>=7.1.0", "opencv-contrib-python-headless>=4.12.0.88", "paddleocr>=3.3.1", + "paddlepaddle>=3.2.1", ] [dependency-groups] diff --git a/entrypoint.sh b/scripts/entrypoint.sh old mode 100644 new mode 100755 similarity index 72% rename from entrypoint.sh rename to scripts/entrypoint.sh index 661283e..bfb1327 --- a/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -32,7 +32,15 @@ log "Refreshing shared static assets..." rsync -a --delete --no-perms --no-owner --no-group /app/static/ /shared-static/ log "[prod] Running migrations" -gosu "${PUID}:${PGID}" goose up || true +gosu "${PUID}:${PGID}" goose up + +# Initialize JWT secret if not exists (before starting workers) +JWT_SECRET_FILE="/var/lib/clepsy/jwt_secret.txt" +if [ ! -f "$JWT_SECRET_FILE" ] && [ -z "${JWT_SECRET:-}" ]; then + log "Generating JWT secret..." + gosu "${PUID}:${PGID}" uv run python -c "import os, binascii; print(binascii.hexlify(os.urandom(24)).decode())" > "$JWT_SECRET_FILE" +fi + log "[prod] Starting server" exec gosu "${PUID}:${PGID}" uvicorn clepsy.main:app \ --host 0.0.0.0 \ diff --git a/scripts/fix_permissions.sh b/scripts/fix_permissions.sh old mode 100644 new mode 100755 diff --git a/worker_entrypoint.sh b/scripts/worker_entrypoint.sh old mode 100644 new mode 100755 similarity index 100% rename from worker_entrypoint.sh rename to scripts/worker_entrypoint.sh diff --git a/src/__init__.py b/src/__init__.py old mode 100644 new mode 100755 diff --git a/src/clepsy/__init__.py b/src/clepsy/__init__.py old mode 100644 new mode 100755 diff --git a/src/clepsy/aggregator_worker.py b/src/clepsy/aggregator_worker.py old mode 100644 new mode 100755 diff --git a/src/clepsy/auth/auth.py b/src/clepsy/auth/auth.py old mode 100644 new mode 100755 diff --git a/src/clepsy/auth/auth_middleware.py b/src/clepsy/auth/auth_middleware.py old mode 100644 new mode 100755 index 4115ce6..7ed3cfe --- a/src/clepsy/auth/auth_middleware.py +++ b/src/clepsy/auth/auth_middleware.py @@ -107,9 +107,9 @@ async def check_authentication(self, request: Request, to_authenticate: bool): request.state.authenticated = True return True, decoded_token, None - except Exception as e: + except Exception: if to_authenticate: - logger.debug(f"Invalid token, {e}") + logger.exception("Invalid jwt token") return False, None, "invalid_token" async def __call__(self, request: Request, call_next): diff --git a/src/clepsy/bootstrap.py b/src/clepsy/bootstrap.py old mode 100644 new mode 100755 diff --git a/src/clepsy/central_cache.py b/src/clepsy/central_cache.py old mode 100644 new mode 100755 diff --git a/src/clepsy/config.py b/src/clepsy/config.py old mode 100644 new mode 100755 index c354730..8fdce66 --- a/src/clepsy/config.py +++ b/src/clepsy/config.py @@ -87,6 +87,20 @@ def init_master_key(path: Path) -> bytes: cache_dir = Path("/var/lib/clepsy-caches") +def load_jwt_secret(path: Path) -> str: + """Load JWT secret from file or environment variable.""" + env_secret = os.getenv("JWT_SECRET") + if env_secret: + logger.info("Using JWT secret from environment variable.") + return env_secret + + if path.is_file(): + logger.info(f"Loading JWT secret from file: {path}") + return path.read_text().strip() + + raise FileNotFoundError("JWT secret file not found and JWT_SECRET env var not set.") + + class Config(BaseSettings): log_file_path: Path = Path("/var/lib/clepsy/logs/app.log") master_key_file_path: Path = Path("/var/lib/clepsy/secret.key") @@ -102,9 +116,8 @@ class Config(BaseSettings): api_host: str = "0.0.0.0" max_desktop_screenshot_log_interval_seconds: int = 30 cache_dir: Path = cache_dir - jwt_secret: SecretStr = SecretStr( - os.getenv("JWT_SECRET", binascii.hexlify(os.urandom(24)).decode()) - ) + jwt_secret_file_path: Path = Path("/var/lib/clepsy/jwt_secret.txt") + jwt_secret: SecretStr = SecretStr(load_jwt_secret(jwt_secret_file_path)) jwt_algorithm: str = "HS256" environment: Literal["dev", "prod"] boundary_project_id: str | None = os.getenv("BOUNDARY_PROJECT_ID") @@ -129,6 +142,7 @@ class Config(BaseSettings): gliner_cache_dir: Path = cache_dir / "gliner" valkey_url: str ap_scheduler_sqlite_db_path: Path = Path("/var/lib/clepsy/apscheduler.sqlite3") + monitoring_enabled: bool = False @property def ap_scheduler_db_connection_string(self) -> str: diff --git a/src/clepsy/db/__init__.py b/src/clepsy/db/__init__.py old mode 100644 new mode 100755 diff --git a/src/clepsy/db/adapters.py b/src/clepsy/db/adapters.py old mode 100644 new mode 100755 diff --git a/src/clepsy/db/converters.py b/src/clepsy/db/converters.py old mode 100644 new mode 100755 diff --git a/src/clepsy/db/db.py b/src/clepsy/db/db.py old mode 100644 new mode 100755 diff --git a/src/clepsy/db/deps.py b/src/clepsy/db/deps.py old mode 100644 new mode 100755 diff --git a/src/clepsy/db/queries.py b/src/clepsy/db/queries.py old mode 100644 new mode 100755 diff --git a/src/clepsy/entities.py b/src/clepsy/entities.py old mode 100644 new mode 100755 diff --git a/src/clepsy/frontend/components/__init__.py b/src/clepsy/frontend/components/__init__.py old mode 100644 new mode 100755 index 9daa67b..7e42210 --- a/src/clepsy/frontend/components/__init__.py +++ b/src/clepsy/frontend/components/__init__.py @@ -7,7 +7,6 @@ from .sidebar import create_custom_sidebar from .slider import create_slider from .text import ( - create_markdown_editor, create_text_area, create_text_input, ) @@ -35,7 +34,6 @@ "create_base_page", "create_custom_sidebar", "create_text_area", - "create_markdown_editor", "create_text_input", "create_single_select", "create_multiselect", diff --git a/src/clepsy/frontend/components/base_page.py b/src/clepsy/frontend/components/base_page.py old mode 100644 new mode 100755 index 25c9811..25aa117 --- a/src/clepsy/frontend/components/base_page.py +++ b/src/clepsy/frontend/components/base_page.py @@ -80,7 +80,6 @@ def create_base_page( ] styles = [ - link(rel="stylesheet", href="https://unpkg.com/easymde/dist/easymde.min.css"), link(rel="stylesheet", href="/static/app.css", type="text/css"), ] diff --git a/src/clepsy/frontend/components/buttons.py b/src/clepsy/frontend/components/buttons.py old mode 100644 new mode 100755 diff --git a/src/clepsy/frontend/components/generic_modal.py b/src/clepsy/frontend/components/generic_modal.py old mode 100644 new mode 100755 diff --git a/src/clepsy/frontend/components/icons.py b/src/clepsy/frontend/components/icons.py old mode 100644 new mode 100755 diff --git a/src/clepsy/frontend/components/popover.py b/src/clepsy/frontend/components/popover.py old mode 100644 new mode 100755 diff --git a/src/clepsy/frontend/components/select.py b/src/clepsy/frontend/components/select.py old mode 100644 new mode 100755 diff --git a/src/clepsy/frontend/components/sidebar.py b/src/clepsy/frontend/components/sidebar.py old mode 100644 new mode 100755 diff --git a/src/clepsy/frontend/components/slider.py b/src/clepsy/frontend/components/slider.py old mode 100644 new mode 100755 diff --git a/src/clepsy/frontend/components/table.py b/src/clepsy/frontend/components/table.py old mode 100644 new mode 100755 diff --git a/src/clepsy/frontend/components/text.py b/src/clepsy/frontend/components/text.py old mode 100644 new mode 100755 index 48a7282..5bcb4d4 --- a/src/clepsy/frontend/components/text.py +++ b/src/clepsy/frontend/components/text.py @@ -1,6 +1,6 @@ from typing import Any, Optional -from htpy import Element, div, input as htpy_input, label, p, script, textarea +from htpy import Element, div, input as htpy_input, label, p, textarea def create_text_input( @@ -103,84 +103,3 @@ def create_text_area( content.append(p(class_="text-muted-foreground text-sm")[description]) return div(class_=container_classes)[content] - - -def create_markdown_editor( - element_id: str | None, - name: str, - title: str, - placeholder: str = "", - value: str = "", - required: bool = False, - readonly: bool = False, - disabled: bool = False, - x_model: str | None = None, - height: str = "300px", - toolbar_config: list | None = None, - base_classes_override: str | None = None, - extra_classes: str | None = None, - attrs: Optional[dict[str, Any]] = None, -) -> Element: - default_toolbar = [ - "bold", - "italic", - "heading", - "|", - "quote", - "unordered-list", - "ordered-list", - "|", - # "link", - # "image", - # "|", - "preview", - # "side-by-side", - # "fullscreen", - # "|", - # "guide", - ] - toolbar = toolbar_config or default_toolbar - - init_script = f""" - this.editor = new EasyMDE({{ - element: $refs.textarea, - placeholder: `{placeholder}`, - toolbar: {toolbar}, - forceSync: true, - status: false, - spellChecker: false, - hideIcons: ['guide'], - showIcons: ['code', 'table'], - minHeight: '{height}', - - renderingConfig: {{ codeSyntaxHighlighting: true }}, - {"readOnly: 'nocursor'," if disabled else ("readOnly: true," if readonly else "")} - }}); -""" - - outer_div = ( - base_classes_override - or "flex w-full flex-col gap-1 text-on-surface dark:text-on-surface-dark" - ) - return div( - class_=f"{outer_div} {extra_classes or ''}".strip(), - x_data=True, - x_init=init_script, - **(attrs or {}), - )[ - label(for_=element_id, class_="w-fit pl-0.5 text-sm")[title], - script(src="https://unpkg.com/easymde/dist/easymde.min.js"), - div(class_="markdown-editor-wrapper")[ - textarea( - id=element_id, - name=name, - x_model=x_model, - required=required, - readonly=readonly, - disabled=disabled, - value=value, - x_ref="textarea", - class_="prose hidden", - )[value], - ], - ] diff --git a/src/clepsy/frontend/components/time.py b/src/clepsy/frontend/components/time.py old mode 100644 new mode 100755 diff --git a/src/clepsy/frontend/components/time_duration_picker.py b/src/clepsy/frontend/components/time_duration_picker.py old mode 100644 new mode 100755 diff --git a/src/clepsy/frontend/components/time_nav.py b/src/clepsy/frontend/components/time_nav.py old mode 100644 new mode 100755 diff --git a/src/clepsy/frontend/components/toasts.py b/src/clepsy/frontend/components/toasts.py old mode 100644 new mode 100755 diff --git a/src/clepsy/frontend/components/tooltips.py b/src/clepsy/frontend/components/tooltips.py old mode 100644 new mode 100755 diff --git a/src/clepsy/frontend/components/top_bar.py b/src/clepsy/frontend/components/top_bar.py old mode 100644 new mode 100755 diff --git a/src/clepsy/frontend/css/app.css b/src/clepsy/frontend/css/app.css old mode 100644 new mode 100755 index 0dd7ce3..c889e87 --- a/src/clepsy/frontend/css/app.css +++ b/src/clepsy/frontend/css/app.css @@ -1,6 +1,5 @@ @import "tailwindcss"; @import "./themes.css"; -@import "./easymde_override.css"; @import "./multi_select.css"; @import "./flatpickr.css"; diff --git a/src/clepsy/frontend/css/easymde_override.css b/src/clepsy/frontend/css/easymde_override.css deleted file mode 100644 index 836751c..0000000 --- a/src/clepsy/frontend/css/easymde_override.css +++ /dev/null @@ -1,52 +0,0 @@ - - -:root { - --mde-bg: var(--color-card); - --mde-fg: var(--color-card-foreground); - --mde-border: var(--color-border); - - --mde-toolbar-bg: var(--color-card); - --mde-toolbar-fg: var(--color-muted-foreground); - --mde-toolbar-fg-hover: var(--color-foreground); - - --mde-preview-bg: var(--color-popover); - --mde-preview-fg: var(--color-popover-foreground); - - --mde-gutter-bg: var(--color-muted); - --mde-gutter-fg: var(--color-muted-foreground); - - --mde-cursor: var(--color-foreground); - --mde-selection: color-mix(in oklch, var(--color-primary) 22%, transparent); -} - -/* 2) Apply to SimpleMDE/EasyMDE UI */ -.editor-toolbar { background: var(--mde-toolbar-bg); border: 1px solid var(--mde-border); border-bottom: 0; } -.editor-toolbar a { color: var(--mde-toolbar-fg); } -.editor-toolbar a:hover, .editor-toolbar a.active { color: var(--mde-toolbar-fg-hover); border-color: var(--mde-border); } -.editor-statusbar { color: var(--color-muted-foreground); border-top: 1px solid var(--mde-border); } - -.CodeMirror { background: var(--mde-bg); color: var(--mde-fg); border: 1px solid var(--mde-border); } -.CodeMirror-gutters { background: var(--mde-gutter-bg); border-right: 1px solid var(--mde-border); } -.CodeMirror-linenumber { color: var(--mde-gutter-fg); } -.CodeMirror-cursor { border-left-color: var(--mde-cursor); } -.CodeMirror-selected { background: var(--mde-selection) !important; } - -.editor-preview, .editor-preview-side { background: var(--mde-preview-bg); color: var(--mde-preview-fg); } -.editor-preview pre, .editor-preview code, -.editor-preview-side pre, .editor-preview-side code { background: var(--color-muted); color: var(--mde-fg); } - -/* 3) Token colors (default CM5 theme scope) */ -.cm-s-default .cm-header { color: var(--color-primary); } -.cm-s-default .cm-link { color: var(--color-accent); } -.cm-s-default .cm-string { color: var(--color-chart-3); } -.cm-s-default .cm-number { color: var(--color-chart-2); } -.cm-s-default .cm-keyword { color: var(--color-chart-1); } -.cm-s-default .cm-comment { color: var(--color-muted-foreground); } -.cm-s-default .cm-atom { color: var(--color-chart-4); } -.cm-s-default .cm-tag, -.cm-s-default .cm-attribute { color: var(--color-chart-5); } - -/* (optional) respect your mono font */ -.CodeMirror, .editor-preview code, .editor-preview pre { - font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); -} diff --git a/src/clepsy/frontend/css/flatpickr.css b/src/clepsy/frontend/css/flatpickr.css old mode 100644 new mode 100755 diff --git a/src/clepsy/frontend/css/multi_select.css b/src/clepsy/frontend/css/multi_select.css old mode 100644 new mode 100755 diff --git a/src/clepsy/frontend/css/themes.css b/src/clepsy/frontend/css/themes.css old mode 100644 new mode 100755 diff --git a/src/clepsy/human_readable_pw.py b/src/clepsy/human_readable_pw.py old mode 100644 new mode 100755 diff --git a/src/clepsy/infra/dramatiq_setup.py b/src/clepsy/infra/dramatiq_setup.py old mode 100644 new mode 100755 diff --git a/src/clepsy/infra/streams.py b/src/clepsy/infra/streams.py old mode 100644 new mode 100755 diff --git a/src/clepsy/infra/valkey_client.py b/src/clepsy/infra/valkey_client.py old mode 100644 new mode 100755 diff --git a/src/clepsy/jobs/actor_init.py b/src/clepsy/jobs/actor_init.py old mode 100644 new mode 100755 diff --git a/src/clepsy/jobs/aggregation.py b/src/clepsy/jobs/aggregation.py old mode 100644 new mode 100755 diff --git a/src/clepsy/jobs/desktop.py b/src/clepsy/jobs/desktop.py old mode 100644 new mode 100755 diff --git a/src/clepsy/jobs/goals.py b/src/clepsy/jobs/goals.py old mode 100644 new mode 100755 diff --git a/src/clepsy/jobs/mobile.py b/src/clepsy/jobs/mobile.py old mode 100644 new mode 100755 diff --git a/src/clepsy/jobs/sessions.py b/src/clepsy/jobs/sessions.py old mode 100644 new mode 100755 diff --git a/src/clepsy/llm.py b/src/clepsy/llm.py old mode 100644 new mode 100755 diff --git a/src/clepsy/main.py b/src/clepsy/main.py old mode 100644 new mode 100755 index 7f917d7..1b9f1de --- a/src/clepsy/main.py +++ b/src/clepsy/main.py @@ -23,6 +23,8 @@ from clepsy.modules.sources.router import router as sources_router from clepsy.modules.tags.router import router as tags_router from clepsy.modules.user_settings.router import router as user_settings_router +from prometheus_fastapi_instrumentator import Instrumentator +from clepsy.config import config @asynccontextmanager @@ -43,6 +45,10 @@ async def lifespan(app_: FastAPI): app = FastAPI(title="Clepsy backend", lifespan=lifespan) +if config.monitoring_enabled: + Instrumentator().instrument(app).expose(app) + + mimetypes.add_type("application/javascript", ".js") app.mount("/static", StaticFiles(directory="static"), name="static") diff --git a/src/clepsy/modules/__init__.py b/src/clepsy/modules/__init__.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/account_creation/page.py b/src/clepsy/modules/account_creation/page.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/account_creation/router.py b/src/clepsy/modules/account_creation/router.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/activities/add_router.py b/src/clepsy/modules/activities/add_router.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/activities/components/activity_edit_form.py b/src/clepsy/modules/activities/components/activity_edit_form.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/activities/components/event_editor.py b/src/clepsy/modules/activities/components/event_editor.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/activities/edit_router.py b/src/clepsy/modules/activities/edit_router.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/activities/json_serializers.py b/src/clepsy/modules/activities/json_serializers.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/activities/router.py b/src/clepsy/modules/activities/router.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/aggregator/desktop_source/router.py b/src/clepsy/modules/aggregator/desktop_source/router.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/aggregator/desktop_source/worker.py b/src/clepsy/modules/aggregator/desktop_source/worker.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/aggregator/mobile_source/router.py b/src/clepsy/modules/aggregator/mobile_source/router.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/aggregator/programmatic_timeline_validation.py b/src/clepsy/modules/aggregator/programmatic_timeline_validation.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/aggregator/router.py b/src/clepsy/modules/aggregator/router.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/aggregator/stitching.py b/src/clepsy/modules/aggregator/stitching.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/goals/__init__.py b/src/clepsy/modules/goals/__init__.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/goals/calculate_goals.py b/src/clepsy/modules/goals/calculate_goals.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/goals/pages/__init__.py b/src/clepsy/modules/goals/pages/__init__.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/goals/pages/create.py b/src/clepsy/modules/goals/pages/create.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/goals/pages/edit.py b/src/clepsy/modules/goals/pages/edit.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/goals/pages/home.py b/src/clepsy/modules/goals/pages/home.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/goals/pages/utils.py b/src/clepsy/modules/goals/pages/utils.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/goals/router.py b/src/clepsy/modules/goals/router.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/home/components/__init__.py b/src/clepsy/modules/home/components/__init__.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/home/components/activity_edit_form.py b/src/clepsy/modules/home/components/activity_edit_form.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/home/components/graphs_and_controls.py b/src/clepsy/modules/home/components/graphs_and_controls.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/home/components/unified_diagram.py b/src/clepsy/modules/home/components/unified_diagram.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/home/page.py b/src/clepsy/modules/home/page.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/home/router.py b/src/clepsy/modules/home/router.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/insights/components/__init__.py b/src/clepsy/modules/insights/components/__init__.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/insights/components/focus_sessions.py b/src/clepsy/modules/insights/components/focus_sessions.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/insights/components/productivity_donut.py b/src/clepsy/modules/insights/components/productivity_donut.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/insights/components/productivity_time_slice.py b/src/clepsy/modules/insights/components/productivity_time_slice.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/insights/components/tag_transition_chord.py b/src/clepsy/modules/insights/components/tag_transition_chord.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/insights/components/time_spent_per_tag.py b/src/clepsy/modules/insights/components/time_spent_per_tag.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/insights/pages/__init__.py b/src/clepsy/modules/insights/pages/__init__.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/insights/pages/home.py b/src/clepsy/modules/insights/pages/home.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/insights/router.py b/src/clepsy/modules/insights/router.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/login/page.py b/src/clepsy/modules/login/page.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/login/router.py b/src/clepsy/modules/login/router.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/ocr/ocr.py b/src/clepsy/modules/ocr/ocr.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/pii/pii.py b/src/clepsy/modules/pii/pii.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/pii/sanitize.py b/src/clepsy/modules/pii/sanitize.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/sessions/tasks.py b/src/clepsy/modules/sessions/tasks.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/sources/router.py b/src/clepsy/modules/sources/router.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/tags/router.py b/src/clepsy/modules/tags/router.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/user_settings/general/__init__.py b/src/clepsy/modules/user_settings/general/__init__.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/user_settings/general/page.py b/src/clepsy/modules/user_settings/general/page.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/user_settings/general/router.py b/src/clepsy/modules/user_settings/general/router.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/user_settings/llm/component.py b/src/clepsy/modules/user_settings/llm/component.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/user_settings/llm_models/__init__.py b/src/clepsy/modules/user_settings/llm_models/__init__.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/user_settings/llm_models/page.py b/src/clepsy/modules/user_settings/llm_models/page.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/user_settings/llm_models/router.py b/src/clepsy/modules/user_settings/llm_models/router.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/user_settings/page.py b/src/clepsy/modules/user_settings/page.py old mode 100644 new mode 100755 index dc7861b..41f0e50 --- a/src/clepsy/modules/user_settings/page.py +++ b/src/clepsy/modules/user_settings/page.py @@ -25,9 +25,9 @@ common_timezones_list, create_button, create_generic_modal, - create_markdown_editor, create_single_select, create_standard_content, + create_text_area, create_text_input, ) @@ -233,13 +233,13 @@ async def create_productivity_page(user_settings: UserSettings) -> Element: form_inner_content = div(class_="card p-6 space-y-6")[ h2(class_="text-lg font-semibold text-on-surface-strong mb-4")["Productivity"], - create_markdown_editor( + create_text_area( element_id="productivity-prompt", name="productivity_prompt", value=user_settings.productivity_prompt or "", placeholder="e.g., How productive was I...", title="Productivity Level Prompt", - height="150px", + rows=6, ), ] diff --git a/src/clepsy/modules/user_settings/password/__init__.py b/src/clepsy/modules/user_settings/password/__init__.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/user_settings/password/page.py b/src/clepsy/modules/user_settings/password/page.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/user_settings/password/router.py b/src/clepsy/modules/user_settings/password/router.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/user_settings/productivity/__init__.py b/src/clepsy/modules/user_settings/productivity/__init__.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/user_settings/productivity/page.py b/src/clepsy/modules/user_settings/productivity/page.py old mode 100644 new mode 100755 index 88bd3d0..6b6ca4e --- a/src/clepsy/modules/user_settings/productivity/page.py +++ b/src/clepsy/modules/user_settings/productivity/page.py @@ -3,21 +3,21 @@ from clepsy.entities import UserSettings from clepsy.frontend.components import ( create_button, - create_markdown_editor, create_standard_content, + create_text_area, ) async def create_productivity_page(user_settings: UserSettings) -> Element: form_inner_content = div(class_="card p-6 space-y-6")[ h2(class_="text-lg font-semibold text-on-surface-strong mb-4")["Productivity"], - create_markdown_editor( + create_text_area( element_id="productivity-prompt", name="productivity_prompt", value=user_settings.productivity_prompt or "", placeholder="e.g., How productive was I...", title="Productivity Level Prompt", - height="150px", + rows=6, ), ] form_with_buttons = form( diff --git a/src/clepsy/modules/user_settings/productivity/router.py b/src/clepsy/modules/user_settings/productivity/router.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/user_settings/router.py b/src/clepsy/modules/user_settings/router.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/user_settings/sources/__init__.py b/src/clepsy/modules/user_settings/sources/__init__.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/user_settings/sources/page.py b/src/clepsy/modules/user_settings/sources/page.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/user_settings/sources/router.py b/src/clepsy/modules/user_settings/sources/router.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/user_settings/tags/__init__.py b/src/clepsy/modules/user_settings/tags/__init__.py old mode 100644 new mode 100755 diff --git a/src/clepsy/modules/user_settings/tags/component.py b/src/clepsy/modules/user_settings/tags/component.py old mode 100644 new mode 100755 diff --git a/src/clepsy/scheduling.py b/src/clepsy/scheduling.py old mode 100644 new mode 100755 index f822bad..281b6e0 --- a/src/clepsy/scheduling.py +++ b/src/clepsy/scheduling.py @@ -62,6 +62,18 @@ async def init_schedules(sched: AsyncScheduler) -> None: Uses a Redis lock to ensure only one worker initializes schedules. """ + # Register task callables on ALL workers (must be done locally on each worker) + await sched.configure_task( # type: ignore[attr-defined] + func_or_task_id=TASK_ID_SESSIONIZATION, func=run_sessionization_job.send + ) + await sched.configure_task( # type: ignore[attr-defined] + func_or_task_id=TASK_ID_AGGREGATION, func=aggregate_window.send + ) + await sched.configure_task( # type: ignore[attr-defined] + func_or_task_id=TASK_ID_GOAL_PREV_FULL_PERIOD, + func=run_update_previous_full_period_result_job.send, + ) + conn = get_connection() lock_key = "scheduler:init_lock" lock_acquired = False @@ -76,18 +88,7 @@ async def init_schedules(sched: AsyncScheduler) -> None: logger.info("Acquired scheduler init lock, registering schedules...") - # Register resolvable task IDs to avoid pickling callables - await sched.configure_task( # type: ignore[attr-defined] - func_or_task_id=TASK_ID_SESSIONIZATION, func=run_sessionization_job.send - ) - await sched.configure_task( # type: ignore[attr-defined] - func_or_task_id=TASK_ID_AGGREGATION, func=aggregate_window.send - ) - await sched.configure_task( # type: ignore[attr-defined] - func_or_task_id=TASK_ID_GOAL_PREV_FULL_PERIOD, - func=run_update_previous_full_period_result_job.send, - ) - + # Only the lock holder creates the schedules in the database await sched.add_schedule( # type: ignore[attr-defined] TASK_ID_SESSIONIZATION, trigger=IntervalTrigger( diff --git a/src/clepsy/utils.py b/src/clepsy/utils.py old mode 100644 new mode 100755 diff --git a/static/alpinejs.3.14.7.min.js b/static/alpinejs.3.14.7.min.js old mode 100644 new mode 100755 diff --git a/static/custom_scripts/app.js b/static/custom_scripts/app.js old mode 100644 new mode 100755 diff --git a/static/custom_scripts/chart_utils.js b/static/custom_scripts/chart_utils.js old mode 100644 new mode 100755 diff --git a/static/custom_scripts/clepsy_chart.js b/static/custom_scripts/clepsy_chart.js old mode 100644 new mode 100755 diff --git a/static/custom_scripts/dark_light_mode.js b/static/custom_scripts/dark_light_mode.js old mode 100644 new mode 100755 diff --git a/static/custom_scripts/date_range.js b/static/custom_scripts/date_range.js old mode 100644 new mode 100755 diff --git a/static/custom_scripts/insights_focus_sessions.js b/static/custom_scripts/insights_focus_sessions.js old mode 100644 new mode 100755 diff --git a/static/custom_scripts/insights_productivity_donut.js b/static/custom_scripts/insights_productivity_donut.js old mode 100644 new mode 100755 diff --git a/static/custom_scripts/insights_productivity_time_slice.js b/static/custom_scripts/insights_productivity_time_slice.js old mode 100644 new mode 100755 diff --git a/static/custom_scripts/insights_tag_transition_chord.js b/static/custom_scripts/insights_tag_transition_chord.js old mode 100644 new mode 100755 diff --git a/static/custom_scripts/insights_time_spent_per_tag.js b/static/custom_scripts/insights_time_spent_per_tag.js old mode 100644 new mode 100755 diff --git a/static/custom_scripts/theme_switcher.js b/static/custom_scripts/theme_switcher.js old mode 100644 new mode 100755 diff --git a/static/custom_scripts/time_duration_picker.js b/static/custom_scripts/time_duration_picker.js old mode 100644 new mode 100755 diff --git a/static/custom_scripts/unified_diagram.js b/static/custom_scripts/unified_diagram.js old mode 100644 new mode 100755 diff --git a/static/custom_scripts/utils.js b/static/custom_scripts/utils.js old mode 100644 new mode 100755 diff --git a/static/d3-sankey.min.js b/static/d3-sankey.min.js old mode 100644 new mode 100755 diff --git a/static/d3.v7.min.js b/static/d3.v7.min.js old mode 100644 new mode 100755 diff --git a/static/favicon.svg b/static/favicon.svg old mode 100644 new mode 100755 diff --git a/static/htmx.min.js b/static/htmx.min.js old mode 100644 new mode 100755 diff --git a/test_images/Adobe-Photoshop-Screenshot.png b/test_images/Adobe-Photoshop-Screenshot.png old mode 100644 new mode 100755 diff --git "a/test_images/El_Perell\303\263_-_Old_olive_tree.jpg" "b/test_images/El_Perell\303\263_-_Old_olive_tree.jpg" old mode 100644 new mode 100755 diff --git a/test_images/calculator.png b/test_images/calculator.png old mode 100644 new mode 100755 diff --git a/test_images/discord-2880x1620-8b18742c6a01.jpg b/test_images/discord-2880x1620-8b18742c6a01.jpg old mode 100644 new mode 100755 diff --git a/test_images/docker_desktop.png b/test_images/docker_desktop.png old mode 100644 new mode 100755 diff --git a/test_images/file_explorer.png b/test_images/file_explorer.png old mode 100644 new mode 100755 diff --git a/test_images/forticlient_vpn.png b/test_images/forticlient_vpn.png old mode 100644 new mode 100755 diff --git a/test_images/league_client.png b/test_images/league_client.png old mode 100644 new mode 100755 diff --git a/test_images/nordvpn.png b/test_images/nordvpn.png old mode 100644 new mode 100755 diff --git a/test_images/notepad_elephants.png b/test_images/notepad_elephants.png old mode 100644 new mode 100755 diff --git a/test_images/paint.png b/test_images/paint.png old mode 100644 new mode 100755 diff --git a/test_images/razer.png b/test_images/razer.png old mode 100644 new mode 100755 diff --git a/test_images/spotify.png b/test_images/spotify.png old mode 100644 new mode 100755 diff --git a/test_images/steam.png b/test_images/steam.png old mode 100644 new mode 100755 diff --git a/test_images/steam_popup.png b/test_images/steam_popup.png old mode 100644 new mode 100755 diff --git a/test_images/task_manager.png b/test_images/task_manager.png old mode 100644 new mode 100755 diff --git a/test_images/vlc_blank.png b/test_images/vlc_blank.png old mode 100644 new mode 100755 diff --git a/test_images/vscode.png b/test_images/vscode.png old mode 100644 new mode 100755 diff --git a/test_images/vscode_coding.png b/test_images/vscode_coding.png old mode 100644 new mode 100755 diff --git a/test_images/whatsapp-windows.png b/test_images/whatsapp-windows.png old mode 100644 new mode 100755 diff --git a/test_images/windows_store.png b/test_images/windows_store.png old mode 100644 new mode 100755 diff --git a/tests/__init__.py b/tests/__init__.py old mode 100644 new mode 100755 diff --git a/tests/conftest.py b/tests/conftest.py old mode 100644 new mode 100755 index 8ed018f..2f6cf52 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,5 @@ +import os + import pytest from clepsy.entities import ( @@ -8,6 +10,10 @@ from .llm_configs import gemini_2_5 +# Set test environment variables before config is loaded +os.environ.setdefault("JWT_SECRET", "test-jwt-secret-for-tests") + + @pytest.fixture def mock_llm_config(): """Mock LLM configuration for testing - uses real API key from environment""" diff --git a/tests/llm_configs.py b/tests/llm_configs.py old mode 100644 new mode 100755 diff --git a/tests/scenarios/__init__.py b/tests/scenarios/__init__.py old mode 100644 new mode 100755 diff --git a/tests/scenarios/instances/__init__.py b/tests/scenarios/instances/__init__.py old mode 100644 new mode 100755 diff --git a/tests/scenarios/instances/casual_web_browsing_and_social_media.py b/tests/scenarios/instances/casual_web_browsing_and_social_media.py old mode 100644 new mode 100755 diff --git a/tests/scenarios/instances/coding_then_afk.py b/tests/scenarios/instances/coding_then_afk.py old mode 100644 new mode 100755 diff --git a/tests/scenarios/instances/context_switching.py b/tests/scenarios/instances/context_switching.py old mode 100644 new mode 100755 diff --git a/tests/scenarios/instances/data_analysis_and_email.py b/tests/scenarios/instances/data_analysis_and_email.py old mode 100644 new mode 100755 diff --git a/tests/scenarios/instances/interrupted_content_creation.py b/tests/scenarios/instances/interrupted_content_creation.py old mode 100644 new mode 100755 diff --git a/tests/scenarios/instances/interrupted_design_work.py b/tests/scenarios/instances/interrupted_design_work.py old mode 100644 new mode 100755 diff --git a/tests/scenarios/instances/llm_stitching_match_writing.py b/tests/scenarios/instances/llm_stitching_match_writing.py old mode 100644 new mode 100755 diff --git a/tests/scenarios/instances/llm_stitching_no_match.py b/tests/scenarios/instances/llm_stitching_no_match.py old mode 100644 new mode 100755 diff --git a/tests/scenarios/instances/llm_stitching_required.py b/tests/scenarios/instances/llm_stitching_required.py old mode 100644 new mode 100755 diff --git a/tests/scenarios/instances/meeting_then_coding.py b/tests/scenarios/instances/meeting_then_coding.py old mode 100644 new mode 100755 diff --git a/tests/scenarios/instances/online_shopping_with_research.py b/tests/scenarios/instances/online_shopping_with_research.py old mode 100644 new mode 100755 diff --git a/tests/scenarios/instances/photo_editing_and_organization.py b/tests/scenarios/instances/photo_editing_and_organization.py old mode 100644 new mode 100755 diff --git a/tests/scenarios/instances/project_management_and_communication.py b/tests/scenarios/instances/project_management_and_communication.py old mode 100644 new mode 100755 diff --git a/tests/scenarios/instances/simple_coding.py b/tests/scenarios/instances/simple_coding.py old mode 100644 new mode 100755 diff --git a/tests/scenarios/make_all_scenarios.py b/tests/scenarios/make_all_scenarios.py old mode 100644 new mode 100755 diff --git a/tests/scenarios/types.py b/tests/scenarios/types.py old mode 100644 new mode 100755 diff --git a/tests/sessions/__init__.py b/tests/sessions/__init__.py old mode 100644 new mode 100755 diff --git a/tests/sessions/test_build_island_arrays.py b/tests/sessions/test_build_island_arrays.py old mode 100644 new mode 100755 diff --git a/tests/sessions/test_extract_islands.py b/tests/sessions/test_extract_islands.py old mode 100644 new mode 100755 diff --git a/tests/sessions/test_pick_best_window.py b/tests/sessions/test_pick_best_window.py old mode 100644 new mode 100755 diff --git a/tests/sessions/test_validate_and_select.py b/tests/sessions/test_validate_and_select.py old mode 100644 new mode 100755 diff --git a/tests/test_date_shenanigans.py b/tests/test_date_shenanigans.py old mode 100644 new mode 100755 diff --git a/tests/test_desktop_check_scenarios.py b/tests/test_desktop_check_scenarios.py old mode 100644 new mode 100755 diff --git a/tests/test_desktop_worker_ocr.py b/tests/test_desktop_worker_ocr.py old mode 100644 new mode 100755 diff --git a/tests/test_human_readable_pw.py b/tests/test_human_readable_pw.py old mode 100644 new mode 100755 diff --git a/tests/test_isolated_timeline_generation.py b/tests/test_isolated_timeline_generation.py old mode 100644 new mode 100755 diff --git a/tests/test_llm_models_validation.py b/tests/test_llm_models_validation.py old mode 100644 new mode 100755 diff --git a/tests/test_manual_reconciliation.py b/tests/test_manual_reconciliation.py old mode 100644 new mode 100755 diff --git a/tests/test_pii.py b/tests/test_pii.py old mode 100644 new mode 100755 diff --git a/tests/test_stitching.py b/tests/test_stitching.py old mode 100644 new mode 100755 diff --git a/tests/test_tag_classification.py b/tests/test_tag_classification.py old mode 100644 new mode 100755 diff --git a/tests/test_utils.py b/tests/test_utils.py old mode 100644 new mode 100755 diff --git a/uv.lock b/uv.lock old mode 100644 new mode 100755 index 975bcf8..f6dd4d5 --- a/uv.lock +++ b/uv.lock @@ -367,6 +367,7 @@ dependencies = [ { name = "openai" }, { name = "opencv-contrib-python-headless" }, { name = "paddleocr" }, + { name = "paddlepaddle" }, { name = "pillow" }, { name = "prometheus-client" }, { name = "prometheus-fastapi-instrumentator" }, @@ -422,6 +423,7 @@ requires-dist = [ { name = "openai", specifier = ">=1.61.1" }, { name = "opencv-contrib-python-headless", specifier = ">=4.12.0.88" }, { name = "paddleocr", specifier = ">=3.3.1" }, + { name = "paddlepaddle", specifier = ">=3.2.1" }, { name = "pillow", specifier = ">=11.0.0" }, { name = "prometheus-client", specifier = ">=0.20.0" }, { name = "prometheus-fastapi-instrumentator", specifier = ">=7.1.0" }, @@ -1479,6 +1481,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/44/d0/75057aac72cb7134b430a7c4c715af8a0cc68fe17c69889d64f33a5e311a/opencv_contrib_python_headless-4.12.0.88-cp37-abi3-win_amd64.whl", hash = "sha256:c57e32812fea2a542bb220088fb3ce8a210fe114c9454d1c9e8cd162e1a1fde8", size = 45190148, upload-time = "2025-07-07T09:18:15.15Z" }, ] +[[package]] +name = "opt-einsum" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/bf/9257e53a0e7715bc1127e15063e831f076723c6cd60985333a1c18878fb8/opt_einsum-3.3.0.tar.gz", hash = "sha256:59f6475f77bbc37dcf7cd748519c0ec60722e91e63ca114e68821c0c54a46549", size = 73951, upload-time = "2020-07-19T22:40:32.141Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/19/404708a7e54ad2798907210462fd950c3442ea51acc8790f3da48d2bee8b/opt_einsum-3.3.0-py3-none-any.whl", hash = "sha256:2455e59e3947d3c275477df7f5205b30635e266fe6dc300e3d9f9646bfcea147", size = 65486, upload-time = "2020-07-19T22:40:30.301Z" }, +] + [[package]] name = "packaging" version = "25.0" @@ -1503,6 +1517,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/49/f9/a558a2c506674c0fed9a6ecd03717e88148398fdde9b7c7fa94e1ef4784f/paddleocr-3.3.1-py3-none-any.whl", hash = "sha256:b9401ad605cc29840458b63173ea91c01bab548323f96f0b8f0d09a5aad4f9cb", size = 86703, upload-time = "2025-10-29T11:49:59.226Z" }, ] +[[package]] +name = "paddlepaddle" +version = "3.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "networkx" }, + { name = "numpy" }, + { name = "opt-einsum" }, + { name = "pillow" }, + { name = "protobuf" }, + { name = "safetensors" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/d8/81076b4029e119664eb29a8592e74fe0d5203298055dd4fda4addd4f61ae/paddlepaddle-3.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5190d3680bf0678a337f710a76f3235833c449a274cac38a9d60cab2d8fb0acf", size = 99491247, upload-time = "2025-10-30T13:35:06.146Z" }, + { url = "https://files.pythonhosted.org/packages/e0/de/9debc4622c60ba6a2cd54f613befa88d70f598ba0eac688aa85f1ef6e84a/paddlepaddle-3.2.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:71cc81e8dfc5a42722ec6680ed2eaf5695128f4bf8f4072c30a1f3f37ee89900", size = 189263031, upload-time = "2025-10-31T03:12:48.465Z" }, + { url = "https://files.pythonhosted.org/packages/97/63/6b8298a19e7fb056966df1a53e8f80ec53e7679bc658e08c743d4ecdf051/paddlepaddle-3.2.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:9af09e9953ffe85e732559fb422e59667d36e3fba07db90ca56e94178e374762", size = 88127153, upload-time = "2025-10-30T13:57:05.336Z" }, + { url = "https://files.pythonhosted.org/packages/32/68/fb93a38c567291c3d8a336d3fdf8306a5d378dec3deeb23f2d732a71cf81/paddlepaddle-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:d634bb3df4ab6a6bfbb1d66c84e7f22a653c17f70f5c606356705f043453ec3a", size = 101708076, upload-time = "2025-10-30T13:18:06.973Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c8/f2a289e835e04a225fb593b5816a01145b83cdbda1d9b0c51d06b4069f87/paddlepaddle-3.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8411ccb1e4e89426459529d76cf6cb3b07877a950a6685dadda2d8ca2a087b5a", size = 99489975, upload-time = "2025-10-30T13:38:11.586Z" }, + { url = "https://files.pythonhosted.org/packages/cc/b0/6e7be06af2678209102cd251af0314c9b2a5eba68cba3545576691e37b01/paddlepaddle-3.2.1-cp313-cp313-manylinux1_x86_64.whl", hash = "sha256:49ba669e91e91878b344e849adfc56eabfef78e072f52a4ef697f16ba75e0a3c", size = 189263243, upload-time = "2025-10-31T03:16:44.372Z" }, + { url = "https://files.pythonhosted.org/packages/4e/a2/2e8a73e16168729b201ea13d58008a365c08ec706f805f636e3c7f5b3e22/paddlepaddle-3.2.1-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:4e80019c1ceea3784a3e478bc2f0ec6ccee3c2520b4e1372a6adad089c6b537c", size = 88124112, upload-time = "2025-10-30T14:00:04.442Z" }, + { url = "https://files.pythonhosted.org/packages/a3/6f/042d73453ad01a2e3dd4adeae4870e25679d9eaa340d70bd54cd409cb982/paddlepaddle-3.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:d16fd322246c4a580ca512971840ac8c16126a77d59a7bceee165d8f2196a6b2", size = 101708504, upload-time = "2025-10-30T13:21:18.125Z" }, +] + [[package]] name = "paddlex" version = "3.3.9"