mt4ctl 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.
@@ -0,0 +1,42 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ concurrency:
10
+ group: ${{ github.workflow }}-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ test:
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ python-version: ["3.11", "3.12", "3.13"]
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+
23
+ - name: Set up Python ${{ matrix.python-version }}
24
+ uses: actions/setup-python@v5
25
+ with:
26
+ python-version: ${{ matrix.python-version }}
27
+ cache: pip
28
+
29
+ - name: Install
30
+ run: pip install -e ".[dev]"
31
+
32
+ - name: Lint (ruff)
33
+ run: ruff check src tests
34
+
35
+ - name: Format check (ruff)
36
+ run: ruff format --check src tests
37
+
38
+ - name: Type-check (mypy)
39
+ run: mypy
40
+
41
+ - name: Test (pytest)
42
+ run: pytest --cov=mt4ctl --cov-report=term-missing
@@ -0,0 +1,75 @@
1
+ name: Release
2
+
3
+ # Tag a release to publish: git tag v0.1.0 && git push origin v0.1.0
4
+ on:
5
+ push:
6
+ tags: ["v*"]
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ build:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: "3.12"
20
+
21
+ - name: Verify tag matches project version
22
+ run: |
23
+ TAG="${GITHUB_REF_NAME#v}"
24
+ VER=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
25
+ echo "tag=$TAG project=$VER"
26
+ test "$TAG" = "$VER" || { echo "::error::tag v$TAG does not match pyproject version $VER"; exit 1; }
27
+
28
+ - name: Build sdist and wheel
29
+ run: |
30
+ python -m pip install --upgrade build twine
31
+ python -m build
32
+ python -m twine check dist/*
33
+
34
+ - uses: actions/upload-artifact@v4
35
+ with:
36
+ name: dist
37
+ path: dist/
38
+
39
+ publish-pypi:
40
+ needs: build
41
+ runs-on: ubuntu-latest
42
+ # Must match the environment registered with the PyPI Trusted Publisher.
43
+ environment:
44
+ name: pypi
45
+ url: https://pypi.org/p/mt4ctl
46
+ permissions:
47
+ id-token: write # OIDC token for PyPI Trusted Publishing (no API token needed)
48
+ steps:
49
+ - uses: actions/download-artifact@v4
50
+ with:
51
+ name: dist
52
+ path: dist/
53
+
54
+ - name: Publish to PyPI
55
+ uses: pypa/gh-action-pypi-publish@release/v1
56
+
57
+ github-release:
58
+ needs: build
59
+ runs-on: ubuntu-latest
60
+ permissions:
61
+ contents: write # create the GitHub Release and attach artifacts
62
+ steps:
63
+ - uses: actions/download-artifact@v4
64
+ with:
65
+ name: dist
66
+ path: dist/
67
+
68
+ - name: Create GitHub Release
69
+ env:
70
+ GH_TOKEN: ${{ github.token }}
71
+ run: |
72
+ gh release create "${GITHUB_REF_NAME}" dist/* \
73
+ --repo "${GITHUB_REPOSITORY}" \
74
+ --title "${GITHUB_REF_NAME}" \
75
+ --generate-notes
@@ -0,0 +1,35 @@
1
+ # --- private infrastructure: never commit real hosts/accounts/secrets ---
2
+ /terminals.yaml
3
+ terminals.local.yaml
4
+ credentials.json
5
+ *.secret
6
+ .env
7
+ .env.*
8
+
9
+ # --- python ---
10
+ venv/
11
+ .venv/
12
+ __pycache__/
13
+ *.py[cod]
14
+ *.egg-info/
15
+ build/
16
+ dist/
17
+ .eggs/
18
+
19
+ # --- tooling caches ---
20
+ .pytest_cache/
21
+ .mypy_cache/
22
+ .ruff_cache/
23
+ .coverage
24
+ htmlcov/
25
+ coverage.xml
26
+
27
+ # --- local scratch ---
28
+ .cache/
29
+ *.png
30
+ plans/
31
+
32
+ # --- editors / OS ---
33
+ .idea/
34
+ .vscode/
35
+ .DS_Store
@@ -0,0 +1,17 @@
1
+ # Run `pre-commit install` once; hooks then run on every commit.
2
+ repos:
3
+ - repo: https://github.com/astral-sh/ruff-pre-commit
4
+ rev: v0.15.14
5
+ hooks:
6
+ - id: ruff
7
+ args: [--fix]
8
+ - id: ruff-format
9
+
10
+ - repo: https://github.com/pre-commit/pre-commit-hooks
11
+ rev: v5.0.0
12
+ hooks:
13
+ - id: end-of-file-fixer
14
+ - id: trailing-whitespace
15
+ - id: check-yaml
16
+ - id: check-added-large-files
17
+ - id: detect-private-key
@@ -0,0 +1,30 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres
5
+ to [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] - 2026-05-23
10
+
11
+ Initial release.
12
+
13
+ ### Added
14
+
15
+ - MCP stdio server exposing six tools: `mt4_list`, `mt4_status`, `mt4_logs`,
16
+ `mt4_screenshot`, `mt4_control`, `mt4_login`.
17
+ - YAML registry with validation; supports native Linux and WSL2 hosts.
18
+ - Concurrent, per-terminal status with cgroup-based broker-connection attribution
19
+ (gated on root or matching service user, otherwise reported as unknown).
20
+ - Headless first-login bootstrap with process-group-scoped cleanup and
21
+ credential shredding via a cleanup trap.
22
+ - Live-trading guardrails (`confirm=true`) on mutating operations.
23
+ - Credential resolution chain (argument → env var → secrets file) with a
24
+ permission check on the secrets file.
25
+ - Human-friendly entry point (`--help`/`--version` and a TTY guard) plus
26
+ fail-fast config resolution and actionable SSH-failure errors.
27
+ - Test suite, strict `mypy`, `ruff`, and a 3.11–3.13 CI matrix.
28
+
29
+ [Unreleased]: https://github.com/ak40u/mt4ctl/compare/v0.1.0...HEAD
30
+ [0.1.0]: https://github.com/ak40u/mt4ctl/releases/tag/v0.1.0
@@ -0,0 +1,63 @@
1
+ # Contributing
2
+
3
+ Thanks for your interest in `mt4ctl`.
4
+
5
+ ## Dev setup
6
+
7
+ ```bash
8
+ python -m venv venv && source venv/bin/activate
9
+ pip install -e ".[dev]"
10
+ ```
11
+
12
+ ## Before opening a PR
13
+
14
+ All three must pass (CI enforces them on Python 3.11–3.13):
15
+
16
+ ```bash
17
+ ruff check src tests
18
+ mypy
19
+ pytest
20
+ ```
21
+
22
+ ## Conventions
23
+
24
+ - **Keep the core network-free.** Shell logic belongs in `scripts.py` /
25
+ `login.py` as pure builders; `ssh.py` is the only module that executes
26
+ commands. New behavior should be testable without a live host.
27
+ - **Type everything.** `mypy` runs in strict mode.
28
+ - **Actionable errors.** Raise the typed errors in `errors.py`; messages should
29
+ tell the caller how to recover.
30
+ - **Never log secrets.** Passwords flow only through `auth.py` and the transient
31
+ remote login config, which is shredded after use.
32
+ - **Conventional commits** (`feat:`, `fix:`, `docs:`, `refactor:`, `test:`,
33
+ `chore:`).
34
+
35
+ ## Releasing
36
+
37
+ Tagging a version publishes to **PyPI** via Trusted Publishing (OIDC — no stored
38
+ tokens) and creates a **GitHub Release** with the wheel + sdist attached.
39
+
40
+ One-time PyPI setup (account owner): add a pending publisher at
41
+ https://pypi.org/manage/account/publishing/ for project `mt4ctl`, owner `ak40u`,
42
+ repo `mt4ctl`, workflow `release.yml`, environment `pypi`.
43
+
44
+ To cut a release:
45
+
46
+ 1. Bump `version` in `pyproject.toml` and `__version__` in
47
+ `src/mt4ctl/__init__.py`; update `CHANGELOG.md`.
48
+ 2. Commit, then tag and push:
49
+
50
+ ```bash
51
+ git tag v0.1.0 && git push origin v0.1.0
52
+ ```
53
+
54
+ `release.yml` verifies the tag matches the project version, builds and
55
+ `twine check`s the artifacts, publishes to PyPI, and creates the GitHub Release.
56
+
57
+ ## Adding a tool
58
+
59
+ 1. Implement the operation in `operations.py` (or a focused module) against the
60
+ `Registry`.
61
+ 2. Add a thin `@mcp.tool()` wrapper in `server.py` with a clear docstring.
62
+ 3. Cover the parsing / command construction with unit tests.
63
+ 4. Document it in `docs/tools.md`.
mt4ctl-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pavel Volkov
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.
mt4ctl-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,266 @@
1
+ Metadata-Version: 2.4
2
+ Name: mt4ctl
3
+ Version: 0.1.0
4
+ Summary: MCP server for managing headless MetaTrader terminals over SSH (Wine + systemd)
5
+ Project-URL: Homepage, https://github.com/ak40u/mt4ctl
6
+ Project-URL: Repository, https://github.com/ak40u/mt4ctl
7
+ Project-URL: Issues, https://github.com/ak40u/mt4ctl/issues
8
+ Author-email: Pavel Volkov <pvolkov@live.ru>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: devops,mcp,metatrader,model-context-protocol,mt4,ssh,systemd,trading,wine
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Office/Business :: Financial :: Investment
19
+ Classifier: Topic :: System :: Systems Administration
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.11
22
+ Requires-Dist: mcp>=1.2
23
+ Requires-Dist: pyyaml>=6.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: mypy>=1.11; extra == 'dev'
26
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
27
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
28
+ Requires-Dist: pytest>=8.0; extra == 'dev'
29
+ Requires-Dist: ruff>=0.6; extra == 'dev'
30
+ Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
31
+ Description-Content-Type: text/markdown
32
+
33
+ <div align="center">
34
+
35
+ # mt4ctl
36
+
37
+ **An MCP server for operating headless MetaTrader terminals — over SSH, from your agent.**
38
+
39
+ Manage MetaTrader 4 terminals running under **Wine + systemd** on remote hosts
40
+ (native Linux *or* WSL2) entirely through the [Model Context Protocol](https://modelcontextprotocol.io):
41
+ check status, read logs, capture screenshots, control the systemd lifecycle, and
42
+ perform the tricky **headless first-login** — all as clean, typed tools.
43
+
44
+ [![CI](https://github.com/ak40u/mt4ctl/actions/workflows/ci.yml/badge.svg)](https://github.com/ak40u/mt4ctl/actions/workflows/ci.yml)
45
+ [![PyPI](https://img.shields.io/pypi/v/mt4ctl)](https://pypi.org/project/mt4ctl/)
46
+ [![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/)
47
+ [![MCP](https://img.shields.io/badge/MCP-server-7C3AED)](https://modelcontextprotocol.io)
48
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
49
+
50
+ </div>
51
+
52
+ ---
53
+
54
+ ## Why
55
+
56
+ Algo traders increasingly run MetaTrader 4 **headless on Linux** — Wine under
57
+ `Xvfb`, supervised by `systemd`, no GUI. That's great for uptime and terrible for
58
+ day-to-day operations: every "is it connected?", "restart that one", or "log this
59
+ new account in" turns into a fragile chain of
60
+ `ssh → (Windows cmd → wsl) → bash → systemctl → wine`, with quoting hazards at
61
+ every hop.
62
+
63
+ `mt4ctl` collapses that chain into a handful of MCP tools. Point it at a registry
64
+ of your hosts and terminals, wire it into Claude (or any MCP client), and operate
65
+ the whole farm conversationally:
66
+
67
+ > *"Which demo terminals are down?"* · *"Restart demo2."* ·
68
+ > *"Log demo2 into account 1000002 on ExampleBroker-Demo."* ·
69
+ > *"Screenshot the live terminal so I can see the AutoTrading state."*
70
+
71
+ ## Quickstart (5 minutes)
72
+
73
+ `mt4_list` works **offline** (no SSH/MT4 needed), so you can confirm the wiring
74
+ before anything else lines up:
75
+
76
+ ```bash
77
+ # 1. create a minimal registry
78
+ mkdir -p ~/.config/mt4ctl
79
+ cat > ~/.config/mt4ctl/terminals.yaml <<'YAML'
80
+ hosts:
81
+ box: { ssh: my-ssh-alias, kind: native }
82
+ terminals:
83
+ t1: { host: box, service: mt4-t1, data_dir: /home/trader/mt4/t1, account: "1000001" }
84
+ YAML
85
+
86
+ # 2. add to Claude Code (uvx runs the server straight from git — no install)
87
+ claude mcp add --scope user mt4ctl \
88
+ --env MT4CTL_CONFIG="$HOME/.config/mt4ctl/terminals.yaml" \
89
+ -- uvx --from git+https://github.com/ak40u/mt4ctl mt4ctl
90
+ ```
91
+
92
+ Then ask Claude: **"Use mt4_list to show my configured terminals."** You should
93
+ see your `t1` row. Once the SSH alias and `systemd` unit line up, ask for
94
+ **"mt4_status t1"**. Full setup and other clients are below.
95
+
96
+ ## Features
97
+
98
+ - **Per-terminal connection detection** — attributes established broker sockets
99
+ to each terminal's `systemd` cgroup, so terminals sharing a host (and a Wine
100
+ prefix) are reported independently — not guessed from a host-wide count.
101
+ - **Headless first-login** — automates the one-time bootstrap a migrated terminal
102
+ needs (MetaTrader's saved password is machine-bound), then hands control back
103
+ to `systemd` for automatic reconnection on every restart.
104
+ - **Native *and* WSL2 hosts** — one registry, two execution models; commands are
105
+ base64-shipped so nothing breaks in the `cmd.exe → wsl.exe → bash` gauntlet.
106
+ - **Live-trading guardrails** — terminals tagged `env: live` reject mutating
107
+ operations unless you pass `confirm=true`.
108
+ - **Concurrent status** — hosts are polled in parallel via `asyncio`.
109
+ - **Secrets stay secret** — passwords resolve from arg → env → secrets file,
110
+ are never logged, and the transient remote login config is `shred`-ed after use.
111
+
112
+ ## How it works
113
+
114
+ ```
115
+ ┌────────────┐ MCP/stdio ┌──────────────────┐
116
+ │ MCP client │ ────────────► │ mt4ctl │
117
+ │ (Claude…) │ │ FastMCP server │
118
+ └────────────┘ └────────┬─────────┘
119
+ │ asyncio SSH (base64-framed)
120
+ ┌─────────────────────┼─────────────────────┐
121
+ ▼ ▼
122
+ ┌─────────────────┐ ┌──────────────────┐
123
+ │ native Linux │ │ Windows + WSL2 │
124
+ │ sudo systemctl │ │ wsl -u root -- │
125
+ ├─────────────────┤ ├──────────────────┤
126
+ │ mt4-live-main… │ systemd units running │ mt4-demo1… │
127
+ │ wine terminal.exe (Xvfb display) │ wine terminal.exe│
128
+ └─────────────────┘ └──────────────────┘
129
+ ```
130
+
131
+ A thin, typed core (`models` → `config` → `ssh` → `scripts` → `operations`/`login`)
132
+ sits under the `server` adapter, so the logic is testable without a network and
133
+ the MCP layer stays a one-line-per-tool shell.
134
+
135
+ ## Install
136
+
137
+ The fastest path needs no clone and no global install — [`uv`](https://docs.astral.sh/uv/)
138
+ runs `mt4ctl` straight from the repo and fetches a matching Python itself:
139
+
140
+ ```bash
141
+ uvx --from git+https://github.com/ak40u/mt4ctl mt4ctl # runs the stdio server
142
+ ```
143
+
144
+ No `uv` yet? `curl -LsSf https://astral.sh/uv/install.sh | sh` — or skip it and use
145
+ the `pipx` path below.
146
+
147
+ Prefer a persistent `mt4ctl` command? Install it with `uv` or `pipx`:
148
+
149
+ ```bash
150
+ uv tool install git+https://github.com/ak40u/mt4ctl
151
+ # or
152
+ pipx install git+https://github.com/ak40u/mt4ctl
153
+ ```
154
+
155
+ For development:
156
+
157
+ ```bash
158
+ git clone https://github.com/ak40u/mt4ctl.git && cd mt4ctl
159
+ python -m venv venv && source venv/bin/activate
160
+ pip install -e ".[dev]"
161
+ ```
162
+
163
+ The server machine needs either `uv` or Python 3.11+, plus SSH access to your
164
+ hosts. The remote hosts need the usual tools `mt4ctl` shells out to: `systemctl`,
165
+ `ss`, `getent`, and (for screenshots) `imagemagick`/`scrot` + `xdotool`.
166
+
167
+ ## Configure
168
+
169
+ Copy the example registry and fill in your real hosts and terminals:
170
+
171
+ ```bash
172
+ mkdir -p ~/.config/mt4ctl
173
+ cp examples/terminals.example.yaml ~/.config/mt4ctl/terminals.yaml
174
+ ```
175
+
176
+ The registry is resolved from `MT4CTL_CONFIG`, then
177
+ `~/.config/mt4ctl/terminals.yaml`, then `./terminals.yaml`. See
178
+ [`examples/terminals.example.yaml`](examples/terminals.example.yaml) for the full
179
+ schema and [`docs/configuration.md`](docs/configuration.md) for details.
180
+
181
+ > **Keep your populated registry private.** It maps your accounts and
182
+ > infrastructure. The default `.gitignore` excludes `terminals.yaml`.
183
+
184
+ ## Connect to an MCP client
185
+
186
+ **Claude Code** — one command wires it up (user scope = available in every project):
187
+
188
+ ```bash
189
+ claude mcp add --scope user mt4ctl \
190
+ --env MT4CTL_CONFIG="$HOME/.config/mt4ctl/terminals.yaml" \
191
+ -- uvx --from git+https://github.com/ak40u/mt4ctl mt4ctl
192
+ ```
193
+
194
+ Or commit a project `.mcp.json` to share with a team (Claude Code expands `${HOME}`):
195
+
196
+ ```json
197
+ {
198
+ "mcpServers": {
199
+ "mt4ctl": {
200
+ "command": "uvx",
201
+ "args": ["--from", "git+https://github.com/ak40u/mt4ctl", "mt4ctl"],
202
+ "env": { "MT4CTL_CONFIG": "${HOME}/.config/mt4ctl/terminals.yaml" }
203
+ }
204
+ }
205
+ }
206
+ ```
207
+
208
+ **Claude Desktop** — Settings → Developer → Edit Config (`claude_desktop_config.json`),
209
+ same shape but use an **absolute** config path (Desktop does not expand `${HOME}`),
210
+ and an absolute `command` path if `uvx` is not on the GUI app's `PATH` (`which uvx`):
211
+
212
+ ```json
213
+ {
214
+ "mcpServers": {
215
+ "mt4ctl": {
216
+ "command": "uvx",
217
+ "args": ["--from", "git+https://github.com/ak40u/mt4ctl", "mt4ctl"],
218
+ "env": { "MT4CTL_CONFIG": "/Users/you/.config/mt4ctl/terminals.yaml" }
219
+ }
220
+ }
221
+ }
222
+ ```
223
+
224
+ > Installed `mt4ctl` persistently (uv/pipx)? Replace `command`/`args` with just
225
+ > `"command": "mt4ctl"`.
226
+
227
+ ## Tools
228
+
229
+ | Tool | Mutates | Description |
230
+ | --- | :---: | --- |
231
+ | `mt4_list` | – | List configured terminals (offline). |
232
+ | `mt4_status` | – | Per-terminal service state + broker connection + log age. |
233
+ | `mt4_logs` | – | Tail / grep a terminal's newest log file. |
234
+ | `mt4_screenshot` | – | Capture a terminal window as PNG. |
235
+ | `mt4_control` | ✓ | `start` / `stop` / `restart` a unit (live needs `confirm`). |
236
+ | `mt4_login` | ✓ | One-time headless login for auto-reconnect (live needs `confirm`). |
237
+
238
+ Full reference: [`docs/tools.md`](docs/tools.md).
239
+
240
+ ## Security
241
+
242
+ - Mutations on `env: live` terminals require explicit `confirm=true`.
243
+ - Credentials resolve from argument → `MT4CTL_PASSWORD_<account>` →
244
+ secrets file; they are never written to logs and the transient remote login
245
+ config is shredded after use.
246
+ - All remote execution goes through your existing SSH config and key-based auth;
247
+ `mt4ctl` stores no credentials of its own.
248
+ - During `mt4_login` the password is embedded in the base64-framed script handed
249
+ to `ssh`, so it is briefly visible in the local process list to your own user.
250
+ On the remote side it is written only to a fresh `mktemp` config (mode 600) that
251
+ a cleanup trap `shred`s on any exit path. On POSIX, the local secrets file is
252
+ rejected if it is readable by group/other.
253
+
254
+ ## Development
255
+
256
+ ```bash
257
+ ruff check src tests # lint
258
+ mypy # type-check (strict)
259
+ pytest # tests
260
+ ```
261
+
262
+ See [`docs/architecture.md`](docs/architecture.md) for the module boundaries.
263
+
264
+ ## License
265
+
266
+ MIT © Pavel Volkov. See [LICENSE](LICENSE).