meshcore-irc-bridge 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. meshcore_irc_bridge-0.1.0/.github/workflows/publish.yml +64 -0
  2. meshcore_irc_bridge-0.1.0/.gitignore +14 -0
  3. meshcore_irc_bridge-0.1.0/LICENSE +21 -0
  4. meshcore_irc_bridge-0.1.0/PKG-INFO +147 -0
  5. meshcore_irc_bridge-0.1.0/README.md +117 -0
  6. meshcore_irc_bridge-0.1.0/config.example.yaml +91 -0
  7. meshcore_irc_bridge-0.1.0/pyproject.toml +92 -0
  8. meshcore_irc_bridge-0.1.0/src/meshcore_irc_bridge/__init__.py +0 -0
  9. meshcore_irc_bridge-0.1.0/src/meshcore_irc_bridge/__main__.py +4 -0
  10. meshcore_irc_bridge-0.1.0/src/meshcore_irc_bridge/bridge.py +287 -0
  11. meshcore_irc_bridge-0.1.0/src/meshcore_irc_bridge/cli.py +91 -0
  12. meshcore_irc_bridge-0.1.0/src/meshcore_irc_bridge/config.py +397 -0
  13. meshcore_irc_bridge-0.1.0/src/meshcore_irc_bridge/formatting.py +86 -0
  14. meshcore_irc_bridge-0.1.0/src/meshcore_irc_bridge/irc/__init__.py +0 -0
  15. meshcore_irc_bridge-0.1.0/src/meshcore_irc_bridge/irc/client.py +320 -0
  16. meshcore_irc_bridge-0.1.0/src/meshcore_irc_bridge/irc/protocol.py +183 -0
  17. meshcore_irc_bridge-0.1.0/src/meshcore_irc_bridge/irc/sasl.py +46 -0
  18. meshcore_irc_bridge-0.1.0/src/meshcore_irc_bridge/mesh_connection.py +87 -0
  19. meshcore_irc_bridge-0.1.0/tests/__init__.py +0 -0
  20. meshcore_irc_bridge-0.1.0/tests/cli/__init__.py +0 -0
  21. meshcore_irc_bridge-0.1.0/tests/cli/test_cli.py +188 -0
  22. meshcore_irc_bridge-0.1.0/tests/conftest.py +0 -0
  23. meshcore_irc_bridge-0.1.0/tests/fakes/__init__.py +0 -0
  24. meshcore_irc_bridge-0.1.0/tests/fakes/fake_irc_server.py +85 -0
  25. meshcore_irc_bridge-0.1.0/tests/fakes/meshcore_double.py +108 -0
  26. meshcore_irc_bridge-0.1.0/tests/unit/__init__.py +0 -0
  27. meshcore_irc_bridge-0.1.0/tests/unit/irc/__init__.py +0 -0
  28. meshcore_irc_bridge-0.1.0/tests/unit/irc/test_client.py +711 -0
  29. meshcore_irc_bridge-0.1.0/tests/unit/irc/test_protocol.py +316 -0
  30. meshcore_irc_bridge-0.1.0/tests/unit/irc/test_sasl.py +47 -0
  31. meshcore_irc_bridge-0.1.0/tests/unit/test_bridge.py +763 -0
  32. meshcore_irc_bridge-0.1.0/tests/unit/test_config.py +708 -0
  33. meshcore_irc_bridge-0.1.0/tests/unit/test_formatting.py +142 -0
  34. meshcore_irc_bridge-0.1.0/tests/unit/test_mesh_connection.py +139 -0
@@ -0,0 +1,64 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ jobs:
8
+ test:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+ - uses: actions/setup-python@v5
13
+ with:
14
+ python-version: "3.12"
15
+ - run: pip install -e .[dev]
16
+ - run: ruff check .
17
+ - run: mypy
18
+ - run: pytest
19
+
20
+ build:
21
+ needs: test
22
+ runs-on: ubuntu-latest
23
+ outputs:
24
+ version: ${{ steps.version.outputs.version }}
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ - uses: actions/setup-python@v5
28
+ with:
29
+ python-version: "3.12"
30
+ - run: pip install build
31
+ - run: python -m build
32
+ - id: version
33
+ run: |
34
+ echo "version=$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')" >> "$GITHUB_OUTPUT"
35
+ - uses: actions/upload-artifact@v4
36
+ with:
37
+ name: dist
38
+ path: dist/
39
+
40
+ publish:
41
+ needs: build
42
+ runs-on: ubuntu-latest
43
+ environment: pypi
44
+ permissions:
45
+ id-token: write
46
+ steps:
47
+ - uses: actions/download-artifact@v4
48
+ with:
49
+ name: dist
50
+ path: dist/
51
+ - name: Check whether this version is already on PyPI
52
+ id: check
53
+ run: |
54
+ VERSION="${{ needs.build.outputs.version }}"
55
+ STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://pypi.org/pypi/meshcore-irc-bridge/${VERSION}/json")
56
+ if [ "$STATUS" = "200" ]; then
57
+ echo "Version ${VERSION} is already published; skipping upload."
58
+ echo "exists=true" >> "$GITHUB_OUTPUT"
59
+ else
60
+ echo "exists=false" >> "$GITHUB_OUTPUT"
61
+ fi
62
+ - name: Publish to PyPI
63
+ if: steps.check.outputs.exists == 'false'
64
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .venv/
5
+ build/
6
+ dist/
7
+ .coverage
8
+ .coverage.*
9
+ htmlcov/
10
+ .pytest_cache/
11
+ .mypy_cache/
12
+ .ruff_cache/
13
+ config.yaml
14
+ .DS_Store
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Faradome
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,147 @@
1
+ Metadata-Version: 2.5
2
+ Name: meshcore-irc-bridge
3
+ Version: 0.1.0
4
+ Summary: A one-way bridge relaying MeshCore companion radio channel messages into IRC channels
5
+ Project-URL: Homepage, https://github.com/Faradome/meshcore-irc-bridge
6
+ Project-URL: Issues, https://github.com/Faradome/meshcore-irc-bridge/issues
7
+ Author-email: William Canterbury <william.canterbury@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Environment :: Console
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Communications :: Chat :: Internet Relay Chat
14
+ Classifier: Topic :: Communications :: Ham Radio
15
+ Requires-Python: >=3.10
16
+ Requires-Dist: bleak>=0.22
17
+ Requires-Dist: meshcore>=2.3.9
18
+ Requires-Dist: pyserial>=3.5
19
+ Requires-Dist: pyyaml>=6.0
20
+ Provides-Extra: dev
21
+ Requires-Dist: build>=1.0; extra == 'dev'
22
+ Requires-Dist: mypy>=1.10; extra == 'dev'
23
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
24
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
25
+ Requires-Dist: pytest>=8.0; extra == 'dev'
26
+ Requires-Dist: ruff>=0.6; extra == 'dev'
27
+ Requires-Dist: types-pyserial; extra == 'dev'
28
+ Requires-Dist: types-pyyaml; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # meshcore-irc-bridge
32
+
33
+ > **This is an AI-generated application.** The design, code, tests, and documentation in this
34
+ > repository were produced by an AI coding agent (Claude), directed and reviewed by a human
35
+ > maintainer.
36
+
37
+ A one-way bridge: it connects to a [MeshCore](https://meshcore.co.uk/) companion
38
+ radio, listens for channel messages, and relays them into IRC channels — one
39
+ mesh channel mapped to one IRC channel. It never sends anything back to the
40
+ mesh; IRC messages are received and ignored.
41
+
42
+ Built on the [`meshcore`](https://pypi.org/project/meshcore/) Python package
43
+ for the radio side (BLE, serial, or TCP companion connection). The IRC side is
44
+ a small hand-rolled asyncio client with first-class
45
+ [IRCv3 SASL](https://ircv3.net/specs/extensions/sasl-3.1) support, a NickServ
46
+ `IDENTIFY` fallback (with a configurable wait before joining channels), and
47
+ support for connecting with a fully unregistered nickname.
48
+
49
+ ## Install
50
+
51
+ ```bash
52
+ python3 -m venv .venv
53
+ source .venv/bin/activate
54
+ pip install -e ".[dev]" # from a checkout, until this is published
55
+ ```
56
+
57
+ ## Configure
58
+
59
+ Copy [`config.example.yaml`](config.example.yaml) to `config.yaml` and edit it:
60
+
61
+ ```bash
62
+ cp config.example.yaml config.yaml
63
+ ```
64
+
65
+ - `mesh.connection` — how to reach your companion radio: `type: ble` (with
66
+ `address`), `type: serial` (with `port`/`baudrate`), or `type: tcp` (with
67
+ `host`/`port`).
68
+ - `irc` — the IRC server and how to authenticate. `irc.auth.mode` is one of:
69
+ - `sasl` — authenticate with `AUTHENTICATE PLAIN` before registration
70
+ completes. Requires `irc.auth.sasl.username`/`password`.
71
+ - `nickserv` — connect without SASL, wait for the welcome (`001`), send
72
+ `PRIVMSG NickServ :IDENTIFY <password>`, then wait
73
+ `irc.auth.nickserv.join_wait_seconds` before joining channels (long
74
+ enough for services to apply your cloak/account before you join gated
75
+ channels). Requires `irc.auth.nickserv.password`.
76
+ - `none` — connect with a fully unregistered nickname and join immediately.
77
+
78
+ The auth mode is fixed by config — if it fails (e.g. the server doesn't
79
+ offer SASL, or the password is rejected), the bridge logs the failure and
80
+ retries the *same* mode on reconnect rather than silently switching
81
+ methods.
82
+
83
+ Secrets support `${ENV_VAR}` interpolation so passwords don't need to sit
84
+ in the YAML file in plaintext, e.g. `password: "${IRC_SASL_PASSWORD}"`.
85
+
86
+ - `channels` — the mesh-channel-to-IRC-channel mapping, e.g.:
87
+
88
+ ```yaml
89
+ channels:
90
+ - mesh_channel: 0
91
+ irc_channel: "#mesh-general"
92
+ - mesh_channel: 1
93
+ irc_channel: "#mesh-emergency"
94
+ ```
95
+
96
+ Several mesh channels may map to the same IRC channel. When they do,
97
+ each relayed line is prefixed with `[<mesh_channel>] ` so messages from
98
+ either stay attributable once interleaved there (e.g. `[0] hello`,
99
+ `[1] hi`); a mesh channel with an IRC channel all to itself is left
100
+ unprefixed.
101
+
102
+ ### Setting up a channel on the radio itself
103
+
104
+ This bridge only *reads* channel messages — it never creates, renames, or
105
+ rekeys a channel on the companion radio. To set one up (or check what's
106
+ already configured), use [`meshcorectl`](https://github.com/Faradome/meshcorectl),
107
+ a companion CLI for MeshCore radios:
108
+
109
+ ```bash
110
+ meshcorectl create channel 0 "General" # create/rename channel 0
111
+ meshcorectl get channels # list what's configured on the radio
112
+ ```
113
+
114
+ ## Run
115
+
116
+ ```bash
117
+ meshcore-irc-bridge --config config.yaml
118
+ # or
119
+ python -m meshcore_irc_bridge --config config.yaml
120
+ ```
121
+
122
+ `--log-level` (default `INFO`) controls verbosity.
123
+
124
+ ## Development
125
+
126
+ ```bash
127
+ python3 -m venv .venv
128
+ source .venv/bin/activate
129
+ pip install -e ".[dev]"
130
+ pytest # 100% line+branch coverage enforced
131
+ ruff check .
132
+ mypy
133
+ ```
134
+
135
+ Tests never touch real hardware or a real IRC network: the mesh side is
136
+ exercised against a hardware-free double of `meshcore.MeshCore`
137
+ (`tests/fakes/meshcore_double.py`), and the IRC client is exercised against a
138
+ real (loopback-only) asyncio TCP server that scripts IRC protocol exchanges
139
+ (`tests/fakes/fake_irc_server.py`).
140
+
141
+ Real end-to-end verification against an actual radio and IRC network is out
142
+ of scope for the automated test suite — run the bridge against your own
143
+ setup once installed to confirm it end-to-end.
144
+
145
+ ## License
146
+
147
+ [MIT](LICENSE)
@@ -0,0 +1,117 @@
1
+ # meshcore-irc-bridge
2
+
3
+ > **This is an AI-generated application.** The design, code, tests, and documentation in this
4
+ > repository were produced by an AI coding agent (Claude), directed and reviewed by a human
5
+ > maintainer.
6
+
7
+ A one-way bridge: it connects to a [MeshCore](https://meshcore.co.uk/) companion
8
+ radio, listens for channel messages, and relays them into IRC channels — one
9
+ mesh channel mapped to one IRC channel. It never sends anything back to the
10
+ mesh; IRC messages are received and ignored.
11
+
12
+ Built on the [`meshcore`](https://pypi.org/project/meshcore/) Python package
13
+ for the radio side (BLE, serial, or TCP companion connection). The IRC side is
14
+ a small hand-rolled asyncio client with first-class
15
+ [IRCv3 SASL](https://ircv3.net/specs/extensions/sasl-3.1) support, a NickServ
16
+ `IDENTIFY` fallback (with a configurable wait before joining channels), and
17
+ support for connecting with a fully unregistered nickname.
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ python3 -m venv .venv
23
+ source .venv/bin/activate
24
+ pip install -e ".[dev]" # from a checkout, until this is published
25
+ ```
26
+
27
+ ## Configure
28
+
29
+ Copy [`config.example.yaml`](config.example.yaml) to `config.yaml` and edit it:
30
+
31
+ ```bash
32
+ cp config.example.yaml config.yaml
33
+ ```
34
+
35
+ - `mesh.connection` — how to reach your companion radio: `type: ble` (with
36
+ `address`), `type: serial` (with `port`/`baudrate`), or `type: tcp` (with
37
+ `host`/`port`).
38
+ - `irc` — the IRC server and how to authenticate. `irc.auth.mode` is one of:
39
+ - `sasl` — authenticate with `AUTHENTICATE PLAIN` before registration
40
+ completes. Requires `irc.auth.sasl.username`/`password`.
41
+ - `nickserv` — connect without SASL, wait for the welcome (`001`), send
42
+ `PRIVMSG NickServ :IDENTIFY <password>`, then wait
43
+ `irc.auth.nickserv.join_wait_seconds` before joining channels (long
44
+ enough for services to apply your cloak/account before you join gated
45
+ channels). Requires `irc.auth.nickserv.password`.
46
+ - `none` — connect with a fully unregistered nickname and join immediately.
47
+
48
+ The auth mode is fixed by config — if it fails (e.g. the server doesn't
49
+ offer SASL, or the password is rejected), the bridge logs the failure and
50
+ retries the *same* mode on reconnect rather than silently switching
51
+ methods.
52
+
53
+ Secrets support `${ENV_VAR}` interpolation so passwords don't need to sit
54
+ in the YAML file in plaintext, e.g. `password: "${IRC_SASL_PASSWORD}"`.
55
+
56
+ - `channels` — the mesh-channel-to-IRC-channel mapping, e.g.:
57
+
58
+ ```yaml
59
+ channels:
60
+ - mesh_channel: 0
61
+ irc_channel: "#mesh-general"
62
+ - mesh_channel: 1
63
+ irc_channel: "#mesh-emergency"
64
+ ```
65
+
66
+ Several mesh channels may map to the same IRC channel. When they do,
67
+ each relayed line is prefixed with `[<mesh_channel>] ` so messages from
68
+ either stay attributable once interleaved there (e.g. `[0] hello`,
69
+ `[1] hi`); a mesh channel with an IRC channel all to itself is left
70
+ unprefixed.
71
+
72
+ ### Setting up a channel on the radio itself
73
+
74
+ This bridge only *reads* channel messages — it never creates, renames, or
75
+ rekeys a channel on the companion radio. To set one up (or check what's
76
+ already configured), use [`meshcorectl`](https://github.com/Faradome/meshcorectl),
77
+ a companion CLI for MeshCore radios:
78
+
79
+ ```bash
80
+ meshcorectl create channel 0 "General" # create/rename channel 0
81
+ meshcorectl get channels # list what's configured on the radio
82
+ ```
83
+
84
+ ## Run
85
+
86
+ ```bash
87
+ meshcore-irc-bridge --config config.yaml
88
+ # or
89
+ python -m meshcore_irc_bridge --config config.yaml
90
+ ```
91
+
92
+ `--log-level` (default `INFO`) controls verbosity.
93
+
94
+ ## Development
95
+
96
+ ```bash
97
+ python3 -m venv .venv
98
+ source .venv/bin/activate
99
+ pip install -e ".[dev]"
100
+ pytest # 100% line+branch coverage enforced
101
+ ruff check .
102
+ mypy
103
+ ```
104
+
105
+ Tests never touch real hardware or a real IRC network: the mesh side is
106
+ exercised against a hardware-free double of `meshcore.MeshCore`
107
+ (`tests/fakes/meshcore_double.py`), and the IRC client is exercised against a
108
+ real (loopback-only) asyncio TCP server that scripts IRC protocol exchanges
109
+ (`tests/fakes/fake_irc_server.py`).
110
+
111
+ Real end-to-end verification against an actual radio and IRC network is out
112
+ of scope for the automated test suite — run the bridge against your own
113
+ setup once installed to confirm it end-to-end.
114
+
115
+ ## License
116
+
117
+ [MIT](LICENSE)
@@ -0,0 +1,91 @@
1
+ # meshcore-irc-bridge configuration.
2
+ #
3
+ # Copy this file to config.yaml and edit it for your setup:
4
+ # cp config.example.yaml config.yaml
5
+ #
6
+ # Secrets support ${ENV_VAR} interpolation, so passwords don't need to sit
7
+ # in this file in plaintext -- e.g. password: "${IRC_SASL_PASSWORD}".
8
+
9
+ mesh:
10
+ connection:
11
+ # One of: ble | serial | tcp
12
+ type: ble
13
+
14
+ # --- ble ---
15
+ address: "AA:BB:CC:DD:EE:FF"
16
+
17
+ # --- serial (uncomment and remove the ble fields above) ---
18
+ # type: serial
19
+ # port: /dev/ttyUSB0
20
+ # baudrate: 115200
21
+
22
+ # --- tcp (uncomment and remove the ble fields above) ---
23
+ # type: tcp
24
+ # host: 192.168.1.50
25
+ # port: 5000
26
+
27
+ # Let the meshcore library itself retry a dropped transport before this
28
+ # bridge gives up and reconnects from scratch.
29
+ auto_reconnect: true
30
+ max_reconnect_attempts: 5
31
+
32
+ irc:
33
+ server: irc.libera.chat
34
+ port: 6697
35
+ tls: true
36
+ nickname: meshcore-bridge
37
+ # Defaults to `nickname` if omitted.
38
+ username: meshcore
39
+ realname: MeshCore IRC Bridge
40
+
41
+ auth:
42
+ # One of: sasl | nickserv | none
43
+ #
44
+ # The mode is fixed here -- if it fails (server doesn't offer SASL, a
45
+ # password is rejected, ...) the bridge logs the failure and retries
46
+ # the *same* mode on reconnect, it does not silently fall back to a
47
+ # different method.
48
+ mode: sasl
49
+
50
+ # --- sasl (AUTHENTICATE PLAIN, before registration completes) ---
51
+ sasl:
52
+ username: mynick
53
+ password: "${IRC_SASL_PASSWORD}"
54
+
55
+ # --- nickserv (connect unauthenticated, IDENTIFY, then wait before
56
+ # joining channels -- for networks without SASL) ---
57
+ # auth:
58
+ # mode: nickserv
59
+ # nickserv:
60
+ # password: "${IRC_NICKSERV_PASSWORD}"
61
+ # join_wait_seconds: 5 # default 5
62
+
63
+ # --- none (fully unregistered nickname, joins immediately) ---
64
+ # auth:
65
+ # mode: none
66
+
67
+ reconnect:
68
+ initial_delay_seconds: 1
69
+ max_delay_seconds: 60
70
+
71
+ # A dead peer (netsplit, a middlebox silently dropping the connection,
72
+ # the server being killed) can look identical to an idle one from here
73
+ # -- both send nothing. After this many seconds of silence the bridge
74
+ # sends its own PING; if nothing at all comes back within
75
+ # ping_timeout_seconds after that, the link is declared dead and
76
+ # reconnected. Tune ping_idle_seconds down if your network pings less
77
+ # often than the default assumes.
78
+ ping_idle_seconds: 120
79
+ ping_timeout_seconds: 60
80
+
81
+ # Mesh channel index -> IRC channel mapping. Each mesh channel maps to
82
+ # exactly one IRC channel; several mesh channels may map to the same IRC
83
+ # channel. To create or rename a channel on the radio itself, use
84
+ # meshcorectl (https://github.com/Faradome/meshcorectl):
85
+ # meshcorectl create channel 0 "General"
86
+ # meshcorectl get channels
87
+ channels:
88
+ - mesh_channel: 0
89
+ irc_channel: "#mesh-general"
90
+ - mesh_channel: 1
91
+ irc_channel: "#mesh-emergency"
@@ -0,0 +1,92 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "meshcore-irc-bridge"
7
+ version = "0.1.0"
8
+ description = "A one-way bridge relaying MeshCore companion radio channel messages into IRC channels"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ authors = [
13
+ { name = "William Canterbury", email = "william.canterbury@gmail.com" },
14
+ ]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "Operating System :: OS Independent",
18
+ "Environment :: Console",
19
+ "Topic :: Communications :: Ham Radio",
20
+ "Topic :: Communications :: Chat :: Internet Relay Chat",
21
+ ]
22
+ dependencies = [
23
+ "meshcore>=2.3.9",
24
+ "bleak>=0.22",
25
+ "pyserial>=3.5",
26
+ "pyyaml>=6.0",
27
+ ]
28
+
29
+ [project.optional-dependencies]
30
+ dev = [
31
+ "pytest>=8.0",
32
+ "pytest-cov>=5.0",
33
+ "pytest-asyncio>=0.23",
34
+ "ruff>=0.6",
35
+ "mypy>=1.10",
36
+ "types-PyYAML",
37
+ "types-pyserial",
38
+ "build>=1.0",
39
+ ]
40
+
41
+ [project.urls]
42
+ Homepage = "https://github.com/Faradome/meshcore-irc-bridge"
43
+ Issues = "https://github.com/Faradome/meshcore-irc-bridge/issues"
44
+
45
+ [project.scripts]
46
+ meshcore-irc-bridge = "meshcore_irc_bridge.cli:main"
47
+
48
+ [tool.hatch.build.targets.wheel]
49
+ packages = ["src/meshcore_irc_bridge"]
50
+
51
+ [tool.pytest.ini_options]
52
+ testpaths = ["tests"]
53
+ asyncio_mode = "auto"
54
+ addopts = "--cov=meshcore_irc_bridge --cov-report=term-missing --cov-fail-under=100"
55
+
56
+ [tool.coverage.run]
57
+ source = ["meshcore_irc_bridge"]
58
+ branch = true
59
+ # A 3-line launcher shim, exercised for real by a subprocess smoke test
60
+ # (tests/cli/test_cli.py) -- subprocess coverage isn't worth the
61
+ # `COVERAGE_PROCESS_START` machinery for something this thin.
62
+ omit = ["*/__main__.py"]
63
+
64
+ [tool.coverage.report]
65
+ exclude_lines = [
66
+ "pragma: no cover",
67
+ "if TYPE_CHECKING:",
68
+ "raise NotImplementedError",
69
+ ]
70
+
71
+ [tool.ruff]
72
+ line-length = 100
73
+ target-version = "py310"
74
+
75
+ [tool.ruff.lint]
76
+ select = ["E", "F", "I", "UP", "B"]
77
+
78
+ [tool.mypy]
79
+ python_version = "3.10"
80
+ files = ["src"]
81
+ warn_unused_ignores = true
82
+ warn_redundant_casts = true
83
+ no_implicit_optional = true
84
+ check_untyped_defs = true
85
+
86
+ [[tool.mypy.overrides]]
87
+ module = "meshcore.*"
88
+ ignore_missing_imports = true
89
+
90
+ [[tool.mypy.overrides]]
91
+ module = "serial.*"
92
+ ignore_missing_imports = true
@@ -0,0 +1,4 @@
1
+ from meshcore_irc_bridge.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())