[project] name = "muselab" version = "2.1.0" description = "Self-hosted multi-model Agent workbench for local workspaces, files, terminals, and artifacts." readme = "README.md" license = { file = "LICENSE" } authors = [ { name = "hesorchen", email = "hesorchen@gmail.com" }, ] keywords = [ "claude", "anthropic", "claude-agent-sdk", "mcp", "self-hosted", "llm", "ai-assistant", "agent-workspace", "deepseek", "pwa", ] classifiers = [ # PyPI Trove classifiers — surface project status / audience / license / # platform / Python version so PyPI search and dependency-graph tools # can categorise muselab even though it's not meant to be `pip install`-able # (it's a server you self-host, not a library). "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: FastAPI", "Intended Audience :: End Users/Desktop", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: POSIX :: Linux", "Operating System :: MacOS", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.12", "Topic :: Communications :: Chat", "Topic :: Office/Business", "Topic :: Scientific/Engineering :: Artificial Intelligence", ] requires-python = ">=3.12" dependencies = [ # Exact pin is deliberate: muselab adapts both SDK and bundled-CLI contracts # that are not capability-negotiated — the harness-tool denylist, JSONL # transcript shape, and native AskUserQuestion answer injection. Every SDK # upgrade must refresh the tool catalog and rerun the native question matrix. # Path/effort drift is already auto-eliminated via # project_key_for_directory() + EffortLevel. "claude-agent-sdk==0.2.157", # Security floor for CVE-2026-63374 and CVE-2026-64847. "anyio>=4.14.2", "fastapi>=0.138.1", # Pin starlette above the fastapi-transitive 1.0.0, which carries # PYSEC-2026-161 (fixed in 1.0.1). Direct constraint so the pip-audit # CVE-scan CI job stays green. "starlette>=1.3.1", "python-dotenv>=1.2.2", "python-multipart>=0.0.32", "uvicorn[standard]>=0.49.0", # Live file-tree refresh for Agent / terminal / external-editor changes. # One on-demand watcher per subscribed workspace feeds the durable SQLite # index; offline reconciliation runs when that workspace is reactivated. "watchfiles>=1.2.0", # xlsx preview — read-only, used by /api/files/xlsx. Pure-Python, no # native deps; safe to ship in the muselab Docker image. "openpyxl>=3.1.5", # Web Push: VAPID-signed pushes to browsers / PWAs subscribed to # /api/push/subscribe. Has a transitive cryptography dep but no # external binaries. "pywebpush>=2.0.0", # Keep aiohttp at the currently audited security floor rather than the # older pywebpush-transitive range. Direct constraint keeps fresh lock # resolutions aligned with the pip-audit CI baseline. "aiohttp>=3.14.3", # Security floor for the C-extension reference leak in CVE-2026-104874. "multidict>=6.9.1", "pillow>=12.3.0", # Security floors for the claim-verification and parser advisories # affecting PyJWT 2.13.0 and the proxy/redirect advisories in urllib3 2.7.0. # Keep both explicit so future lock resolutions retain the fixes. "pyjwt[crypto]>=2.15.1", "urllib3>=2.8.0", # Direct async HTTP client. Used by backend/memory_client.py to talk to the # optional self-hosted mem0 daemon. It is already an SDK/httpcore transitive # dep today, but we import it directly, so declare it explicitly — otherwise # an upstream drop would make `import httpx` fail and (because mem0 is # fail-soft) silently disable memory with no signal. "httpx>=0.27", # Optional pgvector adapter for the long-term memory index. Imported only # when pgvector is selected; Qdrant deployments never open a Postgres # connection, but keeping the driver installed makes provider switching a # runtime setting instead of a redeploy. "psycopg[binary]>=3.2.9", ] [project.urls] Homepage = "https://github.com/hesorchen/muselab" Source = "https://github.com/hesorchen/muselab" Issues = "https://github.com/hesorchen/muselab/issues" Documentation = "https://github.com/hesorchen/muselab/blob/main/docs/quickstart.md" Container = "https://github.com/hesorchen/muselab/pkgs/container/muselab" [dependency-groups] dev = [ "pytest>=9.1.1", "pytest-asyncio>=1.4.0", # Parallelize the large unit/integration suite on multi-core development # machines and CI. Browser E2E and upgrade smoke tests stay serial because # they intentionally share one live server/runtime. "pytest-xdist>=3.8.0", # Starlette 1.2+ prefers httpx2 for TestClient. Keep production httpx # separately because memory_client imports that package directly. "httpx2>=2.0.0", # Browser regressions cover Alpine rendering races and trusted mobile # touch gestures that static source assertions cannot exercise. "pytest-playwright>=0.7.0", # Pinned alongside dev deps so `scripts/lint.sh` and CI both have a # deterministic ruff version. Without this each dev / CI run pulled # latest via `uv run --with ruff`, occasionally causing rule churn. "ruff>=0.15.20", # CI collects coverage in its existing primary test run; lock the plugin # so worker instrumentation and the later report use the same toolchain. "pytest-cov>=7.1.0", ] # Ruff config — keep targeted, not aggressive. Goal: catch real bugs # (unused imports / variables, broken syntax-adjacent patterns) without # fighting a project that values terse multi-statement-on-one-line where # it reads naturally. Style enforcement is left to human review. [tool.ruff] target-version = "py312" line-length = 100 # informational; we don't auto-format [tool.ruff.lint] # Default ruleset (E, F, W) — pyflakes + pycodestyle errors only. # Explicitly NO opinionated rule families (B, I, N, UP) — they'd churn # 6k LOC of working code for marginal gain. select = ["E", "F", "W"] # Project-specific tolerances: # - E501 line-too-long: many existing lines are 100-120 chars by deliberate # choice (one full sentence per comment line). Not worth the churn. ignore = ["E501"]