[project] name = "druks" version = "0.13.0" description = "Autonomous software delivery; the self-hosted home for durable agent apps." readme = "README.md" license = "FSL-1.1-ALv2" license-files = ["LICENSE"] authors = [{ name = "Paulo Alvarado Garcia" }] keywords = ["agents", "dbos", "durable-execution", "orchestration", "workflows"] classifiers = [ "Development Status :: 3 - Alpha", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.15", ] requires-python = ">=3.11" dependencies = [ "asyncssh>=2.22", "drukbox-python-sdk>=0.5.1", # 0.138 adds app.frontend(), which serves an app's shipped dist/. "fastapi>=0.138.0", "githubkit[auth-app]>=0.15.5", # hishel 2 dropped the httpx2 transport githubkit 0.17 imports. "hishel[async,httpx2]>=1.4.0,<2", "httpx>=0.28.0", "httpx2>=2.12.0", "jinja2>=3.1", "pydantic>=2.12.0", "pydantic-settings>=2.14.2", "pyyaml>=6.0", "sqlalchemy>=2.0", "uvicorn>=0.38.0", "uuid-utils>=0.16.0", "psycopg[binary]>=3.2", "alembic>=1.13", "dbos>=3.2,<4", "croniter>=6.2.2", "redis>=8", "tomlkit>=0.13", "blinker>=1.9", "cryptography>=48.0.1", "fastmcp>=4.1.0,<5", "agent-client-protocol==0.12.1", "sqlalchemy-encrypted-field==1.0.2", "jsonpointer>=3.1.1", "authlib>=1.7.2", "joserfc>=1.7.4", "slack-sdk>=3.44.1", "aiohttp>=3.14.3", "vobject>=0.9.9", ] [project.urls] Documentation = "https://docs.druks.ai" Issues = "https://github.com/czpython/druks/issues" Repository = "https://github.com/czpython/druks" [dependency-groups] dev = [ "pyjwt[crypto]>=2.13.0", "pyright>=1.1.411", "pytest>=9.1.1", "pytest-asyncio>=1.4.0", "pytest-timeout>=2.4.0", "ruff>=0.16.1", ] [project.scripts] druks = "druks.cli:main" [project.entry-points.pytest11] druks = "druks.testing" # Apps are discovered here. An app (in-tree today, a separate package # tomorrow) registers one App subclass; the platform loads them at startup and # walks each one's package for capabilities. Adding an app is one entry, not # edits across the platform wiring. [project.entry-points."druks.apps"] chat = "druks.chat.app:Chat" core = "druks.core.app:Core" software_factory = "druks.contrib.software_factory.app:SoftwareFactory" usage = "druks.usage.app:Usage" [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] packages = ["backend/druks"] [tool.hatch.build.targets.sdist] include = [ "/backend/druks", "/LICENSE", "/pyproject.toml", "/README.md", ] [tool.ruff] line-length = 100 target-version = "py311" # Alembic-generated revisions are machine-written; don't hand-lint them. extend-exclude = ["backend/druks/migrations/versions"] [tool.ruff.lint] select = ["E", "F", "I", "UP", "B", "SIM", "TID251"] # SIM108 rewrites if/else into a ternary; formatter-wrapped ternaries are banned here, and plain if/else is the house form. ignore = ["SIM108"] [tool.ruff.lint.flake8-tidy-imports.banned-api] # Internal code takes ``session: AsyncSession`` as a required parameter; only # author code and the seams that bind the registry read the ambient session. "druks.db.db_session".msg = "internal code takes session: AsyncSession; the ambient session is for author code and the session seams" "druks.database.db_session".msg = "internal code takes session: AsyncSession; the ambient session is for author code and the session seams" [tool.ruff.lint.per-file-ignores] # The seams that bind the registry, the author surface, and the author-style # entry points (tasks, subscribers, webhooks) read the ambient session. "backend/druks/database.py" = ["TID251"] "backend/druks/testing.py" = ["E402", "TID251"] # E402: the plugin sets the environment before its druks imports "backend/druks/models.py" = ["TID251"] "backend/druks/accounts/models.py" = ["TID251"] "backend/druks/durable/datastructures.py" = ["TID251"] "backend/druks/durable/engine.py" = ["TID251"] "backend/druks/workflows.py" = ["TID251"] "backend/druks/agents.py" = ["TID251"] "backend/druks/workspaces.py" = ["TID251"] "backend/druks/apps/base.py" = ["TID251"] "backend/druks/services/base.py" = ["TID251"] "backend/druks/services/resources.py" = ["TID251"] "backend/druks/files/datastructures.py" = ["TID251"] "backend/druks/browser/sessions.py" = ["TID251"] "backend/druks/browser/subscribers.py" = ["TID251"] "backend/druks/core/tasks.py" = ["TID251"] "backend/druks/chat/tasks.py" = ["TID251"] "backend/druks/core/webhooks/slack.py" = ["TID251"] "backend/druks/chat/channels/slack/subscribers.py" = ["TID251"] "backend/druks/chat/channels/github/subscribers.py" = ["TID251"] "backend/druks/chat/channels/whatsapp/webhooks.py" = ["TID251"] "backend/druks/chat/channels/calls/webhooks.py" = ["TID251"] "backend/druks/contrib/**" = ["TID251"] "backend/tests/**" = ["TID251"] [tool.ruff.lint.flake8-bugbear] # FastAPI dependency markers are the documented way to declare injected # parameters; B008 (no function calls in argument defaults) is a false # positive for them. extend-immutable-calls = ["fastapi.Depends", "fastapi.Query", "fastapi.Body"] [tool.pytest.ini_options] testpaths = ["backend/tests"] asyncio_mode = "auto" pythonpath = ["backend", "backend/tests"] # A parked await hangs a run silently (the suite normally finishes in ~90s). # thread method: dump EVERY thread's stack — DBOS workers included — then # exit from inside the process, so CI ends the step normally and keeps the # dump. Covers fixture setup, re-arms per test, survives pytest's own # faulthandler cancellations. timeout = 180 timeout_method = "thread" [tool.pyright] pythonVersion = "3.11" include = ["backend"] # Test code stays on extraPaths for import resolution but out of the checked set: # its loose mocks (call_args, SimpleNamespace-as-Request) are false-positive noise # that drowns the real signal in backend/druks. exclude = ["backend/tests"] # The proof app (backend/tests/druks-field_notes) is a standalone package; its # root is on the path so its intra-package imports and the tests that load it resolve. extraPaths = ["backend", "backend/tests", "backend/tests/druks-field_notes"] typeCheckingMode = "standard" reportAssignmentType = "warning"