diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 2fa88e5..faadafa 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -5,30 +5,30 @@ body: - type: markdown attributes: value: | - Hopefully you never need this form. + We hope that you never need this form. - If you do: start by running this, and attach the file it writes. + If you do need it, first run this command and attach the file that it writes. ``` meshterm diagnostics --out bug.md ``` - It answers which MeshTerm, on which machine, in which terminal, talking to which - radio over which transport and firmware — most of what I would otherwise ask one - question at a time, and the details people most often end up guessing at. Nothing - in it is private: no keys, no pairing PINs, no positions, no contacts. That is a - tested property, not a promise. + The file says which MeshTerm you use, on which machine, in which terminal, and with + which radio, transport, and firmware. This is most of the information that I would + otherwise ask for in single questions. It is also the information that people most + often guess. The file has no private information: no keys, no pairing PINs, no + positions, and no contacts. A test checks this. It is not only a promise. - The **Diagnostics** page in the menu saves the same file (with `s`, or F3 on the - PicoCalc), next to the log. + The **Diagnostics** page in the menu saves the same file next to the log. Press `s`, + or F3 on the PicoCalc. - The rest of this form asks only for what that file cannot know. + The rest of this form asks only for the information that the file cannot know. - type: textarea id: problem attributes: - label: What's the problem - description: What happens, and what you expected to happen instead. + label: What is the problem + description: What happens, and what you expected to happen. validations: required: true @@ -47,11 +47,12 @@ body: - type: textarea id: repro attributes: - label: How do I make it happen + label: How can I make it happen description: > - The steps, in order, starting from launching MeshTerm. Screens and keys beat - descriptions — "Contacts, Enter on a node, Routes tab, Enter on the second route" - is worth more than "open a trace". + Write the steps in order. Start from the time that you start MeshTerm. Name the + screens and the keys. This is more useful than a description. For example, + "Contacts, Enter on a node, Routes tab, Enter on the second route" is better than + "open a trace". placeholder: | 1. 2. @@ -64,15 +65,16 @@ body: attributes: label: Does it still happen with `meshterm --mock` description: > - `--mock` runs the whole app against a fake radio. If the bug survives that, it is - in MeshTerm and I can chase it without owning your hardware. If it doesn't, it is - in the link or the radio, which is a different hunt. This one question decides - which of those two jobs this is, so it is worth the minute it takes. + `--mock` runs the complete app with a fake radio. If the bug also occurs with + `--mock`, the bug is in MeshTerm, and I can look for it without your hardware. If + the bug does not occur with `--mock`, it is in the link or in the radio. This is a + different search. This one question tells me which of the two jobs this is, so + please take the minute to answer it. options: - "Yes, still happens with --mock" - "No, --mock is fine" - - "Haven't tried" - - "Doesn't apply — the problem is connecting" + - "I have not tried it" + - "Does not apply: the problem is the connection" validations: required: true @@ -81,9 +83,9 @@ body: attributes: label: The diagnostics block description: > - Attach `bug.md` by dragging it in, or paste its contents here. If you genuinely - cannot produce one, because MeshTerm will not start at all, write down why instead, - along with your version, and I will work with that. + Drag `bug.md` into this field, or paste its contents here. If you cannot make the + file, because MeshTerm does not start at all, write the reason instead. Also write + your version. I will work with that information. validations: required: true @@ -92,9 +94,9 @@ body: attributes: label: Which handheld, if it is a handheld description: > - Only if you are running on something the diagnostics file cannot name. It knows a - PicoCalc from a regular terminal, but a uConsole and a laptop look much the same to - it. Leave blank on a desktop, laptop, or Mac. + Answer this only if you use a device that the diagnostics file cannot name. The file + can tell a PicoCalc from a regular terminal. But a uConsole and a laptop look almost + the same to it. Leave this blank on a desktop, a laptop, or a Mac. placeholder: "ClockworkPi uConsole (CM4)" - type: input @@ -102,10 +104,10 @@ body: attributes: label: Which radio and firmware, if MeshTerm could not reach it description: > - The diagnostics file reads the model and firmware off the radio itself, so when the - problem *is* connecting, it has nothing to read. That is the one case where I need - you to tell me. Be precise — "a Xiao" covers several boards that do not behave - alike. + The diagnostics file reads the model and the firmware from the radio itself. When + the problem is the connection, the file has nothing to read. In this one case, I + need you to tell me. Be exact. "A Xiao" can mean several boards that do not behave + in the same way. placeholder: "Seeed XIAO nRF52840 + Wio-SX1262, MeshCore 1.15.0" - type: textarea @@ -113,8 +115,9 @@ body: attributes: label: The exact error, if there was one description: > - Copy it as text rather than photographing the screen — text can be searched, and a - photo of a terminal is hard to read. Paste the whole traceback if it crashed. + Copy it as text. Do not take a photograph of the screen. A search can find text, and + a photograph of a terminal is difficult to read. If MeshTerm crashed, paste the + complete traceback. render: text - type: textarea @@ -122,22 +125,22 @@ body: attributes: label: The log file description: > - MeshTerm keeps one at `~/.meshterm/meshterm.log` (or wherever `MESHTERM_HOME` - points). Attach the file, or paste the last twenty lines inside a ``` fence. If the - problem is hard to catch, set **Log detail** to Debug on the Preferences page, - restart MeshTerm, do it once more, and send that instead. It records a great deal - more. + MeshTerm keeps a log at `~/.meshterm/meshterm.log` (or at the place that + `MESHTERM_HOME` points to). Attach the file, or paste the last twenty lines inside a + ``` fence. If the problem is difficult to catch, set **Log detail** to Debug on the + Preferences page and restart MeshTerm. Then do the action one more time, and send + that log instead. It records much more information. - type: textarea id: workaround attributes: label: Is there a way around it - description: Anything that avoids it, or gets you unstuck. Say so even if it's ugly. + description: Write anything that avoids the problem or lets you continue. Tell us also when the way around is not good. - type: textarea id: extra attributes: label: Anything else description: > - Did it used to work? Did it start after an update? Screenshots of a wrong-looking - screen are genuinely useful here, even though text is better for errors. + Did it work before? Did the problem start after an update? Screenshots of a screen + that looks wrong are very useful here. For errors, text is better. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 5e7ce21..8294656 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,15 +1,16 @@ blank_issues_enabled: false contact_links: - - name: Not sure it's a bug? Ask on Discord + - name: Not sure it is a bug? Ask on Discord url: https://discord.gg/AZwe5Uvb3S about: > - Setup trouble, "is it supposed to do this", or anything you want a second opinion - on. The #support forum is the place. Someone running your radio has probably hit it. + Use Discord for a problem with the setup, for the question "is it correct that it does + this", or when you want a second opinion. The #support forum is the correct place. + Probably someone who runs your radio had the same problem. - name: "No GitHub account? Report it in the #bugs forum instead" url: https://discord.gg/AZwe5Uvb3S about: > - One post per bug, tagged, with the diagnostics file attached. Real ones get opened - here and your post is renamed with the issue number, so you can follow it without an - account — and something comes back to tell you when it ships. Being told beats - filing it tidily. + Make one post for each bug. Add a tag, and attach the diagnostics file. I open an issue + here for each real bug, and I rename your post with the number of the issue. Thus you can + follow it without an account. You also get a message when the correction is released. + It is better that you tell us about a bug than that you file it in a tidy way. diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml index d2abe42..c3a9087 100644 --- a/.github/ISSUE_TEMPLATE/feature.yml +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -5,15 +5,15 @@ body: - type: markdown attributes: value: | - MeshTerm is one person's spare time, so the honest answer to a good idea is often - "yes, eventually". Saying what you are actually trying to do — rather than the - feature you have in mind — often finds something that already works. + One person works on MeshTerm in his spare time. For this reason, the honest answer to a + good idea is often "yes, but not soon". Tell us what you want to do, and not the + feature that you have in mind. Then we can often find a function that already works. - type: textarea id: problem attributes: - label: What are you trying to do - description: The situation you are in, not the feature. What are you trying to find out about your mesh? + label: What do you want to do + description: Describe your situation, and not the feature. What do you want to find out about your mesh? validations: required: true @@ -21,10 +21,10 @@ body: id: idea attributes: label: What you had in mind - description: If you have a shape in mind — a screen, a key, a subcommand — describe it. + description: If you have an idea of the form, such as a screen, a key, or a subcommand, describe it. - type: textarea id: today attributes: label: What you do today instead - description: The workaround, if there is one. This says more about how much the feature is worth than anything else. + description: Describe the way around the problem, if there is one. This tells us more than anything else about how much the feature is worth. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 3872172..2770fc0 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,30 +1,31 @@ ## What this changes, and why - + ## Discord - + Username: Link to the yes: ## AI use - + ## Before you mark it ready -- [ ] I've read [CONTRIBUTING.md](../blob/main/CONTRIBUTING.md) and agree its licensing terms apply to this contribution -- [ ] I've put my Discord username and a link to the yes above -- [ ] If I used AI, I said so above and in the commits it helped with +- [ ] I have read [CONTRIBUTING.md](../blob/main/CONTRIBUTING.md) and I agree that its licensing terms apply to this contribution +- [ ] I have written my Discord username and a link to the yes above +- [ ] If I used AI, I said so above and in the commits that it helped with - [ ] `python -m pytest -q` passes, including the dual-platform gallery gate -- [ ] `ruff check .` and `ruff format --check .` are clean -- [ ] New screens, rows, and hints go through the helpers in `ui/menus.py`, and follow the +- [ ] `ruff check .` and `ruff format --check .` have no errors +- [ ] New screens, rows, and hints use the helpers in `ui/menus.py`, and follow the lexicon and UX standards in `CLAUDE.md` -- [ ] Anything user-visible has a line in `CHANGELOG.md` under `[Unreleased]` +- [ ] Anything that the user can see has a line in `CHANGELOG.md` under `[Unreleased]` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5d80925..d575516 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,100 +1,104 @@ # Contributing to MeshTerm -Thanks for wanting to work on MeshTerm. A few things to know before you open a pull +Thank you for your wish to work on MeshTerm. Read these notes before you open a pull request. -## Licensing — what happens to your contribution +## Licensing: what happens to your contribution -MeshTerm is licensed under the [Apache License, Version 2.0](LICENSE). By opening a -pull request, you agree to license your contribution under those same terms — and, -going a step beyond what Apache-2.0 alone requires, you additionally grant Jean-Pierre -Martineau (the project's copyright holder) the right to relicense your contribution as -part of the MeshTerm project, should the project's license ever change in the future. -You keep copyright over what you write; you're not signing it away, just granting these -usage rights alongside it. No separate form, bot, or signature is needed — opening the -PR is how you agree. +MeshTerm is licensed under the [Apache License, Version 2.0](LICENSE). When you open a +pull request, you agree to license your contribution under the same terms. You also agree +to one more term that Apache-2.0 alone does not require. You grant Jean-Pierre Martineau +(the project's copyright holder) the right to relicense your contribution as part of +the MeshTerm project, if the project's license changes in the future. +You keep the copyright of what you write. You do not give it away. You only grant these +rights of use in addition to it. You do not need a separate form, bot, or signature. When +you open the pull request, you agree. -If that's not something you're comfortable granting, say so on the PR before it's -merged and we'll talk about it, but by default that's the deal for anything merged into -this repository. +If you are not comfortable with this grant, say so on the pull request before we merge it, +and we will talk about it. If you do not say so, these are the terms for each contribution +that we merge into this repository. ## Before you start -- Read [CLAUDE.md](CLAUDE.md). It isn't just instructions for AI assistants — it's the - actual style guide for this codebase: the terminology (node vs. contact, heard vs. - seen, etc.), UX standards for screens/dialogs/menus, and where the reusable building - blocks live in code (`ui/menus.py`, `ui/markdown.py`, `ui/widgets.py`, `ui/theme.py`). - New code should read like it always belonged here. -- Tell us before you start working on something (see below). For anything bigger than a - small fix, expect to talk it through first. That saves everyone a rewrite. +- Read [CLAUDE.md](CLAUDE.md). It is not only instructions for AI assistants. It is the + real style guide for this codebase. It gives the terms (node and contact, heard and + seen, and other terms), the UX standards for screens, dialogs, and menus, and the place + in the code of each building block that you can use again (`ui/menus.py`, + `ui/markdown.py`, `ui/widgets.py`, `ui/theme.py`). New code must look like it was always + part of the project. +- Tell us before you start work on a change (refer to the sections below). If a change is + bigger than a small correction, expect to talk about it first. This saves everyone from + a rewrite. ## Who can contribute -If you use MeshTerm, you're welcome here. Maybe something broke for you, or you wished -it could do one more thing, or a screen looked wrong on your radio. Those are exactly -the changes we want. +If you use MeshTerm, you are welcome here. Maybe something did not work for you. Maybe you +wanted one more function. Maybe a screen looked wrong on your radio. We want exactly these +changes. ### How MeshTerm was made -Some people in the MeshCore community don't trust code written by AI. After some of -what has happened lately, that's easy to understand, and it deserves a straight answer. +Some persons in the MeshCore community do not trust code that AI wrote. After some recent +events, we understand this, and it needs a direct answer. -Jean-Pierre Martineau, who started MeshTerm, sees it differently. AI was a big help on -this project. Honestly, MeshTerm couldn't have been built without it. But the choices -were his. He decided what MeshTerm should be, what it should do, and how it should -look. He uses it every day. It's handmade, with AI's help. +Jean-Pierre Martineau started MeshTerm. He has a different opinion. AI helped much with +this project. Honestly, it was not possible to build MeshTerm without it. But he made the +choices. He decided what MeshTerm must be, what it must do, and how it must look. He uses +it each day. It is handmade, with the help of AI. -The project wants to stay that way. That's what the rules below are for. +The project must stay this way. The rules below are for this purpose. ### Come say hello first -Using AI tools to help you work is fine. What matters is that a real person who cares -about MeshTerm is behind the change. +It is permitted to use AI tools to help you work. What is important is that a real person +who cares about MeshTerm is behind the change. -The MeshTerm community lives on [Discord](https://discord.gg/AZwe5Uvb3S), and everyone -is welcome to join. But please join before you change things. Before you start writing -code, say hello in the **#contributing** channel and tell us what you'd like to work on. -Then wait until we say yes. This goes for small fixes too. +The MeshTerm community is on [Discord](https://discord.gg/AZwe5Uvb3S), and everyone is +welcome to join. But join before you change anything. Before you start to write code, +say hello in the **#contributing** channel and tell us what you want to work on. Then wait +until we say yes. This is also the rule for small corrections. -When you open your pull request, include two things: +When you open your pull request, include these two items: -- **Your Discord username**, the one you use on the MeshTerm server. +- **Your Discord username.** Use the one that you use on the MeshTerm server. - **A link to the message where we said yes.** In Discord, right-click the message and choose "Copy Message Link." -If you haven't joined the Discord, or nobody said yes to your idea, your pull request -will be closed without a review. +If you did not join the Discord, or if nobody said yes to your idea, we will close your +pull request without a review. ### No bots -Some accounts use AI to find open issues, write a fix, and open a pull request, all on -their own. There's no person behind it who read the change or can answer questions -about it. People call this "AI slop." We close those pull requests without a review, -even if the code looks fine. The same goes for issues and bug reports written that way. +Some accounts use AI to find open issues, write a correction, and open a pull request, all +by themselves. No person read the change, and no person can answer questions about it. +People call this "AI slop." We close these pull requests without a review, also when the +code looks correct. The same rule is true for issues and bug reports that are written in +this way. ### Tell us if you used AI -This part is required. If an AI tool wrote any part of your change, say so in two +This part is necessary. If an AI tool wrote any part of your change, you must say so in two places: -- **In each commit it helped with.** Add a line at the end of the commit message naming - the tool, like `Co-Authored-By: Claude ` or +- **In each commit that it helped with.** Add a line at the end of the commit message with + the name of the tool, for example `Co-Authored-By: Claude ` or `Assisted-by: GitHub Copilot`. -- **In your pull request.** Write a sentence about what the tool did and what you - checked yourself. +- **In your pull request.** Write a sentence about what the tool did and what you checked + yourself. -If you used AI and didn't say so, we'll treat your pull request as a bot's. Saying so -costs you nothing. Either way, you're the author, so be ready to explain your change. +If you used AI and you did not say so, we will treat your pull request as a bot's pull +request. It costs you nothing to say so. In all cases, you are the author, so be ready to +explain your change. ## Making a change -1. Fork the repo and branch off `main`. +1. Fork the repository and make a branch from `main`. 2. Set up a dev install: ```bash pip install -e ".[dev]" git config blame.ignoreRevsFile .git-blame-ignore-revs # skip the formatting sweeps ``` -3. Before opening the PR, make sure these all pass: +3. Before you open the pull request, make sure that all these checks pass: ```bash python -m pytest -q # tests, including tests/test_gallery.py — the dual-platform # readability gate (screens must stay readable at 72 cols @@ -102,29 +106,37 @@ costs you nothing. Either way, you're the author, so be ready to explain your ch ruff check . # lint ruff format --check . # formatting ``` -4. Keep commits focused; write imperative-mood messages and explain *why* when it isn't - obvious from the diff. -5. Open the PR against `main` with a clear description of what changed and why. + The tests include `tests/test_writing_style.py`. It checks the docstrings and the + comments for the marks that ASD-STE100 does not use. Refer to + [How we write](docs/development/writing-style.md). +4. Keep each commit to one subject. Write the commit message in the imperative mood. Explain + *why* when the diff does not show the reason. +5. Open the pull request against `main`. Write a clear description of what changed and why. ## Code style -- Match the surrounding file: naming, comment density, idiom. -- Follow the lexicon and UX standards in [CLAUDE.md](CLAUDE.md) — one term per concept; - don't introduce a synonym for something that already has a name. -- New screens, dialogs, and rows go through the existing helpers rather than hand-rolled - layout — CLAUDE.md names the enforcement points. +- Make your change match the file around it: the names, the amount of comments, and the + idioms. +- Follow the terms and the UX standards in [CLAUDE.md](CLAUDE.md). Use one term for each + concept. Do not add a synonym for something that already has a name. +- Build new screens, dialogs, and rows with the existing helpers. Do not make the layout by + hand. CLAUDE.md names the places where the code enforces these standards. +- Write docstrings, comments, and documents in ASD-STE100 Simplified Technical English, + with Canadian spelling. [How we write](docs/development/writing-style.md) gives the + rules and the glossary. ## Reporting bugs / requesting features -Open a GitHub issue. For bugs, include what you expected, what happened, and enough to -reproduce it — device/platform, MeshCore firmware version if relevant, and steps. +Open a GitHub issue. For a bug, include what you expected, what happened, and enough +information for us to reproduce it. This is the device and platform, the MeshCore firmware +version if it is relevant, and the steps. The [bug form](https://github.com/jpmartineau/MeshTerm/issues/new?template=bug.yml) asks -for all of this. Attach the file that `meshterm diagnostics --out bug.md` writes. No -GitHub account? Post it in the #bugs forum on [Discord](https://discord.gg/AZwe5Uvb3S) -instead. +for all of this. Attach the file that `meshterm diagnostics --out bug.md` writes. If you do +not have a GitHub account, post the report in the #bugs forum on +[Discord](https://discord.gg/AZwe5Uvb3S) instead. --- -Questions about any of this, licensing included, are welcome as a GitHub issue before you -put work in. +You can ask a question about any of this, and this includes licensing, as a GitHub issue +before you start work. diff --git a/README.md b/README.md index b5a6ff6..db49263 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ # MeshTerm -**A full-featured TUI [MeshCore](https://meshcore.io/) client for your terminal** — tune -your radio, chat across the mesh, watch the network live, and keep a longitudinal record -of everything you overhear, all from a connected serial, Bluetooth, or network (TCP) -companion. +**A full-featured TUI [MeshCore](https://meshcore.io/) client for your terminal.** Use it +to tune your radio, chat across the mesh, and watch the network live. It also keeps a +long-term record of everything that you overhear. It works with a companion that is +connected by serial, Bluetooth, or network (TCP). -MeshTerm is interactive by default: a modern, keyboard-driven TUI built on -[Rich](https://github.com/Textualize/rich) and -[prompt_toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit). It is also -fully scriptable — **most screens have a mirror `meshterm` subcommand** — so the same -capabilities drive both an evening of exploring the mesh and a cron job. Every packet the -radio overhears is recorded to a local SQLite database, so the longer you run it, the more -your mesh's history is worth. +MeshTerm is interactive by default. It is a modern TUI that you operate with the keyboard, +built on [Rich](https://github.com/Textualize/rich) and +[prompt_toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit). You can also +use it in scripts. **Most screens have a mirror `meshterm` subcommand.** Thus the same +functions work for an evening of exploration on the mesh and for a cron job. MeshTerm +stores each packet that the radio overhears in a local SQLite database. The longer you +run MeshTerm, the more the history of your mesh is worth.

CI @@ -23,69 +23,74 @@ your mesh's history is worth. Discord

-> MeshTerm is a side project, run by one person. Bug reports are very welcome. Want to -> change something? Say hello on [Discord](https://discord.gg/AZwe5Uvb3S) first. -> [More on how it's run](#how-this-project-is-run). +> MeshTerm is a side project, and one person runs it. We welcome bug reports. If you want to +> change something, first say hello on [Discord](https://discord.gg/AZwe5Uvb3S). +> [Read more about how the project is run](#how-this-project-is-run). https://github.com/user-attachments/assets/f91a6695-14d5-4ae0-8fdc-e65492d0955d -

Under two minutes on a real mesh.

+

A video of less than two minutes, made on a real mesh.

- The dashboard: a packets-per-minute timeline over the last two hours, and every packet class counted - The map: repeaters named over an OpenStreetMap basemap of the Lachine Canal + The dashboard: a timeline of the packets for each minute over the last two hours, and a count for each class of packet + The map: repeaters with their names, on an OpenStreetMap basemap of the Lachine Canal

- A trace: the route it walked drawn as chips, then the SNR of every hop - Message paths: every route a channel message was heard over, drawn on the ground + A trace: the route that the trace went through, drawn as chips, then the SNR of each hop + Message paths: each route over which a channel message was heard, drawn on the map

- MeshTerm on the PicoCalc, a 53-column console with a radio wired inside + MeshTerm on the PicoCalc, a console that is 53 columns wide, with a radio installed inside it

-

The dashboard, the map, a trace, the paths a message took — and the whole thing on a PicoCalc.

+

The dashboard, the map, a trace, the paths of a message, and the whole app on a PicoCalc.

--- ## Why MeshTerm -- **One tool, two faces.** Launch `meshterm` for a full-screen menu; pass a subcommand for - a scripted one-shot. The registry that builds the menu builds the CLI, so they never - drift apart. -- **It remembers.** A passive monitor logs every overheard advert and telemetry frame — - SNR, RSSI, shared location — to SQLite from the moment the radio opens. Maps, contact lists, - charts, and the Time Machine all read back that history. -- **It measures.** Live trace with per-hop reliability, a coarse→refine→verify TX-power - sweep, and a walkable graph of how the mesh actually hangs together. -- **It talks.** Live channel and direct messaging, room servers' message boards, - store-and-forward courier delivery for contacts that aren't there yet, and shareable - channels as QR codes and `meshcore://` links. -- **It connects however your companion does.** Serial (USB), Bluetooth LE, or TCP (a Wi-Fi - board or a network proxy) — the discoverable transports are auto-found and picked - interactively, a network device is named by hand, and every kind reconnects live when a link - drops. It pairs a PIN-protected Bluetooth companion itself on Windows and Linux, and when a - connection fails it says which step failed and what to do. No radio? A built-in simulator - covers development. -- **It works offline.** The street map caches OpenStreetMap tiles to disk and falls back to - a blank grid when there's no network — it never needs the internet to run. +- **One tool, two faces.** To open a full-screen menu, start `meshterm`. To run one scripted + task, give a subcommand. The same registry builds the menu and the CLI, so they do not + become different from one another. +- **It remembers.** A passive monitor stores each advert and each telemetry packet that it + overhears. This includes the SNR, the RSSI, and the shared position. The monitor starts + when the radio opens. The maps, the contact lists, the charts, and the Time Machine all + read this history. +- **It measures.** It has a live trace with the reliability of each hop. It has a TX-power + sweep that goes from coarse to fine and then verifies the result. It also has a graph + that you can walk through, which shows how the mesh is really connected. +- **It talks.** You can send live messages on a channel and direct messages. You can use the + message boards of room servers. A courier stores messages and sends them when a contact + is not yet there. You can share a channel as a QR code or as a `meshcore://` link. +- **It connects in the same way as your companion.** It supports serial (USB), Bluetooth + LE, and TCP (a Wi-Fi board or a network proxy). It finds the serial and Bluetooth + devices automatically, and you select one from a list. You type the name of a network + device. MeshTerm reconnects live when a link of any kind drops. On Windows and Linux, + MeshTerm pairs with a Bluetooth companion that has a PIN. When a connection fails, + MeshTerm says which step failed and what to do. If you have no radio, a built-in + simulator is available for development. +- **It works offline.** The street map caches OpenStreetMap tiles on disk. When there is no + network, it shows a blank grid. MeshTerm never needs the internet to run. ## Install ### Download a build -One file, no Python needed — Windows, macOS (Intel and Apple silicon), and Linux (x64 and -ARM64, so the uConsole is covered). Every command below fetches the +A build is one file. You do not need Python. Builds are available for Windows, macOS (Intel +and Apple silicon), and Linux (x64 and ARM64, so the uConsole is included). Each command +below gets the **[⬇ latest release](https://github.com/jpmartineau/MeshTerm/releases/latest)**, whichever -one that is, so none of them goes stale. +release that is. Thus the commands do not become old. MeshTerm is a terminal program, so **open a terminal and run it from there.** You can -double-click it and it will work, but you'll get whatever console your system picks, and -if anything goes wrong at startup the window closes before you can read why. +double-click the file and it will work. But then your system selects the console, and you +cannot choose it. If an error occurs at startup, the window closes before you can read the +error. -#### macOS — download it with `curl` +#### macOS: download it with `curl` -**Don't use your browser.** A browser flags the file as downloaded, and macOS then refuses -to open it at all — on Sequoia and later, right-click → Open no longer gets you past that -either. `curl` flags nothing, so what it hands you simply runs. Open Terminal and paste: +**Do not use your browser.** A browser marks the file as downloaded, and then macOS does not +let you open it. On Sequoia and later, right-click → Open does not help. `curl` does not +mark the file, so the file runs. Open Terminal and paste: ```bash curl -fL -o meshterm https://github.com/jpmartineau/MeshTerm/releases/latest/download/meshterm-macos-arm64 @@ -93,13 +98,13 @@ chmod +x meshterm ./meshterm ``` -That's the build for Apple silicon Macs (M1 and later). On an **Intel** Mac, swap `arm64` -for `x64`. And if you already downloaded it with a browser, you don't have to start over: -`xattr -d com.apple.quarantine meshterm` clears the flag. +This is the build for Apple silicon Macs (M1 and later). On an **Intel** Mac, replace +`arm64` with `x64`. If you already downloaded the file with a browser, you do not have to +start again. Run `xattr -d com.apple.quarantine meshterm` to clear the mark. -#### Linux — the same three lines +#### Linux: the same three lines -`linux-x64` on a PC, `linux-arm64` on a uConsole or another 64-bit ARM handheld. +Use `linux-x64` on a PC. Use `linux-arm64` on a uConsole or another 64-bit ARM handheld. ```bash curl -fL -o meshterm https://github.com/jpmartineau/MeshTerm/releases/latest/download/meshterm-linux-x64 @@ -110,52 +115,54 @@ chmod +x meshterm #### Windows Download -**[meshterm-windows-x64.exe](https://github.com/jpmartineau/MeshTerm/releases/latest/download/meshterm-windows-x64.exe)**, -then open Windows Terminal or PowerShell, `cd` to your downloads, and run it: +**[meshterm-windows-x64.exe](https://github.com/jpmartineau/MeshTerm/releases/latest/download/meshterm-windows-x64.exe)**. +Then open Windows Terminal or PowerShell, use `cd` to go to your downloads folder, and run +the file: ```powershell .\meshterm-windows-x64.exe ``` -Windows says *"Windows protected your PC"* the first time. Click **More info → Run anyway**. +The first time, Windows says *"Windows protected your PC"*. Click **More info → Run anyway**. -Whichever you took, you now have a single file: `meshterm` on macOS and Linux, -`meshterm-windows-x64.exe` on Windows. Put it somewhere on your `PATH` (renamed to -`meshterm.exe` on Windows) and it's just `meshterm` from anywhere. +After these steps, you have one file: `meshterm` on macOS and Linux, or +`meshterm-windows-x64.exe` on Windows. Put the file in a folder that is in your `PATH`. On +Windows, rename it to `meshterm.exe`. Then you can run `meshterm` from any folder. -> **These builds aren't code-signed yet**, which is why macOS and Windows both push back -> the first time. **Windows signing is on its way.** MeshTerm has applied to the +> **These builds are not code-signed yet.** For this reason, macOS and Windows both show a +> warning the first time. **Windows signing will come.** MeshTerm applied to the > [SignPath Foundation](https://signpath.org), which signs open-source projects for free. -> Once that's set up, every Windows download will be signed. The -> [code signing policy](#code-signing-policy) below explains how. Mac builds will stay -> unsigned, because Apple charges for signing and this is a free side project. -> Every release ships a `SHA256SUMS` file if you'd rather check what you got, and -> [installing with pip or pipx](#or-install-with-pip) sidesteps the whole business, since -> nothing arrives as a downloaded binary. +> After the setup is complete, each Windows download will be signed. The +> [code signing policy](#code-signing-policy) below gives the details. The Mac builds will +> stay unsigned, because Apple charges a fee for signing and this is a free side project. +> Each release has a `SHA256SUMS` file. Use it to check the file that you got. +> [Installing with pip or pipx](#or-install-with-pip) avoids the problem, because you do not +> download a binary. > -> Each release page also carries these commands written out for **that exact version**, -> next to its own downloads. +> The page of each release also has these commands, written for **that exact version**, next +> to its own downloads. ### If your terminal draws empty boxes -MeshTerm is drawn with emoji icons, braille charts, and powerline path chips, and whether -you see them is up to your *terminal* rather than your font. Windows Terminal, VS Code's -terminal, and anything modern on macOS and Linux all draw them correctly. +MeshTerm draws emoji icons, braille charts, and powerline path chips. Your *terminal* +decides if you see them, not your font. Windows Terminal, the terminal of VS Code, and each +modern terminal on macOS and Linux draw them correctly. -The classic Windows console — the black `cmd.exe` window you get from a double-click — -does not. So when MeshTerm lands there it **moves to Windows Terminal**, says so, and -opens there; nothing is installed and nothing is changed. Where there's no Windows -Terminal to move to, it offers the charts and marks without the icons instead, and will -install Microsoft's [Cascadia Mono PL](https://github.com/microsoft/cascadia-code) just -for you — no administrator rights, nothing downloaded. +The classic Windows console does not draw them. This is the black `cmd.exe` window that +opens when you double-click the file. When MeshTerm starts there, it **moves to Windows +Terminal**, tells you, and opens there. It does not install anything and does not change +anything. If Windows Terminal is not available, MeshTerm offers the charts and marks without +the icons. It also installs Microsoft's [Cascadia Mono PL](https://github.com/microsoft/cascadia-code) +for you. This needs no administrator rights, and it downloads nothing. -Preferences → Display → Console setup turns all of this off if you'd rather stay put. -[Terminals, icons, and the Windows console](docs/guide/terminals.md) explains why any of it is -necessary. +To stay in the same console, turn off these actions in Preferences → Display → Console +setup. [Terminals, icons, and the Windows console](docs/guide/terminals.md) explains why +they are necessary. -> **Trying a build without touching your real data.** MeshTerm keeps everything in -> `~/.meshterm` — your history database, contacts, channel keys — and *every* copy of -> MeshTerm uses that same folder. Set `MESHTERM_HOME` to try one in isolation: +> **Try a build without a change to your real data.** MeshTerm keeps all its data in +> `~/.meshterm`. This includes your history database, your contacts, and your channel +> keys. *Each* copy of MeshTerm uses this same folder. To try a copy in isolation, set +> `MESHTERM_HOME`: > > ```bash > MESHTERM_HOME=~/meshterm-test ./meshterm # macOS, Linux @@ -164,53 +171,54 @@ necessary. > $env:MESHTERM_HOME = "$HOME\meshterm-test"; .\meshterm-windows-x64.exe > ``` > -> Same variable if you run two radios and want them kept apart. +> Use the same variable if you run two radios and you want to keep them separate. ### Or install with pip -You need **Python 3.10 or newer**. That's the only requirement — MeshTerm pulls in -everything else itself. +You need **Python 3.10 or newer**. This is the only requirement. MeshTerm installs all +other parts by itself. ```bash pip install git+https://github.com/jpmartineau/MeshTerm meshterm ``` -`meshterm` opens the full-screen menu. That's the whole install. +`meshterm` opens the full-screen menu. This is the complete installation. -**Rather not touch your system Python?** [pipx](https://pipx.pypa.io/) puts the app in its -own private environment and still gives you a plain `meshterm` command: +**Do you not want to change your system Python?** [pipx](https://pipx.pypa.io/) puts the +app in its own private environment. It also gives you a normal `meshterm` command: ```bash pipx install git+https://github.com/jpmartineau/MeshTerm ``` -**No radio yet?** `meshterm --mock` runs the entire app against a simulated mesh, so you -can have a look around before you buy anything. +**Do you not have a radio yet?** `meshterm --mock` runs the complete app on a simulated +mesh. You can look at the app before you buy anything. ### Coming -**`pip install mesh-term`**, once the package is published. It isn't yet — the name -question is still being sorted out ([why](https://github.com/pypi/support/issues/12162)). +**`pip install mesh-term`** will work after the package is published. It is not published +yet, because the question of the name is not resolved +([read why](https://github.com/pypi/support/issues/12162)). -Setting up for development instead? That's in [CONTRIBUTING.md](CONTRIBUTING.md). +To set up a development environment, refer to [CONTRIBUTING.md](CONTRIBUTING.md). ## Documentation -**[The documentation index](docs/README.md)** lists everything, including the code survey -kept as a historical record. The short version: +**[The documentation index](docs/README.md)** lists all the documents. This includes the +code survey, which we keep as a historical record. This table is a short version: | Read | For | | --- | --- | -| **[The command line](docs/cli/README.md)** | Every subcommand and option, what each prints on the plain and JSON faces, the exit statuses, recipes. | -| **[What MeshTerm does](docs/guide/features.md)** | Every screen the menu offers, and the command that does the same job without it. | -| [The CLI cookbook](docs/cli/cookbook.md) | Common one-liners, by the thing you're trying to do. | -| [Configuring MeshTerm](docs/guide/configuration.md) | Preferences, device profiles, and everything kept under `~/.meshterm`. | -| [When a companion won't connect](docs/guide/connecting.md) | Bluetooth pairing on each system, finding the PIN, USB permissions, and every connection error, with what to do about it. | -| [MeshTerm on hardware](docs/devices/README.md) | Which handheld manual is yours — the [PicoCalc](docs/devices/picocalc-lyra.md) build, or the [uConsole](docs/devices/uconsole.md), whose LoRa chip MeshTerm can drive directly. The [Cardputer Zero](docs/devices/cardputer-zero.md) is **not supported yet**. | -| [How the code is laid out](docs/development/architecture.md) | The layering, and where a new feature goes. | +| **[The command line](docs/cli/README.md)** | Each subcommand and option, what each prints on the plain face and on the JSON face, the exit statuses, and recipes. | +| **[What MeshTerm does](docs/guide/features.md)** | Each screen that the menu has, and the command that does the same job without the menu. | +| [The CLI cookbook](docs/cli/cookbook.md) | Common one-line commands, in groups by the task that you want to do. | +| [Configuring MeshTerm](docs/guide/configuration.md) | The preferences, the device profiles, and all the files that MeshTerm keeps in `~/.meshterm`. | +| [When a companion does not connect](docs/guide/connecting.md) | Bluetooth pairing on each system, how to find the PIN, USB permissions, and each connection error, with the action to take. | +| [MeshTerm on hardware](docs/devices/README.md) | Which handheld manual is the correct one for you: the [PicoCalc](docs/devices/picocalc-lyra.md) build, or the [uConsole](docs/devices/uconsole.md), whose LoRa chip MeshTerm can control directly. The [Cardputer Zero](docs/devices/cardputer-zero.md) is **not supported yet**. | +| [How the code is laid out](docs/development/architecture.md) | The layers of the code, and where to put a new feature. | | [CONTRIBUTING.md](CONTRIBUTING.md) | The development setup and the house rules. | -| [CHANGELOG.md](CHANGELOG.md) | What changed, newest first. | +| [CHANGELOG.md](CHANGELOG.md) | What changed, with the newest change first. | ## Quick start @@ -237,40 +245,43 @@ meshterm --mock --platform picocalc-lyra meshterm specimen ``` -Putting MeshTerm on a handheld has its own manuals. **[MeshTerm on the -PicoCalc](docs/devices/picocalc-lyra.md)** takes a stock PicoCalc from the shopping list to a -Linux handheld with a LoRa radio soldered inside; **[MeshTerm on the -uConsole](docs/devices/uconsole.md)** drives a uConsole's SPI LoRa board directly with -`--spi`, or through a small bridge if you want the node on the mesh all the time. M5Stack's -Cardputer Zero has a page too, but it is **[not supported yet](docs/devices/cardputer-zero.md)**. -The device-side scripts the manuals run are in [`scripts/`](scripts/); not sure which -manual is yours? [`docs/devices/README.md`](docs/devices/README.md) says. +MeshTerm has its own manuals for handhelds. **[MeshTerm on the +PicoCalc](docs/devices/picocalc-lyra.md)** goes from the shopping list for a stock PicoCalc +to a Linux handheld that has a LoRa radio soldered inside. **[MeshTerm on the +uConsole](docs/devices/uconsole.md)** shows how to control the SPI LoRa board of a uConsole +directly with `--spi`. It also shows how to use a small bridge, if you want the node to be +on the mesh at all times. M5Stack's Cardputer Zero also has a page, but it is **[not +supported yet](docs/devices/cardputer-zero.md)**. The scripts that the manuals run on the +device are in [`scripts/`](scripts/). If you do not know which manual is correct for you, +refer to [`docs/devices/README.md`](docs/devices/README.md). -A companion that won't connect? **[When a companion won't -connect](docs/guide/connecting.md)** covers Bluetooth pairing on Windows, Linux, and macOS, the -`dialout` group and ModemManager for USB on Linux, and every error message MeshTerm gives. +Does a companion not connect? **[When a companion does not +connect](docs/guide/connecting.md)** explains Bluetooth pairing on Windows, Linux, and +macOS. It also explains the `dialout` group and ModemManager for USB on Linux, and each +error message that MeshTerm gives. ## What it does -The menu asks one question — *what would you like to do?* — and answers it in two halves. -Three sections name a doing: **Message** is chat, channels, the courier outbox, and -contacts; **Watch** is the dashboard, the live feed, the watchtower, and the time machine; -**Explore** is the map, the mesh walk, tracing, and the trophy case. Three name whose it is: -**This node** is the radio in your hand, **Other nodes** is someone else's over the mesh, -and **This app** is MeshTerm itself — preferences, diagnostics, and the written pages. +The menu asks one question: *what would you like to do?* It gives the answer in two halves. +Three sections name an activity. **Message** has chat, channels, the courier outbox, and +contacts. **Watch** has the dashboard, the live feed, the watchtower, and the time machine. +**Explore** has the map, the mesh walk, tracing, and the trophy case. The other three +sections name the owner. **This node** is the radio in your hand. **Other nodes** is the +someone else's radio, which you reach over the mesh. **This app** is MeshTerm itself, with +the preferences, the diagnostics, and the written pages. -Nearly every one of them has a mirror `meshterm` subcommand — the same registry builds -the menu and the CLI, so they can't drift apart. The live screens, such as the map and -the dashboard, are only in the menu. +Almost all of these have a mirror `meshterm` subcommand. The same registry builds the menu +and the CLI, so they do not become different from one another. The live screens, such as the +map and the dashboard, are only in the menu. -**[What MeshTerm does](docs/guide/features.md)** is the full catalogue: every screen, what it -does, and its scripted equivalent where one exists. +**[What MeshTerm does](docs/guide/features.md)** is the complete catalogue. It shows each +screen, what the screen does, and the scripted equivalent where there is one. ## Scripting it -Nearly every command speaks `--json`, and the global options — `--profile/-p`, `--port`, -`--ble`, `--ble-pin`, `--tcp`, `--spi`, `--mock`, `--db`, `--json`, `--absolute`, `--quiet/-q`, -`--platform` — may be typed before or after the subcommand. +Almost every command accepts `--json`. The global options are `--profile/-p`, `--port`, +`--ble`, `--ble-pin`, `--tcp`, `--spi`, `--mock`, `--db`, `--json`, `--absolute`, +`--quiet/-q`, and `--platform`. You can type them before or after the subcommand. ```bash # Every repeater's public key, for a script @@ -288,44 +299,46 @@ meshterm config set radio_sf 9 meshterm monitor --seconds 60 ``` -**[The command line](docs/cli/README.md)** is the manual — every command and option, what each -prints on both faces, and the exit statuses. -**[The CLI cookbook](docs/cli/cookbook.md)** has more one-liners like these. +**[The command line](docs/cli/README.md)** is the manual. It shows each command and option, +what each prints on both faces, and the exit statuses. +**[The CLI cookbook](docs/cli/cookbook.md)** has more one-line commands like these. ## How this project is run -MeshTerm is one person working evenings and weekends. I'd rather tell you that up front -than have you guess from how long things take. +One person works on MeshTerm, in the evenings and at weekends. I want to tell you this at +the start, so that you do not have to guess from the time that things take. -**Issues are welcome — all of them.** Bugs, questions, "is this supposed to do that". The -[bug form](https://github.com/jpmartineau/MeshTerm/issues/new?template=bug.yml) asks for a -fair bit, and that's on purpose: how well a problem is described really does decide whether -I can do anything with it. If I can reproduce it, I'll usually chase it. If I can't, I'm -mostly guessing. +**We welcome all issues.** This includes bugs, questions, and "is this a fault or is it +correct". The +[bug form](https://github.com/jpmartineau/MeshTerm/issues/new?template=bug.yml) asks for +much information. This is intentional. The quality of the description of a problem +decides if I can do something about it. If I can reproduce a problem, I will usually work +on it. If I cannot reproduce it, I can only guess. **Ask before you write a pull request.** Join the [Discord](https://discord.gg/AZwe5Uvb3S) -and say hello in `#contributing`. Tell us what you'd like to change. Then wait for a yes. -That goes for small fixes too. I'm not being precious. MeshTerm has firm house rules -about how screens get built (they're in [CLAUDE.md](CLAUDE.md)), and I'd hate for you to -spend a weekend on something I then ask you to rewrite. A quick conversation first saves -us both. AI tools are fine, as long as you say you used them. -[CONTRIBUTING.md](CONTRIBUTING.md) has the details. +and say hello in `#contributing`. Tell us what you want to change. Then wait for a yes. +This applies to small corrections also. This is not because I am too careful. MeshTerm has +firm house rules for how to build screens (they are in [CLAUDE.md](CLAUDE.md)). I do not +want you to spend a weekend on a change that I then ask you to write again. A short +conversation at the start saves time for both of us. You can use AI tools, but you must say +that you used them. [CONTRIBUTING.md](CONTRIBUTING.md) has the details. -**I can't promise timelines.** Some things get fixed the same night. Some sit for a month -because life happened. If your issue goes quiet, please give it a nudge. That helps me. +**I cannot promise a time.** Some problems are corrected the same night. Some wait for a +month, because I had other tasks in my life. If your issue is quiet for a long time, please +remind me. This helps me. -**Where to report things.** Either works: +**Where to report a problem.** You can use one of these two places: -- **[GitHub issues](https://github.com/jpmartineau/MeshTerm/issues)** if you have an - account. This is where things get tracked and fixed, so it's the shortest path. -- **[Discord](https://discord.gg/AZwe5Uvb3S)** if you don't, or if you're not sure it's a - bug yet. Ask in the support forum, or drop reproducible defects in `#bugs`. I move the - real ones over to GitHub myself. +- **[GitHub issues](https://github.com/jpmartineau/MeshTerm/issues)**, if you have an + account. We track and correct problems here, so this is the shortest path. +- **[Discord](https://discord.gg/AZwe5Uvb3S)**, if you do not have an account, or if you + are not sure that the problem is a bug. Ask in the support forum, or put defects that you + can reproduce in `#bugs`. I move the real bugs to GitHub myself. -Don't worry about picking the wrong one. Getting told about a problem beats filing it -tidily. +Do not worry about the choice of the wrong place. It is better that you tell us about a +problem than that you file it in a tidy way. -Also here: [CONTRIBUTING.md](CONTRIBUTING.md) · +More documents: [CONTRIBUTING.md](CONTRIBUTING.md) · [SECURITY.md](SECURITY.md) · [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) · [CHANGELOG.md](CHANGELOG.md) @@ -335,29 +348,33 @@ Also here: [CONTRIBUTING.md](CONTRIBUTING.md) · Free code signing provided by [SignPath.io](https://signpath.io), certificate by [SignPath Foundation](https://signpath.org). -This is for the Windows download. It's being set up now, so releases up to and including -0.10.2 are still unsigned. +This policy is for the Windows download. The setup is in progress, so the releases up to +and including 0.10.2 are still unsigned. -**How a build gets signed.** Every Windows build is made by GitHub Actions, straight from -the public source code in this repository. SignPath only signs a file it can trace back to -one of those builds, and only after the maintainer approves that release. A file built on -anyone's own computer can't be signed this way, not even the maintainer's. +**How a build is signed.** GitHub Actions makes each Windows build, directly from the public +source code in this repository. SignPath signs only a file that it can trace to one of +these builds, and only after the maintainer approves that release. This process cannot sign +a file that was built on any person's own computer, and this includes the maintainer's +computer. **Who does what.** MeshTerm has one maintainer, -[Jean-Pierre Martineau](https://github.com/jpmartineau), who holds all three roles: +[Jean-Pierre Martineau](https://github.com/jpmartineau), who has all three roles: - **Author:** writes the code and commits it. -- **Reviewer:** reviews every change from anyone else before it goes in. -- **Approver:** approves each release before it gets signed. +- **Reviewer:** reviews each change from anyone else before it goes in. +- **Approver:** approves each release before it is signed. **Privacy.** This program will not transfer any information to other networked systems unless specifically requested by the user or the person installing or operating it. -In practice, MeshTerm goes online for one thing: map tiles. It downloads them from -[OpenFreeMap](https://openfreemap.org/) when you open a screen that shows a map, and -OpenFreeMap's [privacy policy](https://openfreemap.org/privacy) covers those downloads. It -collects nothing about you and doesn't check for updates. Over the radio, it sends only -what you ask it to send. +In practice, MeshTerm goes online for only one purpose: map tiles. It downloads them from +[OpenFreeMap](https://openfreemap.org/) when you open a screen that shows a map. The +[privacy policy](https://openfreemap.org/privacy) of OpenFreeMap applies to these +downloads. MeshTerm collects no information about you, and it does not check for updates. +Over the radio, MeshTerm sends only what you tell it to send. + + + ## Acknowledgements & attribution @@ -429,4 +446,6 @@ Each standalone build is a single file with `LICENSE`, `NOTICE`, and a generated `THIRD-PARTY-NOTICES.txt` (every dependency's own license text) bundled inside it. The same three files are also attached to each release on the [releases page](https://github.com/jpmartineau/MeshTerm/releases), so you can read them -without running anything. \ No newline at end of file +without running anything. + + diff --git a/SECURITY.md b/SECURITY.md index d380814..c5a246b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,61 +2,61 @@ ## Reporting a vulnerability -Use GitHub's private vulnerability reporting: go to the **Security** tab of this -repository and choose **Report a vulnerability**. That opens a private thread visible only -to you and the maintainer, so nothing is disclosed while it is being fixed. +Use the private vulnerability reporting of GitHub. Go to the **Security** tab of this +repository and choose **Report a vulnerability**. This opens a private thread. Only you and +the maintainer can see it, so nothing is disclosed while we correct the problem. -If that is unavailable to you for any reason, email instead. +If you cannot use this for any reason, send an email to instead. -**Please do not open a public issue for a security problem.** +**Do not open a public issue for a security problem.** ## What to expect -MeshTerm is maintained by one person in his spare time, so this promises only what can -actually be kept: +One person maintains MeshTerm in his spare time. For this reason, this policy promises only +what he can keep: -- **An acknowledgement within a week.** Not a fix within a week — an answer saying the - report was read and what happens next. -- **A fix or a decision within 90 days** for anything confirmed. If it will take longer, - you will be told why. -- **Credit in the release notes**, unless you would rather not be named. +- **An acknowledgement within a week.** This is not a correction within a week. It is an + answer that says that he read the report and what happens next. +- **A correction or a decision within 90 days** for each confirmed problem. If it will take + more time, you will be told why. +- **Credit in the release notes**, unless you prefer that we do not use your name. -A slow reply is not a dismissal. If a week passes with silence, a nudge on the same thread -is welcome. +A slow reply is not a rejection. If you hear nothing for a week, you are welcome to remind +us on the same thread. ## Supported versions -While the major version is `0`, only the **latest release** is supported. There are no -backports; a fix ships in the next version. +While the major version is `0`, only the **latest release** is supported. We do not make +backports. A correction is in the next version. ## Scope -In scope — anything in this repository: +In scope: anything in this repository. -- Code execution, path traversal, or file overwrite triggered by data MeshTerm reads: - packets overheard from the mesh, a companion's responses, a config or preferences file, - a downloaded map tile. -- Anything that discloses a **channel key**, a **repeater admin password**, or a node's - private key material — including into the local database, a log, an export, or the - screen when it should not be there. +- Code execution, path traversal, or file overwrite that data which MeshTerm reads can + cause. This data includes packets overheard from the mesh, the responses of a companion, + a config file or a preferences file, and a downloaded map tile. +- Anything that discloses a **channel key**, a **repeater admin password**, or the private + key material of a node. This includes disclosure into the local database, a log, an + export, or the screen, when the material must not be there. - Anything that lets a crafted packet corrupt or delete the observation database. - Weaknesses in how MeshTerm stores credentials on disk. -Out of scope, though still worth telling us about somewhere: +Out of scope, but we still want you to tell us somewhere: -- Vulnerabilities in the **MeshCore firmware** or in the companion device itself. Those - belong upstream at the MeshCore project. -- Vulnerabilities in the `meshcore` Python library, `bleak`, or any other dependency — - report those to their maintainers. If a MeshTerm change can mitigate one, we want to - know. -- The fact that anyone within radio range can hear a public channel. That is how a mesh - works, not a defect. -- Anything requiring an attacker to already have write access to your filesystem or your - companion device. +- Vulnerabilities in the **MeshCore firmware** or in the companion device itself. These + belong upstream, with the MeshCore project. +- Vulnerabilities in the `meshcore` Python library, `bleak`, or any other dependency. + Report these to their maintainers. If a change in MeshTerm can reduce the risk of one, + we want to know. +- The fact that everyone within radio range can hear a public channel. This is how a + mesh works. It is not a defect. +- Anything that needs an attacker who already has write access to your filesystem or to + your companion device. ## A note on what MeshTerm handles -MeshTerm decrypts channel traffic using keys **you** give it, and it records everything it -overhears to a local SQLite database. That database is not encrypted. Treat it as you would -any file holding your mesh's history, and remember that a backup of it carries the same -weight. +MeshTerm decrypts channel traffic with keys that **you** give it. It stores everything that +it overhears in a local SQLite database. This database is not encrypted. Treat it in the +same way as each file that holds the history of your mesh. A backup of the database has the +same importance. diff --git a/config.example.toml b/config.example.toml index d8187d4..120adfd 100644 --- a/config.example.toml +++ b/config.example.toml @@ -1,28 +1,32 @@ -# MeshTerm configuration: where things live, and which device to talk to. -# Copy to ~/.meshterm/config.toml (or pass --db / --port on the CLI). +# MeshTerm configuration: where things are, and which device to talk to. +# Copy this file to ~/.meshterm/config.toml (or give --db / --port on the CLI). # -# How MeshTerm itself behaves — transmit cooldown, message retries, how much history it -# keeps — is *not* here. Those are preferences: they live in ~/.meshterm/preferences.toml, -# and are edited on the Preferences page (or with `meshterm preferences set `). -# Every one has a built-in default, so that file only ever lists what you have changed. +# This file does not have the settings for how MeshTerm itself behaves. These are the +# transmit cooldown, the message retries, and how much history MeshTerm keeps. They are +# preferences. They are in ~/.meshterm/preferences.toml, and you edit them on the +# Preferences page (or with `meshterm preferences set `). +# Each preference has a built-in default, so that file lists only what you changed. # -# Profiles are optional. With no profile, no --port/--ble/--tcp, MeshTerm discovers attached -# serial devices and nearby Bluetooth companions, lets you pick one (interactively) or uses the -# single one present, and remembers the last device that connected as the default for next time. -# A network (TCP) companion isn't discoverable — name it with --tcp host:port, a TCP profile, or -# the picker's "add a network device" prompt. Define profiles below only to give your hardware -# stable aliases (e.g. --profile yagi, --profile handheld for Bluetooth, --profile wifi for TCP). +# Profiles are optional. When you give no profile and none of --port, --ble, or --tcp, +# MeshTerm discovers the serial devices that are connected and the Bluetooth companions that +# are near. It lets you select one (interactively), or it uses the one device that is +# there. It remembers the last device that connected, and uses it as the default the next +# time. A network (TCP) companion is not discoverable. To name one, use --tcp host:port, a +# TCP profile, or the "add a network device" prompt of the picker. Define profiles below +# only to give your hardware names that do not change (for example --profile yagi, +# --profile handheld for Bluetooth, or --profile wifi for TCP). -# Uncomment and name one of your own profiles below to make it the default. Left -# commented, MeshTerm finds the device itself, as described above. +# To make one of your own profiles the default, remove the comment mark below and write +# the name of the profile. While the line is a comment, MeshTerm finds the device by +# itself, as described above. # default_profile = "s3" -# Open the radio link as soon as the menu appears. False opens it lazily instead — only -# once monitoring is turned on or a tool first needs the radio. +# Open the radio link when the menu appears. When the value is false, MeshTerm opens the +# link later: when you turn on monitoring, or when a tool first needs the radio. connect_on_start = true # db_path = "C:/Users/you/meshterm/meshterm.db" -# One [profiles.] block per physical companion device. +# Make one [profiles.] block for each physical companion device. [profiles.s3] port = "COM5" baudrate = 115200 @@ -40,44 +44,46 @@ description = "Local-range repeater" port = "COM8" description = "XIAO observer running a bot" -# A Bluetooth LE companion: give it an `address` (from `meshterm devices`) instead of a port. -# The `transport = "ble"` line is optional — a bare `address` already implies Bluetooth. +# A Bluetooth LE companion: give an `address` (from `meshterm devices`) and not a port. +# The line `transport = "ble"` is optional. An `address` alone means Bluetooth. [profiles.handheld] address = "AA:BB:CC:DD:EE:FF" # transport = "ble" -# ble_pin = "123456" # only if your device requires a pairing PIN +# ble_pin = "123456" # only if your device needs a pairing PIN description = "Pocket handheld over Bluetooth" -# A network (TCP) companion — a WiFi board, or a meshcored-style proxy — reachable at a -# host:port. Give it a `host` (and optional `tcp_port`, default 5000); a bare `host` already -# implies TCP, so the `transport = "tcp"` line is optional. A TCP companion isn't discoverable, -# so a profile (or --tcp host:port, or the picker's "add a network device" prompt) is how you -# reach one. +# A network (TCP) companion, such as a WiFi board or a meshcored-style proxy, at a +# host:port. Give a `host` (and an optional `tcp_port`, which is 5000 by default). A `host` +# alone means TCP, so the line `transport = "tcp"` is optional. A TCP companion is not +# discoverable. To reach one, use a profile, or --tcp host:port, or the "add a network +# device" prompt of the picker. [profiles.wifi] host = "192.168.1.50" # tcp_port = 5000 # transport = "tcp" description = "Basestation over WiFi" -# A radio on this machine's own SPI bus: MeshTerm runs its node while it is connected. -# The pins default to the uConsole AIO v1's, which needs nothing more than this (and is -# listed on the device screen anyway, whenever /dev/spidev1.0 exists). +# A radio on the own SPI bus of this machine: MeshTerm runs its node while the radio is +# connected. The default pins are the pins of the uConsole AIO v1. That board needs only +# this profile. The device screen also lists it, when /dev/spidev1.0 exists. [profiles.aio] transport = "spi" # [profiles.aio.spi] # en_pins = [27] # the AIO v2 powers its radio from pin 27 # -# Which /dev/gpiochip carries the pins is not a fact about the board: a uConsole's CM4 -# has its 40-pin header on chip 0, a CM5 had it on chip 4 under its early kernels, chip 0 -# once Raspberry Pi aliased it there, and another number again on some later kernels. So -# gpio_chip defaults to -1, which finds the chip by its label (pinctrl-rp1 on a CM5, -# pinctrl-bcm2711 on a CM4; `gpiodetect` lists them) and falls back to chip 0 where none -# carries either. Give it a number only to override that: +# The number of the /dev/gpiochip that has the pins does not depend on the board. A +# uConsole CM4 has its 40-pin header on chip 0. A CM5 had it on chip 4 with its early +# kernels, and on chip 0 after Raspberry Pi made an alias there. Some later kernels use +# another number. For this reason, gpio_chip is -1 by default. This value finds the chip by +# its label (pinctrl-rp1 on a CM5, pinctrl-bcm2711 on a CM4, and `gpiodetect` lists them). +# If no chip has one of these labels, MeshTerm uses chip 0. Give a number only to +# override this: # gpio_chip = 4 -# Any other board states its whole wiring — every pin and both switches, because a key -# left out keeps the AIO's value. Read them off the board's meshtasticd config; the -# values here are an illustration only. docs/guide/configuration.md has the key-by-key mapping. +# Any other board must give its complete wiring: each pin and both switches. A key that +# you omit keeps the value of the AIO. Read the values from the meshtasticd config of the +# board. The values here are only an example. docs/guide/configuration.md shows which +# key matches which value. # [profiles.hat.spi] # bus_id = 0 # /dev/spidev0.0 # cs_id = 0 diff --git a/docs/README.md b/docs/README.md index 0b2c446..ad8a06e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,64 +1,66 @@ # MeshTerm documentation -Everything that didn't belong on the front page. The [README](../README.md) is the front -door — what MeshTerm is, how to install it, and enough to get a first session running. -This folder is the rest, in four parts: +This folder has everything that does not belong on the front page. The +[README](../README.md) is the front door. It says what MeshTerm is, how to install it, and +how to start a first session. This folder has the other documents, in four parts: -| Folder | What's in it | +| Folder | What is in it | | --- | --- | -| [`guide/`](#using-meshterm) | Using MeshTerm: what each screen does, configuring it, connecting a companion, terminals. | -| [`cli/`](#the-command-line) | The command line manual, and a cookbook of one-liners. | -| [`devices/`](#running-it-on-a-handheld) | Running MeshTerm on a handheld's own screen, one manual per device. | -| [`development/`](#reading-the-code) | How the code is laid out, and a historical survey. | +| [`guide/`](#using-meshterm) | How to use MeshTerm: what each screen does, how to configure it, how to connect a companion, and terminals. | +| [`cli/`](#the-command-line) | The manual of the command line, and a cookbook of one-line commands. | +| [`devices/`](#running-it-on-a-handheld) | How to run MeshTerm on a handheld's own screen, with one manual for each device. | +| [`development/`](#reading-the-code) | How the code is laid out, how we write, and a historical survey. | ## Using MeshTerm | Read | For | | --- | --- | -| **[What MeshTerm does](guide/features.md)** | Every screen the menu offers, grouped the way the menu groups them, with the command that does the same job without it. | -| [Configuring MeshTerm](guide/configuration.md) | Preferences versus config, device profiles, and everything kept under `~/.meshterm`. | -| [When a companion won't connect](guide/connecting.md) | Bluetooth pairing on each system, finding the PIN, and every connection error MeshTerm reports, with what to do about it. | -| [Terminals, icons, and the Windows console](guide/terminals.md) | Why the icons and charts draw on some terminals and not others, and what MeshTerm does about it on Windows. | +| **[What MeshTerm does](guide/features.md)** | Each screen that the menu has, in the same groups as the menu, with the command that does the same job without the menu. | +| [Configuring MeshTerm](guide/configuration.md) | The difference between preferences and config, the device profiles, and all the files that MeshTerm keeps in `~/.meshterm`. | +| [When a companion does not connect](guide/connecting.md) | Bluetooth pairing on each system, how to find the PIN, and each connection error that MeshTerm reports, with the action to take. | +| [Terminals, icons, and the Windows console](guide/terminals.md) | Why the icons and charts draw correctly on some terminals and not on others, and what MeshTerm does about it on Windows. | ## The command line | Read | For | | --- | --- | -| **[The command line](cli/README.md)** | The manual. Every subcommand and option, what each prints on the plain and JSON faces, the exit statuses, and recipes built on `jq`. | -| [The CLI cookbook](cli/cookbook.md) | Common one-liners by task, for when you want the shape of a command rather than its specification. | +| **[The command line](cli/README.md)** | The manual. Each subcommand and option, what each prints on the plain face and on the JSON face, the exit statuses, and recipes that use `jq`. | +| [The CLI cookbook](cli/cookbook.md) | Common one-line commands, in groups by task. Use it when you want the form of a command and not its specification. | ## Running it on a handheld -Only if you want MeshTerm running on a handheld's own screen. A MeshCore companion on -USB, Bluetooth, or Wi-Fi needs none of this. +Read these documents only if you want to run MeshTerm on a handheld's own screen. A +MeshCore companion on USB, Bluetooth, or Wi-Fi does not need them. | Read | For | | --- | --- | -| **[MeshTerm on hardware](devices/README.md)** | Start here — which manual below is yours, and what the device-side scripts do. | -| [MeshTerm on the PicoCalc](devices/picocalc-lyra.md) | A stock ClockworkPi PicoCalc to a Linux handheld with a LoRa radio soldered inside: the shopping list, the swap to a Luckfox Lyra core, Calculinux, and the radio. | -| [MeshTerm on the uConsole](devices/uconsole.md) | A uConsole (CM4 or CM5) with the hackergadgets AIO LoRa board, whose SX1262 sits on the computer's own SPI bus — MeshTerm drives it directly with `--spi`, or a small bridge fronts it as a companion for an always-on node. | +| **[MeshTerm on hardware](devices/README.md)** | Start here. It says which of the manuals below is the correct one for you, and what the scripts on the device do. | +| [MeshTerm on the PicoCalc](devices/picocalc-lyra.md) | How to change a stock ClockworkPi PicoCalc into a Linux handheld that has a LoRa radio soldered inside: the shopping list, the change to a Luckfox Lyra core, Calculinux, and the radio. | +| [MeshTerm on the uConsole](devices/uconsole.md) | A uConsole (CM4 or CM5) with the hackergadgets AIO LoRa board. Its SX1262 is on the computer's own SPI bus. MeshTerm can control it directly with `--spi`. Or a small bridge can make it a companion for a node that is always on. | | [MeshTerm on the Cardputer Zero](devices/cardputer-zero.md) | ⚠️ **Not supported yet.** What exists for M5Stack's Linux Cardputer, and how to try it in the desktop emulator. | ## Reading the code | Read | For | | --- | --- | -| [How the code is laid out](development/architecture.md) | The layering, and where a new feature goes. | -| [CLAUDE.md](../CLAUDE.md) | The actual style guide — terminology, UX standards, and the reusable building blocks. Written for AI assistants, and the house rules for everyone. | -| [CONTRIBUTING.md](../CONTRIBUTING.md) | How to propose a change, the rules on AI use, the dev install, the three checks a change must pass, and what happens to your contribution's licensing. | +| [How the code is laid out](development/architecture.md) | The layers of the code, and where to put a new feature. | +| [How we write](development/writing-style.md) | How we write: the ASD-STE100 rules and the glossary for docstrings, comments, and documents. | +| [CLAUDE.md](../CLAUDE.md) | The real style guide: the terms, the UX standards, and the building blocks that you can use again. It is written for AI assistants, and it has the house rules for everyone. | +| [CONTRIBUTING.md](../CONTRIBUTING.md) | How to propose a change, the rules for the use of AI, the dev install, the three checks that a change must pass, and what happens to the licensing of your contribution. | ### Historical record -A survey of the code, kept because a project that writes down its own problems is easier -to trust than one that doesn't — but it is a **snapshot of a date, not a to-do list**. -Some of what it describes has been fixed since, and it does not mark which. +This is a survey of the code. We keep it, because a project that writes down its own +problems is easier to trust than a project that does not. But it is a **snapshot of one +date, not a list of tasks**. Some of the problems that it describes are corrected now, and +the survey does not say which. | Read | For | | --- | --- | -| [UI consistency survey](development/survey-ui-consistency.md) | Everything else in the CLAUDE.md UX standard — six sweeps found nothing, which is the useful part. | +| [UI consistency survey](development/survey-ui-consistency.md) | Each other point of the CLAUDE.md UX standard. Six sweeps found nothing, and this is the useful result. | ## Also here -`screenshots/` holds the captures the README shows and the ones -[meshterm.net](https://meshterm.net) builds its landing page from, so a file in there is -referenced from more than one place. +`screenshots/` has the captures that the README shows. It also has the captures from which +[meshterm.net](https://meshterm.net) builds its landing page. Thus more than one place +refers to each file in this folder. diff --git a/docs/development/architecture.md b/docs/development/architecture.md index f2e9bb8..5e51845 100644 --- a/docs/development/architecture.md +++ b/docs/development/architecture.md @@ -1,11 +1,11 @@ # How the code is laid out -A map of the package for anyone reading it for the first time. The house rules that -govern what goes where are in [CLAUDE.md](../../CLAUDE.md), which is the real style guide; -[CONTRIBUTING.md](../../CONTRIBUTING.md) has the dev install and the gates a change has to -pass. +This page is a map of the package for a person who reads it for the first time. +[CLAUDE.md](../../CLAUDE.md) has the house rules that decide what goes where. It is the real +style guide. [CONTRIBUTING.md](../../CONTRIBUTING.md) has the dev install and the checks +that a change must pass. -MeshTerm is layered so a new feature is one file in `meshterm/tools/`: +MeshTerm is divided into layers. Thus a new feature is one file in `meshterm/tools/`: ``` cli.py Typer app; no subcommand -> interactive menu @@ -17,7 +17,6 @@ persistence/ SQLite schema, repository, structured logging ui/ Rich theme/widgets + the full-screen TUI (screens, dialogs, map, chat) ``` -Adding a feature means subclassing `Tool`, decorating it with `@register`, and -implementing `run()`. The same registry builds both the CLI and the menu, and the base -class wraps every execution in a logged `runs` row automatically. - +To add a feature, make a subclass of `Tool`, add the `@register` decorator to it, and +write the `run()` method. The same registry builds the CLI and the menu. The base class +automatically writes a logged `runs` row for each execution. diff --git a/docs/development/survey-ui-consistency.md b/docs/development/survey-ui-consistency.md index 74bfd9f..a334919 100644 --- a/docs/development/survey-ui-consistency.md +++ b/docs/development/survey-ui-consistency.md @@ -1,23 +1,25 @@ -# UI consistency survey — what is off, and what turned out to be fine +# UI consistency survey: what is off, and what turned out to be fine -> **A historical record.** This was a snapshot of the code on the date below, kept -> because a project that writes down its own problems is easier to trust than one -> that doesn't. Some of what's here has since been fixed and some hasn't; nothing in -> it is a to-do list. Read it as "what this looked like then", not "what is wrong now". +> **A historical record.** This was a snapshot of the code on the date below. We keep it, +> because a project that writes down its own problems is easier to trust than a project +> that does not. We have corrected some of these problems since then, and we have not +> corrected others. Nothing in it is a list of tasks. Read it as "what the code looked like +> then", not as "what is wrong now". -Written 2026-08-22 against `main` @ `4b29248`. It covers the CLAUDE.md UX standard apart -from navigation. A catalogue, not a change. +The survey was written on 2026-08-22 against `main` @ `4b29248`. It covers the UX standard +of CLAUDE.md, except navigation. It is a catalogue. It is not a change. -**The short version: the standard is very well kept.** Six sweeps found nothing at all; -what remains is nine small, specific things. That is the useful result — it means the list -below is close to complete rather than a sample. +**The short version: the code follows the standard very well.** Six sweeps found nothing at +all. Nine small, specific points remain. This is the useful result. It means that the list +below is almost complete and is not a sample. --- -## Part 1 — the clean sweeps +## Part 1: the clean sweeps -Each of these was checked mechanically across the whole package, not spot-checked. The scripts -are one-offs and were not kept; each is a dozen lines of `ast.walk` and is trivial to redo. +We checked each of these with a program across the whole package. We did not check them by +hand on a sample. We did not keep the scripts. Each script is a dozen lines of `ast.walk`, +and it is easy to write again. | Rule (CLAUDE.md) | How it was checked | Result | |---|---|---| @@ -27,31 +29,34 @@ are one-offs and were not kept; each is a dozen lines of `ast.walk` and is trivi | Filter atom is always `type to filter` | regex over all hints | **0 deviations** | | No emoji in a screen or dialog title | 86 title literals vs an emoji range | **0** (one hit was a `Choice` row title, where icons belong) | | Sentence case in titles | capitalised non-leading word, minus proper nouns | **0** (5 hits were markup-wrapped panel headings) | -| Lexicon: "heard" not "seen"; "contacts" not "nodes list" | regex over all non-docstring literals | **0** | +| Lexicon: "heard" and not "seen", "contacts" and not "nodes list" | regex over all non-docstring literals | **0** | | Status marks: never `✔ ✖ ✅ ❌` | scan of every source line | **0** (the 3 hits are the fold table that *normalizes* them, and a docstring warning against them) | -| `…` on rows that open a prompt, none on rows that act | 40 row labels via the builders | **1 arguable** — see §2.7 | +| `…` on rows that open a prompt, none on rows that act | 40 row labels via the builders | **1 arguable** (refer to §2.7) | | Empty state never parenthesised | scan of 53 empty-state-shaped strings | **0** | | `render.query_line` is the one query line | import graph | **exactly** the four screens CLAUDE.md names: select list, path composer, map, mesh walk | -Two more worth calling out because they are the kind of rule that usually rots: +Two more points are important, because rules of this kind usually decay: -- **`Del` on a row is gated correctly.** The hint atom only appears when the highlighted row - is actually deletable ([`select.py:448`](../../meshterm/ui/tui/select.py#L448)) — a real - instance of "never advertise a key that would do nothing", implemented rather than assumed. -- **The PicoCalc lane's "a chord earns a chip" rule holds.** The one bare-letter shortcut in - the app — Time Machine's `w` — has its F3 chip, with the reasoning written out in full at +- **`Del` on a row is correctly gated.** The hint atom is visible only when the highlighted + row can be deleted ([`select.py:448`](../../meshterm/ui/tui/select.py#L448)). This is a + real example of "never advertise a key that would do nothing". The code implements the + rule, and the survey did not only assume it. +- **The rule "a chord earns a chip" on the lane of the PicoCalc holds.** The app has one + bare-letter shortcut, which is `w` in the Time Machine. It has its F3 chip. The reason is + written in full at [`timemachine_screen.py:227-242`](../../meshterm/ui/timemachine_screen.py#L227). --- -## Part 2 — the findings +## Part 2: the findings -Ordered by how visible each is to someone using the app. +The findings are in order of how visible each one is to a person who uses the app. ### 2.1 Empty-state voice: four sites shout (rule: lowercase, muted, `— explanation`, no full stop) -The house voice is `no contacts yet — receive an advert first`. These four break it, and they -are the empty states of four whole screens, so each is the *only* thing on screen when it shows: +The house voice is `no contacts yet — receive an advert first`. These four sites do not +follow it. They are the empty states of four complete screens. Thus each one is the *only* +thing on the screen when it shows: | Site | Current | House voice | |---|---|---| @@ -60,158 +65,172 @@ are the empty states of four whole screens, so each is the *only* thing on scree | [`timemachine_screen.py:335-336`](../../meshterm/ui/timemachine_screen.py#L335) | `Nothing recorded in this window.` + `Press w to widen it.` | lowercase, no full stop | | [`timemachine_screen.py:453-454`](../../meshterm/ui/timemachine_screen.py#L453) | `Nothing sent in this window.` + `Press w to widen it.` | ditto | -The two `Press w to widen it.` lines are a second question: they are *hints*, sitting in the -body, on a screen whose F-key lane and footer already advertise `w`. Either the empty state -absorbs it (`nothing recorded in this window — press w to widen it`) or it goes. +The two `Press w to widen it.` lines raise a second question. They are *hints*, and they are +in the body of a screen where the F-key lane and the footer already advertise `w`. There are +two options. The empty state can include the hint (`nothing recorded in this window — press w to widen it`). +Or we can remove the hint. -Deliberately **not** on this list: `Never heard` ([`contacts_screen.py:258`](../../meshterm/ui/contacts_screen.py#L258)) -is a purge-ladder *row label*, and `No revisits` ([`records.py:237`](../../meshterm/services/records.py#L237)) -is a discipline *title*. Both correctly capitalised. +These two sites are **not** on this list on purpose. `Never heard` +([`contacts_screen.py:258`](../../meshterm/ui/contacts_screen.py#L258)) is the *row label* of +a purge ladder. `No revisits` ([`records.py:237`](../../meshterm/services/records.py#L237)) +is the *title* of a discipline. Both are correctly capitalised. ### 2.2 One dialog inverts the button convention -The rule: *safe way out on the left, committing verb on the right and default*. +The rule is: *the safe way out is on the left, and the committing verb is on the right and +is the default*. -[`config_editor.py:538-541`](../../meshterm/ui/config_editor.py#L538) — the Location dialog: +This is the Location dialog, at +[`config_editor.py:538-541`](../../meshterm/ui/config_editor.py#L538): ```python [("Pick on map", "map"), ("Type coordinates", "type"), ("Clear", "clear")], title="Location", ``` -No `Cancel`, no `default=`. So the leftmost slot — reserved for the safe way out — holds an -action, the rightmost — reserved for the default commit — holds the *destructive* option -(`Clear`), and the default falls to index 0. Esc still cancels, so nothing is lost; but it is -the only dialog in the app shaped this way, out of 49 call sites. +It has no `Cancel` and no `default=`. Thus the leftmost slot, which is for the safe way out, +holds an action. The rightmost slot, which is for the default commit, holds the *destructive* +option (`Clear`). The default is index 0. Esc still cancels, so the user loses nothing. But +this is the only dialog in the app with this shape, out of 49 call sites. ### 2.3 One dialog defaults to the middle button -[`config_editor.py:988-992`](../../meshterm/ui/config_editor.py#L988) — `[Cancel, Preview, Apply]` -with `default=1`, so Enter lands on **Preview**, not the rightmost `Apply`. +This is at [`config_editor.py:988-992`](../../meshterm/ui/config_editor.py#L988). The buttons +are `[Cancel, Preview, Apply]` with `default=1`. Thus Enter selects **Preview**, and not the +rightmost button `Apply`. -This is very likely deliberate — previewing before overwriting a whole device config is the -cautious path — but the standard as written says the rightmost verb is the default, and this -is the only three-button dialog with a real Cancel. Either the rule grows a clause ("with -three buttons, the *cautious* commit defaults") or this changes. +This is very probably intentional. Before it overwrites the complete config of a device, the +dialog gives the user a preview, which is the careful path. But the standard, as it is +written, says that the rightmost verb is the default. This is the only dialog with three +buttons and a real Cancel. There are two options. The rule can get a clause ("with three +buttons, the *careful* commit is the default"). Or this dialog changes. -### 2.4 Two dialog *shapes* the standard doesn't describe +### 2.4 Two dialog *shapes* that the standard does not describe -Beyond confirms, the app has two other dialog shapes, both used consistently but neither -written down: +The app has two dialog shapes in addition to the confirm. The app uses both consistently, +but the standard does not describe either: -- **The toggle picker** — `[Off, On]` with the default tracking the current value +- **The toggle picker.** It has `[Off, On]`, and the default follows the current value ([`config_editor.py:441`](../../meshterm/ui/config_editor.py#L441), - [`repeater_admin.py:313`](../../meshterm/ui/repeater_admin.py#L313)). No Cancel; `Esc keep` - carries it, which is exactly the footer verb CLAUDE.md reserves for a value picker. Correct - in practice, undescribed in the standard. -- **The notification with an onward door** — `[Trophy case, Close]`, `default=1` - ([`trace_screen.py:637-639`](../../meshterm/ui/trace_screen.py#L637)). Appears unbidden after a - record-setting trace. `Close` is the default so a stray Enter dismisses rather than - navigating — the inverse of the usual rule, and right for a dialog the user did not ask for. + [`repeater_admin.py:313`](../../meshterm/ui/repeater_admin.py#L313)). It has no Cancel. + `Esc keep` does that job. This is the footer verb that CLAUDE.md reserves for a value + picker. It is correct in practice, but the standard does not describe it. +- **The notification with a door to a next screen.** It has `[Trophy case, Close]` with + `default=1` + ([`trace_screen.py:637-639`](../../meshterm/ui/trace_screen.py#L637)). It opens without a + request from the user, after a trace that sets a record. `Close` is the default. Thus an + Enter that the user pressed by mistake closes the dialog and does not open another screen. + This is the opposite of the usual rule, and it is correct for a dialog that the user did + not ask for. -Both are good designs. The gap is that CLAUDE.md describes only the confirm, so a future -dialog of either shape has nothing to copy. +Both designs are good. The gap is that CLAUDE.md describes only the confirm. Thus a future +dialog of one of these two shapes has no example to copy. -### 2.5 Two full screens have no F-key lane of their own — one of them is Trace +### 2.5 Two full screens have no F-key lane of their own, and one of them is Trace -Twelve classes declare `floating = False` (they are full screens, not dialogs). Ten override -`fkey_lane`. Two do not, and so inherit `Screen.fkey_lane` → the raw `DEFAULT_LANE` -([`fkeys.py:116`](../../meshterm/ui/tui/fkeys.py#L116)): +Twelve classes declare `floating = False`. These are full screens and not dialogs. Ten of +them override `fkey_lane`. Two do not override it. They use `Screen.fkey_lane`, which gives +the raw `DEFAULT_LANE` ([`fkeys.py:116`](../../meshterm/ui/tui/fkeys.py#L116)): | Screen | | |---|---| -| [`TraceScreen`](../../meshterm/ui/trace_screen.py#L345) (`trace_screen.py:345`) | the app's busiest screen | +| [`TraceScreen`](../../meshterm/ui/trace_screen.py#L345) (`trace_screen.py:345`) | the busiest screen of the app | | [`TxSweepScreen`](../../meshterm/ui/tx_screen.py#L93) (`tx_screen.py:93`) | | -`DEFAULT_LANE` is the *always-enabled* pager. The ten others build from -`default_lane(nav=self.content_overflows)` ([`fkeys.py:131`](../../meshterm/ui/tui/fkeys.py#L131)), -which dims the chips when the body already fits. So on PicoCalc both screens advertise live -`Page ↑` / `Page ↓` chips whether or not there is anything to page — the one thing the lane's -standing rule forbids. +`DEFAULT_LANE` is the pager that is *always enabled*. The other ten screens build their lane +from `default_lane(nav=self.content_overflows)` +([`fkeys.py:131`](../../meshterm/ui/tui/fkeys.py#L131)). This dims the chips when the body +fits on the screen. Thus on the PicoCalc, both screens show live `Page ↑` and `Page ↓` +chips, also when there is nothing to page. The standing rule of the lane forbids exactly +this. -Trace is the one worth a proper look rather than a one-liner. Its footer +Trace needs a proper examination, and not only a one-line change. Its footer ([`trace_screen.py:498-505`](../../meshterm/ui/trace_screen.py#L498)) reads -`↑↓ actions · Enter run · PgUp/PgDn scroll · Esc back` — every key physical on the PicoCalc, so -nothing is *undiscoverable*. But it is the screen where F1–F3 would earn their keep most -(re-run, flip the path, open the composer are all one cursor move away today), and it is -currently the only central screen that hasn't been through the lane pass. Worth deciding what -its three free slots should say, not just gating the pager. +`↑↓ actions · Enter run · PgUp/PgDn scroll · Esc back`. Each of these keys is a physical key +on the PicoCalc, so no function is *hidden*. But Trace is the screen where F1 to F3 are most +useful. To run again, to flip the path, and to open the composer, the user now needs only +one move of the highlight. Trace is also the only central screen that has not had the lane +pass. We must decide what its three free slots say, and not only gate the pager. -`TxSweepScreen` genuinely is one line: `return default_lane(nav=self.content_overflows)`. +`TxSweepScreen` really is one line: `return default_lane(nav=self.content_overflows)`. -(The About page reads as an exception in a grep but isn't: it passes `floating=False` as a -constructor argument rather than a class attribute, and it does define its own lane at -[`about.py:115`](../../meshterm/ui/about.py#L115).) +(The About page looks like an exception in a grep, but it is not. It gives `floating=False` +as an argument of the constructor and not as a class attribute. It does define its own lane +at [`about.py:115`](../../meshterm/ui/about.py#L115).) ### 2.6 Dialogs that scroll but declare no lane -CLAUDE.md says `EMPTY_LANE` is for "a screen with none at all (**every dialog**)". Five -surfaces set it explicitly — the confirms, the text prompt, the busy spinner, the reorder -list, one trace dialog. But two floating dialogs that genuinely *do* page inherit +CLAUDE.md says that `EMPTY_LANE` is for "a screen with none at all (**every dialog**)". Five +surfaces set it explicitly: the confirms, the text prompt, the busy spinner, the reorder +list, and one trace dialog. But two floating dialogs that really *do* page use `DEFAULT_LANE` instead: -- `RecordDialog` ([`records_screen.py:146`](../../meshterm/ui/records_screen.py#L146)) — a record's - story, which scrolls -- `PathComposerScreen` ([`path_composer.py`](../../meshterm/ui/path_composer.py)) — a windowed - list of hop suggestions +- `RecordDialog` ([`records_screen.py:146`](../../meshterm/ui/records_screen.py#L146)). It + shows the story of a record, and it scrolls. +- `PathComposerScreen` ([`path_composer.py`](../../meshterm/ui/path_composer.py)). It is a + list of hop suggestions, which shows a window of the list. -Both arguably *want* the pager, which would make the doc's "every dialog" too strong. The -question is whether the rule is "dialogs get no lane" (then these two need `EMPTY_LANE` and -lose paging chips) or "a surface gets the pager iff it pages" (then the doc's parenthetical -should read *every dialog that doesn't scroll*). +It is possible that both *need* the pager. If so, "every dialog" in the document is too +strong. We must decide between two rules. The first rule is "dialogs get no lane". Then +these two dialogs need `EMPTY_LANE`, and they lose their paging chips. The second rule is +"a surface gets the pager if and only if it pages". Then the parenthetical text in the +document must say *every dialog that does not scroll*. -### 2.7 One row label that may want an ellipsis +### 2.7 One row label that possibly needs an ellipsis -[`config_editor.py:787`](../../meshterm/ui/config_editor.py#L787) — `Share QR / URI`. The rule is -"a row that opens further prompts ends with `…`". This one opens a *view* (the QR popup), not -a prompt — so it turns on whether "further prompts" means "any further surface". Every other -row in the app is unambiguous; this is the only edge. +This is at [`config_editor.py:787`](../../meshterm/ui/config_editor.py#L787), and the label +is `Share QR / URI`. The rule is "a row that opens further prompts ends with `…`". This row +opens a *view* (the QR dialog), and it does not open a prompt. Thus the answer depends on the +meaning of "further prompts": is it "each further surface"? Each other row in the app is +clear. This is the only boundary case. -Also worth a glance while there: `Share QR / URI` is the only row label in the app using ` / ` -as a separator. +Also note: `Share QR / URI` is the only row label in the app that uses ` / ` as a separator. -### 2.8 `★` carries two meanings +### 2.8 `★` has two meanings -CLAUDE.md itself lists it twice: `★ you (yellow)` under node types, and `★ best/winner` under -concept icons. In practice both are live — the map and every path line draw `★` for our own -node ([`pathline.SELF_GLYPH`](../../meshterm/ui/pathline.py)), while the trophy case and the -new-record dialog draw `★` in accent for a record +CLAUDE.md itself lists it two times: `★ you (yellow)` under node types, and `★ best/winner` +under concept icons. In practice, both are in use. The map and each path line draw `★` for +our node ([`pathline.SELF_GLYPH`](../../meshterm/ui/pathline.py)). The trophy case and +the dialog of a new record draw `★` in the accent colour for a record ([`trace_screen.py:635-636`](../../meshterm/ui/trace_screen.py#L635)). -They are never on the same surface, and the colours differ (yellow vs accent), so nothing is -actually ambiguous today. Flagged only because it is the single glyph in the whole marks table -with two entries, and the "one glyph per concept" heading promises otherwise. If a record ever -needs to appear next to a path line, this is where it bites — `🏆` is already reserved for the -trophy case and would carry it. +The two uses are never on the same screen, and the colours are different (yellow and +accent). Thus nothing is unclear today. We note it only because it is the single glyph in +the whole table of marks that has two entries, and the heading "one glyph per concept" says +that this does not occur. If a record must appear next to a path line in the future, this is +where a problem will occur. `🏆` is already reserved for the trophy case, and it can carry +the record. -### 2.9 `node.unknown` vs `muted`, unverified +### 2.9 `node.unknown` and `muted`, not verified -CLAUDE.md is emphatic that an unidentified node takes `node.unknown` and that this is +CLAUDE.md is clear that an unidentified node takes `node.unknown`, and that this is *deliberately not* `muted` ("muted is chrome and may sit a step darker, while an unidentified -node is content you can still act on"). `node.unknown` appears at 11 sites. There are also 26 -`style="muted"` uses inside [`pathline.py`](../../meshterm/ui/pathline.py) and -[`widgets.py`](../../meshterm/ui/widgets.py) — the two modules that draw nodes. +node is content you can still act on"). `node.unknown` is used at 11 sites. There are also 26 +uses of `style="muted"` in [`pathline.py`](../../meshterm/ui/pathline.py) and +[`widgets.py`](../../meshterm/ui/widgets.py). These are the two modules that draw nodes. -Most of those 26 are certainly chrome (separators, labels, the un-lit part of a key, which the -standard explicitly says stays muted). But this is the one rule in the standard that a -mechanical sweep cannot settle: it needs a human to look at each and say "that's chrome" or -"that's an unnamed node". Listed here as the one unaudited corner, not as a finding. +Most of these 26 uses are certainly chrome (separators, labels, and the part of a key that +is not lit, which the standard says must stay muted). But a program cannot decide this rule +in a sweep. A person must look at each use and decide: "this is chrome" or "this is a node +without a name". We list it here as the one corner that we did not audit. It is not a +finding. --- -## Part 3 — shortlist +## Part 3: shortlist | # | Finding | Sites | Effort | Weight | |---|---|---|---|---| -| 2.1 | Empty states shout on four screens | 4 (6 strings) | trivial | **high** — it is the whole screen | -| 2.2 | Location dialog has no Cancel and defaults to an action | 1 | small | medium | -| 2.5 | Trace and TX advertise a dead pager on PicoCalc; Trace has never had a lane pass | 2 | one line + one design pass | **medium-high** | -| 2.3 | Restore dialog defaults to the middle button | 1 | decision, not code | low | -| 2.6 | Two scrolling dialogs inherit the pager the doc says they shouldn't have | 2 | decision | low | -| 2.4 | Two dialog shapes used consistently but undocumented | — | doc only | low | -| 2.7 | `Share QR / URI` — ellipsis? separator? | 1 | trivial | low | -| 2.8 | `★` has two entries in a "one glyph per concept" table | — | doc only | low | -| 2.9 | `node.unknown` vs `muted` in the two node-drawing modules | ~26 to eyeball | manual | unknown | +| 2.1 | Empty states shout on four screens | 4 (6 strings) | trivial | **high**, because it is the whole screen | +| 2.2 | The Location dialog has no Cancel and its default is an action | 1 | small | medium | +| 2.5 | Trace and TX advertise a dead pager on the PicoCalc. Trace has never had a lane pass | 2 | one line and one design pass | **medium-high** | +| 2.3 | The Restore dialog defaults to the middle button | 1 | a decision, not code | low | +| 2.6 | Two scrolling dialogs inherit the pager that the document says they must not have | 2 | a decision | low | +| 2.4 | Two dialog shapes that the app uses consistently but that no document describes | none | document only | low | +| 2.7 | `Share QR / URI`: an ellipsis or a different separator? | 1 | trivial | low | +| 2.8 | `★` has two entries in a table of "one glyph per concept" | none | document only | low | +| 2.9 | `node.unknown` and `muted` in the two modules that draw nodes | approximately 26 to examine | manual | unknown | -Items 2.1, 2.2, 2.5 and 2.7 are code. The rest are decisions about the standard itself, and -2.3, 2.4 and 2.8 may well resolve as "the standard should say what the code already does". +Items 2.1, 2.2, 2.5, and 2.7 are code. The other items are decisions about the standard +itself. For items 2.3, 2.4, and 2.8, the decision can be "the standard must say what the code +already does". diff --git a/pyproject.toml b/pyproject.toml index d98eda2..7ff79a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,15 +1,15 @@ [build-system] -# 1.26 is where hatchling started writing the PEP 639 fields below (`License-Expression`, -# `License-File`) into the metadata; an older one builds a wheel that says nothing about -# the licence at all, silently. +# Hatchling 1.26 is the first version that writes the PEP 639 fields below +# (`License-Expression`, `License-File`) into the metadata. An older version builds a +# wheel that says nothing about the licence, and it gives no warning. requires = ["hatchling>=1.26"] 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. +# This is only the name of the distribution. The import package and the console script stay +# `meshterm`. PyPI prohibits that name (refer to pypi/support#12162), but the app does not +# change because of this. The `[tool.hatch.build.targets.wheel]` section below makes the +# wheel contain the `meshterm/` package under this different name. name = "mesh-term" dynamic = ["version"] description = "MeshTerm is a full-featured TUI MeshCore client for your terminal. Connects to a companion over USB, Bluetooth, or TCP. For Windows, macOS, and Linux." @@ -19,7 +19,7 @@ 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. +# There is no `License ::` entry, because PEP 639 does not permit one with the SPDX expression above. classifiers = [ "Development Status :: 4 - Beta", "Environment :: Console", @@ -42,7 +42,7 @@ dependencies = [ "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 + "typer>=0.12", # scripted CLI. The 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) @@ -55,16 +55,17 @@ 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. + # The version is pinned to one minor line. A formatter that is different on different + # machines is not a gate. Ruff 0.16 formats Python inside Markdown and 0.15 does not. + # With an unpinned `ruff>=0.4`, CI and a laptop disagreed about a tree with no errors. "ruff>=0.16.6,<0.17", - # The SPI radio's node, run for real in tests/test_radionode_live.py on a fake radio. - # The bare library is pure Python everywhere; only the `hardware` extra is Linux-bound. + # The node of the SPI radio. tests/test_radionode_live.py runs it with a fake radio. + # The library alone is pure Python on all systems. Only the `hardware` extra needs Linux. "openhop-core>=1.1.3", ] -# A LoRa chip on this machine's own SPI bus (the uConsole AIO), driven by a node MeshTerm -# runs itself — see meshterm/core/spiradio.py. Linux only, and nowhere else worth carrying. +# A LoRa chip on this machine's own SPI bus (the uConsole AIO). A node that MeshTerm runs +# itself controls it (refer to meshterm/core/spiradio.py). It works only on Linux, so +# the other systems do not need it. spi = [ "openhop-core[hardware]>=1.1.3", ] @@ -78,20 +79,21 @@ 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. +# This is the one place where we write the version. `[project] version` is dynamic, so the +# package attribute and the distribution metadata cannot become different. [tool.hatch.version] path = "meshterm/__init__.py" [tool.hatch.build.targets.wheel] packages = ["meshterm"] -# The sdist is the package's source, not the repository's: what someone needs to build -# and test the wheel. Hatchling's default is everything git does not ignore, which swept -# in the Claude skills, the GitHub configuration, the PyInstaller spec, the device-side -# shell scripts (one of which carries data under another licence) and the developer -# environment template — none of which builds, tests, or documents the package. Kept: -# the package, the tests and their fixture, the manual, and the files a build reads. +# The sdist is the source of the package. It is not the source of the repository. It has +# what a person needs to build and test the wheel. By default, Hatchling includes each file +# that git does not ignore. This included the Claude skills, the GitHub configuration, the +# PyInstaller spec, the shell scripts for the device (one of which has data under another +# licence), and the template of the developer environment. None of these builds, tests, or +# documents the package. The sdist keeps the package, the tests and their fixture, the +# manual, and the files that a build reads. [tool.hatch.build.targets.sdist] exclude = [ "/.claude", @@ -108,23 +110,25 @@ exclude = [ [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. +# The helper scripts for the Claude skills are not part of the package. Also exclude `docs/`. +# It has survey documents with dates. Ruff formats Python blocks inside Markdown, and the +# code in these documents is an excerpt that shows how a file looked on a past date. If +# ruff formats a quotation, it changes the record. The excerpts are fragments of calls and +# not complete programs. Thus the formatter would change them into something that never +# existed. 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 +ignore = ["D203", "D213"] # select one style for the blank line before a class and for the multi-line summary [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. +# Typer declares CLI parameters when the default value calls these functions. The objects +# that they return are inert markers, and the command never changes them. B008 guards +# against this kind of change. extend-immutable-calls = ["typer.Argument", "typer.Option"] [tool.pytest.ini_options] diff --git a/scripts/README.md b/scripts/README.md index 59a8399..6b34cc2 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -1,42 +1,46 @@ # scripts/ -Repo-side tools. None of these ship in the wheel. +These are tools for the repository. None of them is in the wheel. | Script | What it is for | |---|---| -| `basemap-doctor.py` | Why a user's map has no basemap — certificates, DNS, proxy, or colour depth | -| `cardputer-zero/` | `launcher-entry.sh` puts a source checkout on the Cardputer Zero's app launcher (and `remove` takes it off), until there is a store package; `meshterm.png` is its icon, the website's logo at the launcher's 100×100. `type-text.py` types a pasted string (an API key, a Wi-Fi password) on the Cardputer's keyboard from another computer, over SSH | -| `picocalc-lyra/` | Calculinux device setup: console font, palette, Wi-Fi; `xiao-radio/` is the XIAO radio kit (firmware build, flashing, UART setup) | -| `uconsole/` | The uConsole's always-on SPI bridge service (MeshTerm can also drive that radio directly, with no script at all — see `docs/devices/uconsole.md`) | +| `basemap-doctor.py` | Finds why a user's map has no basemap: certificates, DNS, proxy, or colour depth | +| `cardputer-zero/` | `launcher-entry.sh` puts a source checkout in the Cardputer Zero's app launcher. `remove` takes it out. This is for the time before there is a store package. `meshterm.png` is the icon: the website's logo, at the launcher's 100×100 size. `type-text.py` types a pasted string (an API key, a Wi-Fi password) on the Cardputer's keyboard from another computer, over SSH | +| `picocalc-lyra/` | Setup of the Calculinux device: console font, palette, Wi-Fi. `xiao-radio/` is the kit for the XIAO radio (firmware build, flashing, UART setup) | +| `uconsole/` | The uConsole's always-on SPI bridge service. MeshTerm can also control that radio directly, with no script (refer to `docs/devices/uconsole.md`) | ## basemap-doctor.py -The map is the only networked part of MeshTerm, and every fetch failure on that path is -logged at DEBUG while `log_level` defaults to WARNING — so a reader whose tiles never -arrive sees a blank ground and no explanation. This prints the explanation and the fix. +The map is the only part of MeshTerm that uses the network. MeshTerm logs each fetch +failure on this path at DEBUG, and `log_level` is WARNING by default. Thus a user whose +tiles do not arrive sees a blank ground and no explanation. This script prints the +explanation and the correction. -Send a user this, to run **on the machine with the problem**: +To use it, tell the user to run this **on the machine that has the problem**: ```bash curl -fsSL https://raw.githubusercontent.com/jpmartineau/MeshTerm/main/scripts/basemap-doctor.py -o /tmp/basemap-doctor.py python3 /tmp/basemap-doctor.py ``` -Any `python3` will do: if that one has no MeshTerm, the script reads the interpreter out -of the installed `meshterm` command's shebang and hands itself over, so the answer always -comes from **the app's own Python** — a Mac routinely has three and only that one has the -trust store that matters. Save it to a file as above rather than piping into `python3 -`: -with no file on disk there is nothing to hand over. +Any `python3` is correct. If that `python3` does not have MeshTerm, the script reads the +interpreter from the installed `meshterm` command's shebang, and it runs again with +that interpreter. Thus the answer always comes from **the app's own Python**. A Mac +often has three Pythons, and only one of them has the trust store that is important. Save +the script to a file, as in the example. Do not pipe it into `python3 -`. When there is no +file on the disk, the script cannot run again with the other interpreter. -Have them paste the whole output back. It names the interpreter, the certificate store, -the tile source, the cached-tile count, `COLORTERM`, and then one verdict: +Ask the user to paste all the output back to you. The output names the interpreter, the +certificate store, the tile source, the number of cached tiles, and `COLORTERM`. Then it +gives one verdict: -- **curl reached it, Python did not** — the Python's certificate store is empty. A - python.org install on macOS ships that way until `Install Certificates.command` is run. -- **neither reached it** — the network path: a firewall, a proxy, a DNS filter, or an - outage. Nothing to fix in MeshTerm. -- **both reached it** — tiles are fine and the complaint is colour: an unset `COLORTERM` - means 256 colours, where the basemap's greens and blues collide. Terminal.app cannot do - 24-bit colour at all. +- **curl reached it, Python did not.** The Python's certificate store is empty. A + python.org install on macOS has an empty store until the user runs + `Install Certificates.command`. +- **neither reached it.** The problem is in the network path: a firewall, a proxy, a DNS + filter, or an outage. There is nothing to correct in MeshTerm. +- **both reached it.** The tiles are correct. The problem is colour. When `COLORTERM` is not + set, the terminal uses 256 colours, and the greens and blues of the basemap are too + similar. Terminal.app cannot do 24-bit colour. -Exit status is 0 when tiles arrive and 1 when they do not, so it can be scripted. +The exit status is 0 when the tiles arrive and 1 when they do not. Thus a script can use it. diff --git a/scripts/picocalc-lyra/xiao-radio/README.md b/scripts/picocalc-lyra/xiao-radio/README.md index 429d4a2..97b67d4 100644 --- a/scripts/picocalc-lyra/xiao-radio/README.md +++ b/scripts/picocalc-lyra/xiao-radio/README.md @@ -1,19 +1,20 @@ -# PicoCalc mesh radio — XIAO nRF52840 over UART +# PicoCalc mesh radio: XIAO nRF52840 over UART -A Seeed XIAO nRF52840 + Wio-SX1262 wired to the Luckfox Lyra's UART1, running MeshCore -companion firmware on `Serial1`, so MeshTerm on the PicoCalc opens `/dev/ttyS1` like any -other serial companion. No BLE, no USB host, no SD-slot sacrifice. +This is a Seeed XIAO nRF52840 with a Wio-SX1262. It is wired to UART1 of the Luckfox Lyra, +and it runs the MeshCore companion firmware on `Serial1`. Thus MeshTerm on the PicoCalc +opens `/dev/ttyS1` in the same way as for each other serial companion. This design does not +use BLE, does not use a USB host, and does not use the SD slot. -**The step-by-step guide is [`docs/devices/picocalc-lyra.md`, Phase 3](../../../docs/devices/picocalc-lyra.md#phase-3--add-the-radio)** — parts, -the firmware build and the patch explained, flashing, wiring, the Lyra setup, and what has -only been proven on one bench. What the register poke does is in `uart1-mux.py`'s own -header. This page is the bench card. +**The step-by-step guide is [`docs/devices/picocalc-lyra.md`, Phase 3](../../../docs/devices/picocalc-lyra.md#phase-3-add-the-radio).** +It has the parts, the firmware build and an explanation of the patch, the flashing, the +wiring, the setup of the Lyra, and what we proved on only one bench. The header of +`uart1-mux.py` explains what the register write does. This page is the bench card. **Licensing.** The firmware is MeshCore, under the MIT License. The `meshcore-uart1.patch` -modifies MeshCore source and `build-firmware.sh` produces a MeshCore binary; the licence is -[`LICENSE.MeshCore`](LICENSE.MeshCore) in this directory. +changes the source of MeshCore, and `build-firmware.sh` makes a MeshCore binary. The licence +is [`LICENSE.MeshCore`](LICENSE.MeshCore) in this directory. -Three steps on two machines: +There are three steps on two machines: ```bash # 1. dev machine: build (needs git + PlatformIO) @@ -29,7 +30,7 @@ sh scripts/picocalc-lyra/xiao-radio/lyra-setup.sh # MT_USER=meshterm by meshterm ``` -Four wires, TX and RX crossed: +There are four wires. Cross TX and RX: | XIAO pad | → | Lyra header | Physical pin | Carries | | --- | --- | --- | --- | --- | @@ -38,6 +39,7 @@ Four wires, TX and RX crossed: | **GND** | → | **GND** | pin **8** | common ground | | **3V3** | → | **3V3 OUT** | pin **36** | power, so it runs without USB | -> **The Lyra does not use Raspberry Pi Pico GPIO numbering** — the header is Pico-shaped, the -> mapping is Luckfox's own. GP4 is `gpio0-0`, GP5 is `gpio0-1`. All signals are 3.3 V and -> there is **no 5 V rail**: power the XIAO from 3V3 only. +> **The Lyra does not use the GPIO numbers of the Raspberry Pi Pico.** The header has the +> shape of a Pico header, but Luckfox made its own mapping. GP4 is `gpio0-0`, and GP5 is +> `gpio0-1`. All signals are 3.3 V, and there is **no 5 V rail**. Connect the power of the +> XIAO to 3V3 only.