[build-system] requires = ["hatchling"] build-backend = "hatchling.build" [project] # The distribution name only. The import package and the console script both stay # `meshterm` — PyPI prohibits that name (see pypi/support#12162), and nothing about the # app changes because of it. `[tool.hatch.build.targets.wheel]` below is what keeps the # wheel shipping the `meshterm/` package under this different name. name = "mesh-term" dynamic = ["version"] description = "MeshTerm is a full-featured TUI-based MeshCore client for your terminal. Supports USB, Bluetooth and TCP companion connection. For Windows, macOS and Linux." readme = "README.md" requires-python = ">=3.10" authors = [{ name = "Jean-Pierre Martineau" }] license = "Apache-2.0" license-files = ["LICENSE", "NOTICE"] keywords = ["meshcore", "lora", "mesh", "radio", "cli"] # No `License ::` entry: PEP 639 forbids one alongside the SPDX expression above. classifiers = [ "Development Status :: 4 - Beta", "Environment :: Console", "Intended Audience :: End Users/Desktop", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Communications :: Ham Radio", "Topic :: System :: Networking", ] dependencies = [ "meshcore>=2.3", # companion-device protocol (serial/BLE/TCP) "pycryptodome>=3.20", # AES/HMAC for decrypting overheard channel packets "pyserial>=3.5", # serial-port enumeration for device discovery "bleak>=0.22", # Bluetooth LE scanning + companion connection "typer>=0.12", # scripted CLI; subcommands mirror the menu "rich>=13.7", # modern console rendering (progress, tables, panels) "markdown-it-py>=3.0", # CommonMark parser behind the written About pages "prompt_toolkit>=3.0", # full-screen interactive TUI (terminal, input, resize) "PyYAML>=6.0", # the preferences file (meshterm.core.preferences) "segno>=1.6", # pure-Python QR codes (channel share links) "tomli>=2.0; python_version < '3.11'", # TOML config reader on 3.10 "tomli-w>=1.0", # TOML writer for device-config backups ] [project.optional-dependencies] dev = [ "pytest>=8.0", "pytest-asyncio>=0.23", # Pinned to a minor line, not floated. A formatter that drifts between machines is # not a gate: 0.16 formats Python inside Markdown and 0.15 does not, so an # unpinned `ruff>=0.4` had CI and a laptop disagreeing about a clean tree. "ruff>=0.16.6,<0.17", ] [project.urls] Homepage = "https://github.com/jpmartineau/MeshTerm" Repository = "https://github.com/jpmartineau/MeshTerm" Issues = "https://github.com/jpmartineau/MeshTerm/issues" Changelog = "https://github.com/jpmartineau/MeshTerm/blob/main/CHANGELOG.md" [project.scripts] meshterm = "meshterm.cli:main" # The one place a version is written; `[project] version` is dynamic so the package # attribute and the distribution metadata cannot drift apart. [tool.hatch.version] path = "meshterm/__init__.py" [tool.hatch.build.targets.wheel] packages = ["meshterm"] [tool.ruff] line-length = 100 target-version = "py310" # Helper scripts for the Claude skills; they are not part of the package. And `docs/`, # which holds dated survey documents: ruff formats Python blocks inside Markdown, and the # code they quote is an excerpt of how a file looked on a past date. Reformatting a # quotation edits the record — and the excerpts are call fragments, not whole programs, so # the formatter's idea of "fixed" turns them into something that was never there. extend-exclude = [".claude", "docs"] [tool.ruff.lint] select = ["E", "F", "I", "UP", "B", "D"] ignore = ["D203", "D213"] # pick one blank-line-before-class / multi-line-summary style [tool.ruff.lint.pydocstyle] convention = "google" [tool.ruff.lint.flake8-bugbear] # Typer declares CLI parameters by calling these in the default; the objects they # return are inert sentinels the command never mutates, which is what B008 guards. extend-immutable-calls = ["typer.Argument", "typer.Option"] [tool.pytest.ini_options] asyncio_mode = "auto" testpaths = ["tests"]