pyproject.toml
Rama main · réplica de lectura del repositorio original.
[project]
name = "noeta"
version = "1.2.3"
description = "Open-source, Claude Code-compatible coding agent you can self-host on many workers: any worker continues any task, waits for approval hold no process. Terminal UI, CLI, ACP, HTTP API and Python SDK; Claude, DeepSeek or any OpenAI-compatible model."
readme = "README.md"
license = "Apache-2.0"
license-files = ["LICENSE"]
authors = [{ name = "The Noeta Authors", email = "initxy0@gmail.com" }]
keywords = ["ai-agent", "coding-agent", "ai-coding", "claude-code", "llm", "agentic-ai", "agent-sdk", "cli", "tui", "mcp", "acp", "anthropic", "claude", "deepseek", "openai", "self-hosted", "durable-execution", "event-sourcing", "human-in-the-loop", "multi-agent"]
classifiers = [
"Development Status :: 4 - Beta",
"Environment :: Console",
"Framework :: AsyncIO",
"Intended Audience :: Developers",
"Operating System :: POSIX :: Linux",
"Operating System :: MacOS",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Software Development",
"Topic :: Software Development :: Code Generators",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"Typing :: Typed",
]
requires-python = ">=3.11"
dependencies = [
"httpx[socks]>=0.28.1",
"msgspec>=0.19",
"pillow>=10.4",
"pyyaml>=6.0",
]
[project.optional-dependencies]
postgres = ["psycopg[binary,pool]>=3.2"]
server = ["starlette>=0.40", "uvicorn>=0.30", "websockets>=12"]
acp = ["agent-client-protocol>=0.12,<0.13"]
keyring = ["keyring>=24"]
# The Workflow tool's embedded JavaScript engine (V8, about 80 MB installed).
workflow = ["mini-racer>=0.14,<0.15"]
# Released together, at the same version (docs/releasing.md).
tui = ["noeta-tui==1.2.3"]
[project.urls]
Homepage = "https://github.com/initxy/noeta"
Documentation = "https://initxy.github.io/noeta/"
Source = "https://github.com/initxy/noeta"
Issues = "https://github.com/initxy/noeta/issues"
Changelog = "https://github.com/initxy/noeta/blob/main/CHANGELOG.md"
[project.scripts]
noeta = "noeta.cli.main:main"
# Built-in plugins and backends register exactly as a third-party package
# would (noeta/core/registry.py).
[project.entry-points."noeta.plugins"]
ask_user = "noeta.plugins.ask_user:AskUser"
checkpoints = "noeta.plugins.checkpoints:Checkpoints"
compaction = "noeta.plugins.compaction:Compaction"
empty_reply = "noeta.plugins.empty_reply:EmptyReply"
environment = "noeta.plugins.environment:Environment"
findings = "noeta.plugins.findings:Findings"
fs = "noeta.plugins.fs:FileTools"
hooks = "noeta.plugins.hooks:Hooks"
instructions = "noeta.plugins.instructions:Instructions"
markdown_commands = "noeta.plugins.markdown_commands:MarkdownCommands"
mcp = "noeta.plugins.mcp:Mcp"
memory = "noeta.plugins.memory:Memory"
permissions = "noeta.plugins.permissions:Permissions"
plan_mode = "noeta.plugins.plan_mode:PlanMode"
project_status = "noeta.plugins.project_status:ProjectStatus"
budget = "noeta.plugins.budget:BudgetCaps"
quiet_nudge = "noeta.plugins.quiet_nudge:QuietNudge"
schedule = "noeta.plugins.schedule:Schedule"
shell = "noeta.plugins.shell:Shell"
skills = "noeta.plugins.skills:Skills"
structured_output = "noeta.plugins.structured_output:StructuredOutput"
title = "noeta.plugins.title:Title"
todos = "noeta.plugins.todos:Todos"
tool_search = "noeta.plugins.tool_search:ToolSearch"
subtasks = "noeta.plugins.subtasks:Subtasks"
web = "noeta.plugins.web:Web"
workflow = "noeta.plugins.workflow:Orchestration"
[project.entry-points."noeta.providers"]
anthropic = "noeta.backends.providers:anthropic_provider"
openai_compat = "noeta.backends.providers:openai_compat_provider"
openai_responses = "noeta.backends.providers:openai_responses_provider"
[project.entry-points."noeta.execenvs"]
local = "noeta.backends.execenvs.local:LocalExecEnv"
container = "noeta.backends.execenvs.container:open_env"
[project.entry-points."noeta.stores"]
sqlite = "noeta.backends.stores.sqlite:open_url"
postgres = "noeta.backends.stores.postgres:open_url"
postgresql = "noeta.backends.stores.postgres:open_url"
[dependency-groups]
dev = [
"pytest>=8.0",
"pytest-asyncio>=0.24",
"pytest-cov>=5.0",
"pytest-xdist>=3.8",
"mypy>=1.10",
"respx>=0.23.1",
"ruff>=0.6",
"import-linter>=2.0",
"types-pyyaml>=6.0",
"pytest-timeout>=2.3",
# The workflow extra, so the Workflow tests run whatever `uv sync` is given.
"mini-racer>=0.14,<0.15",
# The terminal UI (tui/), so `make check` runs its tests and lints it.
"noeta-tui",
]
[tool.uv.workspace]
members = ["tui"]
[tool.uv.sources]
noeta-tui = { workspace = true }
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
# The built web app (make web-bundle) is not in the repository but goes in
# both distributions, so a wheel built from the sdist carries it too.
[tool.hatch.build]
artifacts = ["/noeta/host/web_dist/"]
[tool.hatch.build.targets.wheel]
packages = ["noeta"]
# A local coding tool's settings are not part of the source distribution.
[tool.hatch.build.targets.sdist]
exclude = ["/.claude"]
[tool.mypy]
strict = true
files = ["noeta", "evals", "tui/src"]
[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"
addopts = "-ra -m 'not live and not cluster' --import-mode=importlib"
# A hung test fails with every thread's stack instead of stalling the run:
# faulthandler dumps the stacks at 120 s, pytest-timeout ends the test at 300 s.
faulthandler_timeout = 120
timeout = 300
timeout_method = "thread"
markers = [
"live: needs a real model backend; opt in with -m live",
"cluster: worker processes on Postgres under faults; run with make cluster",
]
[tool.coverage.run]
source = ["noeta"]
branch = true
[tool.ruff]
target-version = "py311"
line-length = 100
[tool.ruff.lint]
select = ["E", "F", "W", "I", "B", "UP", "RUF", "BLE"]
# RUF012: msgspec Struct defaults are copied per instance.
# UP007: module-level union aliases read better as Union[...] when long.
ignore = ["RUF012", "UP007"]
[tool.ruff.lint.isort]
known-first-party = ["noeta", "noeta_tui"]
# tui/tests' shared helpers, on its pytest pythonpath.
known-local-folder = ["support"]
[tool.ruff.lint.flake8-bugbear]
extend-immutable-calls = ["noeta.core.store.Patch", "Patch"]
[tool.ruff.lint.per-file-ignores]
"{tests,tui/tests}/**" = ["B008", "E501"]
# The terminal UI draws Claude Code's glyphs (❯ ⎿ ⏺ ✻).
"tui/**" = ["RUF001", "RUF002", "RUF003"]
# Dev tooling, not shipped: long table lines and prose are fine.
"{evals,bench,scripts}/**" = ["E501", "RUF002", "RUF003", "B905"]
[tool.importlinter]
root_packages = ["noeta", "noeta_tui"]
# The layers, top to bottom; a module may import only from layers below it.
[[tool.importlinter.contracts]]
name = "layers: cli > agents > plugins | backends | host > toolkit > core"
type = "layers"
layers = [
"noeta.cli",
"noeta.agents",
"noeta.plugins | noeta.backends | noeta.host",
"noeta.toolkit",
"noeta.core",
]
# A plugin is written against the kernel only, so any one can be removed.
[[tool.importlinter.contracts]]
name = "plugins do not import each other"
type = "independence"
modules = ["noeta.plugins.*"]
# Front ends and protocol adapters reach plugins only through the kernel (settings by name, the
# question form, commands), so any plugin can be removed or replaced.
[[tool.importlinter.contracts]]
name = "front ends do not import plugins"
type = "forbidden"
source_modules = ["noeta.cli", "noeta.host"]
forbidden_modules = ["noeta.plugins"]
# The terminal UI (tui/) is a front end on noeta's host API: no plugin, no
# backend, nothing of the noeta command (which hands it what it needs).
[[tool.importlinter.contracts]]
name = "the terminal UI imports no plugin, backend, agent or the noeta command"
type = "forbidden"
source_modules = ["noeta_tui"]
forbidden_modules = ["noeta.plugins", "noeta.backends", "noeta.agents", "noeta.cli"]
[[tool.importlinter.contracts]]
name = "backends do not import each other"
type = "independence"
modules = ["noeta.backends.*"]
[[tool.importlinter.contracts]]
name = "the loop is pure: no runner, store or provider"
type = "forbidden"
source_modules = ["noeta.core.loop"]
forbidden_modules = ["noeta.core.runner", "noeta.core.store", "noeta.core.provider"]