[project] name = "niles-core" version = "0.2.16" description = "Local AI Butler" license = "AGPL-3.0-only" requires-python = ">=3.14" dependencies = [ # Web Framework "fastapi>=0.129.0", "uvicorn[standard]>=0.41.0", # Async HTTP Client + Retry "httpx>=0.28.1", "tenacity>=9.0.0", # Database "asyncpg>=0.31.0", # Database migrations (Alembic runs sync via psycopg2) "alembic>=1.13.0", "sqlalchemy>=2.0.0", "psycopg2-binary>=2.9.0", # LLM Client (OpenAI-kompatibel für LM Studio) "openai>=2.21.0", "tiktoken>=0.8.0", # approximate token counting for the context budget # MCP SDK "mcp>=2.3.0,<3", # Config "pydantic-settings>=2.13.0", "pyyaml>=6.0.3", # Scheduling "apscheduler>=3.11.2", # Web UI (templates + static files) "jinja2>=3.1.0", "aiofiles>=24.0.0", # Signed session cookies (Google OAuth) "itsdangerous>=2.0", # Password hashing (Argon2id) "argon2-cffi>=25.1.0", # RRULE expansion for recurring calendar events "python-dateutil>=2.8.0", # Structured JSON logging (12-factor XI) "structlog>=24.1.0", # Prometheus metrics "prometheus-client>=0.21.0", # WebSocket client (Signal integration) "websockets>=14.0", # MCP Web Search is built-in (src/niles/mcp/search) # HTML text extraction for web fetch MCP server "trafilatura>=2.0.0", # Pin transitive lxml[html-clean] to patched release (GHSA-4jhm-jv67-739f) "lxml-html-clean>=0.4.5", # Robust JSON repair for malformed LLM tool-call output "json-repair>=0.58.0", # Notion workspace integration (RAG pipeline) "notion-client>=2.0.0", # Column-level credential encryption (Fernet AES-128-CBC + HMAC) "cryptography>=44.0", # Error tracking (opt-in via SENTRY_DSN) "sentry-sdk[fastapi]>=2.0.0", # Safe XML parsing (XXE protection for CalDAV responses) "defusedxml>=0.7.0", ] [project.optional-dependencies] tracing = [ "langfuse>=5.0.4,<6", ] dev = [ "pytest>=9.0.0", "pytest-asyncio>=1.3.0", "pytest-cov>=6.0.0", "pytest-json-report>=1.5.0", # machine-readable eval pass counts for the LLM-eval gate "httpx>=0.28.1", # For TestClient "mypy>=1.14.0", "pip-audit>=2.7.0", "ruff>=0.9.0", "anthropic>=0.40", # Claude-as-Judge E2E tests "types-python-dateutil>=2.9.0.20260518", "types-pyyaml>=6.0.12.20260518", ] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] packages = ["src/niles"] [tool.pytest.ini_options] testpaths = ["tests"] asyncio_mode = "auto" markers = [ "integration: requires Docker Compose infrastructure (deselect with -m 'not integration')", "e2e: end-to-end agent pipeline tests (deselect with -m 'not e2e')", "llm_judge: requires Ollama + Claude API key, slow (deselect with -m 'not llm_judge')", "llm_eval: behavioral quality evals against live Ollama (deselect with -m 'not llm_eval')", ] [tool.ruff] target-version = "py314" line-length = 120 [tool.ruff.lint] select = ["E", "F", "W", "S"] ignore = ["E501"] # Line length handled by ruff format [tool.ruff.lint.per-file-ignores] # Tests legitimately use assert, hardcoded credentials, verify=False, and try-except-pass "tests/**/*.py" = ["S101", "S105", "S106", "S110", "S501"] # Web route handlers use assert for type narrowing after auth checks "src/niles/sources/web/_admin.py" = ["S101"] "src/niles/sources/web/_chat.py" = ["S101"] "src/niles/sources/web/_contacts.py" = ["S101"] "src/niles/sources/web/_core.py" = ["S101"] "src/niles/sources/web/_settings.py" = ["S101"] "src/niles/sources/web/_vikunja.py" = ["S101"] "src/niles/sources/web/_whatsapp.py" = ["S101"] # CalDAV: uses defusedxml (S314 no longer needed) # Dynamic SQL uses parameterized queries (where_clause built from internal logic, not user input) "src/niles/contact_store.py" = ["S608"] [tool.mypy] python_version = "3.14" warn_return_any = false warn_unused_configs = true ignore_missing_imports = true check_untyped_defs = true disallow_untyped_defs = true disallow_incomplete_defs = true # All per-module error-code suppressions removed in W18 — fixed at the source. # Type stubs (types-python-dateutil, types-pyyaml) resolve the import-untyped cases. [tool.coverage.run] source = ["src/niles"] omit = ["tests/*", "alembic/*"] branch = true [tool.coverage.report] show_missing = true fail_under = 77 # branch-inclusive; raised from 70 after the route/agent-loop test additions (W16/W17) exclude_lines = [ "pragma: no cover", "if __name__", "if TYPE_CHECKING", ]