Files
MeshTerm/CHANGELOG.md
T
Jean-Pierre MartineauandClaude Opus 5 4d8c67358c
Installers / windows-x64 (push) Canceled after 0s
Installers / macos-x64 (push) Canceled after 0s
Installers / macos-arm64 (push) Canceled after 0s
Installers / linux-x64 (push) Canceled after 0s
Installers / linux-arm64 (push) Canceled after 0s
GitHub release / build (push) Canceled after 0s
GitHub release / create the release (push) Canceled after 0s
Release 0.3.0
The command line was rebuilt around the fact that it has two readers.
`--json` is the machine contract and now covers all 49 subcommands
instead of two; the plain face is free to be read by a person, which is
what it was always for.

The changelog entry had been written for the intermediate design — it
promised quoted names and absolute timestamps, both of which the finished
work deliberately reverses — so it describes what shipped instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 05:07:51 -04:00

22 KiB

Changelog

Notable changes to MeshTerm, newest first.

The format follows Keep a Changelog, and the version numbers follow Semantic Versioning — with the caveat SemVer itself makes for a leading zero: while the major version is 0, a minor bump is allowed to change behaviour, not just add to it.

0.3.0 — 2026-09-08

The command line was rebuilt around the fact that it has two readers, and that trying to serve both with one stream had been making it worse at each.

Added

  • --json, on every command. It used to be honoured by two of twenty and silently ignored by the rest, so meshterm --json contacts | jq failed while MeshTerm reported success. It now covers all 49 subcommands: an array for a listing, an object for a set of facts, one compact line per document, and no envelope — contacts --json | jq '.[].node.name' reads the way it looks.

    Values are typed (20, not "20"), absent is null and never an omitted key, timestamps are UTC RFC 3339 to the second so string comparison is time comparison, and a numeric field carries its unit in its key (snr_db, uptime_s, rtt_ms). A key is never truncated — a short id is a hash, because a truncated key cannot go back into --to. monitor and chat listen emit one document per record as they arrive, all the same shape.

    --json changes the rendering and never the report: the same exit status, the same records. An empty result prints its empty document and still exits 5.

  • A documented exit status per outcome, listed under every --help: 0 success, 1 failure, 2 usage error, 3 no device, 4 the device was reached but the operation failed, 5 nothing to report.

    5 is the one worth knowing about. A command that ran fine and found nothing — an empty contact list, a trace that never came home — returns it, so a script can tell "found nothing" from "worked" without counting lines.

  • --absolute, restoring ISO-8601 timestamps for a run, over a new cli_time_format preference. JSON is always absolute UTC regardless: a local offset is a fact about the machine that ran the command, not about the event.

  • A global option may be typed anywhere. meshterm contacts --json and meshterm --json contacts are the same run. The first is what everyone types and it used to fail with a bare usage error.

  • contacts gains HASH and LOCATION. The hash is the token --path takes; you used to slice it out of KEY by hand. preferences show gains DESCRIPTION back, and info glosses its second-valued readings (uptime_s 93784 (1d 2h)).

  • docs/cli.md, a manual for the whole thing: both faces of every command, where MeshTerm keeps its state, what each failure looks like, and recipes. Every sample in it is captured from a real run rather than written.

Changed

  • The plain face is for a person now, and says so. It had grown the menu's manners — colour, boxes, section headings, glyph columns, a closing ✓ — and then briefly overcorrected into something only awk could love. Machine-readability is --json's job, so the plain face is free to be read: no colour and no frames, still, but alignment is the delimiter and names are bare rather than "quoted".

  • A time is an age — now, 5m, 3h, never — because "recently?" is the question you typed the command to ask. An absolute instant survives where the instant is the fact: the device clock, an appointment, a live capture's own TIME.

  • A route is drawn and a path is typed. A route reads Yagi-Repeater (a1) → Alice (d4) → MockCompanion (00); a --path spec stays the comma-joined hex it has to be, because it is the one line that round-trips. Our own node is a hop like any other — the menu's ★ says "you already know who this is", which is true of the reader and false of whoever opens the file later.

  • A value read out of show can be typed back into set. The settings dump printed an enum as 0 (off) and an unset string as (not set), neither of which config set would take back.

  • ui.ack and a tool's closing message go to stderr rather than being dropped. stderr keeps the promise the dropping was made to keep — a redirect catches only the answer — while giving the person at the prompt back their ✓ and their count.

  • --help is Click's own plain help, with no boxed panels and no markup to strip out of a pipe.

Fixed

  • A node name could not be printed safely. A node broadcasts its own name, and Rich read [...] in one as console markup: a node called [bold]Loud printed as Loud — silently no longer the string that identifies it — and one called [/]Bob raised MarkupError and took the command down, then took the error handler down with it while reporting the crash.

  • A control character in a name reached stdout. Escaping covered the newline, the tab and the return and let ESC through, which put a live colour run into the caller's file — the one thing "no escape sequences" exists to prevent. BEL was dropped in silence, so the printed name was not the advertised one, and U+2028 ended the record like a newline. It is now every C0 and C1 control plus the Unicode separators.

  • Numeric columns never right-aligned. Rich's Text.wrap returns early on overflow="ignore", above the justify step, so asking for one threw the other away — while the standard, the code's own docstring and the manual all promised alignment.

  • config show printed an empty string as "", which config set read as those two characters. Feeding a dump back replaced every empty setting with a pair of quote marks, and the next dump looked identical, so nothing ever said so. It also masked an unreported PIN behind bullets, telling the reader the radio was PIN-locked when it was not.

  • meshterm about drew the rule above its colophon at console width — one 48KB line in a 4KB page.

  • config export-key > key.hex wrote a file with [muted] tags around the key that was supposed to be its whole content.

  • Failures were classified wrong in six places. Nothing transmitted is not a device failure: an unopenable --port is 3 rather than 4 with a message about a connection there had never been, and a missing --yes, an unknown --sort/--category/--profile, a missing admin password, an unknown contact and a bad --at are all usage errors. A --category typo used to come back as 5 — indistinguishable from a real empty result — and an unknown --profile fell through to whichever radio was attached, which is the one wrong answer that puts a packet on the air.

  • Errors go to stderr, in the meshterm: what went wrong shape, and stop folding at 80 columns — a sentence broken across three lines is a sentence grep cannot find. Progress bars moved there too and draw nothing off a terminal.

  • An expected failure no longer dumps a traceback. A lost serial link, or an argument a command rejected on its own, reads as one line and the right exit status.

Removed

  • meshterm map. A map is a picture — braille cells whose meaning is their position, and whose nodes are told apart by colour. Stripped of colour it would be unreadable; left coloured it was the one command whose output could not be piped anywhere useful. The map stays in the menu, and the located nodes are scriptable through contacts, which now carries their coordinates.

  • The QR codes from channels share, config share and the About pages' scripted face. A QR is a second rendering of a link the output already prints, drawn for a phone pointed at a screen; redirected into a file it was a block of block characters wrapped around the one thing that was actually the answer. The menu still draws them.

[0.2.8] — 2026-09-07

Fixed

  • The window MeshTerm opened for itself was too small, and it showed. The startup wordmark is 71 columns wide; below that the splash quietly swaps to the narrow one drawn for the PicoCalc — so a desktop session came up wearing the handheld's mark, with every menu description truncated onto the pager underneath it.

    MeshTerm was opening a tab in whatever Windows Terminal window happened to be around, inheriting its size (64 columns on the machine this was found on). It now asks for a window of its own, sized from the platform's own minimum plus a margin. Windows Terminal clamps that to what the display can show, so a small screen degrades exactly as before.

  • The tab it opens is titled MeshTerm, not the executable's full path — which on a downloaded build was a line of Downloads folder.

[0.2.7] — 2026-09-07

Changed

  • MeshTerm moves to Windows Terminal rather than asking to. It still says so, and still tells you how to stop it, but it doesn't put the question. The classic console can't draw a single icon whatever font it's given, so there was only one sensible answer — and it was being asked of someone who hadn't seen the app yet and had nothing to judge it by.

    Installing a font still asks, because that writes a file to your machine. Moving window doesn't. Preferences → Display → Console setup stops both.

[0.2.6] — 2026-09-07

Fixed

  • Accepting "reopen in Windows Terminal" killed the session in the downloadable builds. A new window opened, printed Security validation failure: parent process has different executable!, and exited — while the window that made the offer had already closed. Worse than never offering.

    A one-file build unpacks itself and re-runs itself, the two halves coordinating through private environment variables. Those were being handed to the new copy, which concluded it was the second half of a launch it never made, checked that its parent was the same program, found Windows Terminal instead, and stopped. They are stripped now.

    It worked perfectly when run from source, which is the whole lesson: the packaged build is its own platform.

[0.2.5] — 2026-09-07

Fixed

  • Every icon was a box in the classic Windows console. That console — which is what a double-clicked build still gets — draws no emoji at all, whatever font it is set to, so the menu came up as rows of replacement characters.

    MeshTerm now works out which console it is running in and, on that one, draws its icons through the compact single-character table it already keeps for the PicoCalc: ☼ for an advert, for a message, ★ for the trophy case. Nothing else about the desktop layout changes — same width, same colours, same accented names. Running in Windows Terminal or VS Code's terminal keeps the emoji, and MESHTERM_EMOJI=1 or =0 forces the matter either way. meshterm platform reports which it chose and why.

Added

  • MeshTerm offers to reopen itself in Windows Terminal. The classic Windows console — what you get from a double-click — can't show the emoji icons at all, whatever font it is given: the only two Windows fonts with emoji in them are proportional, and a console won't take one. A terminal is the only thing that fixes it, so MeshTerm now offers to move there. One keypress, nothing installed, nothing changed — and you get the app as it was drawn: icons, charts, path chips and all.

  • MeshTerm can install a font its charts will actually draw in — for anyone who stays in the classic console. That console does no font fallback at all, and none of the fonts it offers can draw braille, which every timeline and activity graph in MeshTerm is made of. Neither can Hack Nerd Font, JetBrains Mono, Fira Code, Source Code Pro, or even DejaVu Sans Mono; the usual advice works only because most terminals quietly fall back to a proportional font.

    So MeshTerm now ships Cascadia Mono PL — Microsoft's console font, SIL OFL — and on that console offers to use it. If you already have Cascadia (every Windows 11 machine, and anyone with Windows Terminal) it just switches to it. If not, it offers to install it for you: no administrator rights, nothing downloaded, 723 KB. Saying no explains what saying no looks like and asks once more; a second no is remembered and never raised again. Preferences → Display → Console setup changes your mind about either offer.

0.2.4 — 2026-09-07

Fixed

  • MeshTerm crashed on startup on Windows. Building the screen raised ctypes.ArgumentError: expected LP__CSBI instance instead of pointer to CONSOLE_SCREEN_BUFFER_INFO, and on a double-clicked build the traceback and the window it was printed in were destroyed together.

    ctypes.windll.kernel32 is a cache: every caller in a process gets the same object, and each function on it is a shared attribute. The emoji-width probe declared GetConsoleScreenBufferInfo as taking a pointer to its copy of the screen-buffer struct — which rewrote the signature for prompt_toolkit, which then called that same function with a pointer to its copy. Same layout, different class; ctypes refused.

    Nothing in MeshTerm reaches for ctypes.windll any more. Five call sites now take private handles from core/win32dll.py, so a signature declared for one purpose is invisible to everything else in the process.

Changed

  • The log-detail preference offers the plain level names — Error, Warning, Info, Debug — rather than descriptions of them. Someone being talked through a problem is told "set it to debug", not "set it to everything".

0.2.3 — 2026-09-07

Changed

  • The log is a plain text file, ~/.meshterm/meshterm.log, one readable line per record with the traceback under the line that raised it. It was JSON Lines on the theory that something would replay it; nothing ever did, and the job it actually has is being opened by a person who has hit a problem.
  • It rotates, at 2 MB across three files. It used to grow without limit — a real one reached 11 MB in ten weeks, which is a lot of disk on a handheld and an unreasonable thing to attach to an issue.
  • How much it keeps is a preference — log_level, under a new Diagnostics group, defaulting to WARNING. The file now holds the problems rather than a narration of a working session: of the last twenty thousand records in a real log, twelve were a warning or an error.

Added

  • One interactive MeshTerm per data directory. A second one is turned away with the way out in the message, rather than quietly overwriting the first one's contacts and settings. The lock is held by the operating system, so a crash releases it — a process id in a file would leave a lock nobody could explain.
  • Failures are written down. Every unhandled error reaches the log with its traceback and the terminal says where to find it; expected errors go in as warnings, because a log that only holds crashes cannot answer "what happened just before". The bug report form asks for the file.
  • The window stays open after a crash when MeshTerm owns it. Double-clicking the executable on Windows gives the process its own console, which is destroyed the instant the process ends — so an error was drawn and erased together and all anyone saw was a flash. It now waits for a keypress, but only on that path: a run from a shell leaves its output on screen already, and a successful run should not make anyone press a key.
  • A readable answer when there is no Windows console. Git Bash, MSYS and Cygwin are not Windows consoles, and prompt_toolkit's own error says so in the language of its internals, arriving as a traceback that reads like a broken app rather than the instruction it is.

Fixed

  • All ten stores write through one helper, and the file each writes to first is named for the process writing it. They shared a fixed .tmp name, so two copies of MeshTerm saving at the same moment took turns inside one scratch file and then each renamed whatever was in it over the real one. The rename was always atomic; what was being renamed was not.

0.2.2 — 2026-09-07

Fixed

  • The downloadable builds were missing every tool. They started, printed help and drew the specimen card, and had a menu with nothing in it but Quit — no contacts, no map, no trace, no chat. Tools are found at runtime with pkgutil.iter_modules, which finds nothing inside a frozen bundle, so none of them registered. The packaging now collects them, and the smoke tests run a tool-provided subcommand instead of only --help, which a gutted build passes perfectly well.

Added

  • MESHTERM_HOME points config and data somewhere other than ~/.meshterm. Every copy of MeshTerm shared one directory, so trying a downloaded build meant letting it open the same history database as the one you use — the valuable half of an install, with no way to keep a second copy away from it. Also useful for a portable install, or two radios kept apart.

0.2.1 — 2026-09-07

Fixed

  • The formatter is pinned. ruff>=0.4 meant CI installed a newer ruff than a laptop had, and the two disagreed about whether the tree was clean — 0.16 formats Python blocks inside Markdown and 0.15 doesn't, so the first CI run went red on a file that was fine locally. An unpinned formatter isn't a gate.
  • docs/ is out of ruff's reach. The survey documents quote code as it looked on a past date, in fragments that were never whole programs; a formatter rewriting a quotation edits the record.
  • The installer builds only run for main. Every Dependabot branch that bumped an action touched the workflow file, matched its path filter, and started a five-platform build.

Changed

  • The distribution is named mesh-term. PyPI prohibits meshterm (pypi/support#12162), and the name you install is the only thing that changes: the import package and the command are both still meshterm.
  • pyproject.toml carries the metadata a stranger sees on a package page — project URLs, trove classifiers, and the licence as a PEP 639 expression.

Added

  • Continuous integration. Lint once, tests across seven OS and Python combinations, and a packaging job that installs the built wheel into a clean environment and runs the command from outside the source tree.
  • A release workflow with no automatic trigger, which publishes nothing until a human runs it on purpose and types the version.
  • A security policy, a code of conduct, issue and pull request templates, weekly dependency updates, and the funding links the app's own support page already offers.
  • A Releases page. Pushing a version tag builds the binaries and attaches them, with a combined SHA256SUMS and release notes taken from this file. It stays inside GitHub and is not the PyPI publish, which remains a separate and deliberate act.
  • Standalone builds. A PyInstaller spec and a workflow that builds a single runnable file for Windows, macOS (Intel and Apple silicon) and Linux (x64 and ARM64), checks each one runs from outside the source tree, and writes a checksum beside it. Nothing is published; the files are workflow artifacts until someone attaches them to a release.

0.2.0 — 2026-09-07

The first version to carry a changelog. Everything before it is in the commit log and is deliberately not reconstructed here.

Added

  • This file.

Changed

  • The project description now says what MeshTerm is, how it reaches a radio, and which systems it runs on, instead of naming a category. It is the PyPI summary and the package docstring, which no longer disagree.
  • The version is written in exactly one place, meshterm/__init__.py. The distribution metadata reads it from there rather than repeating it, so the number on the splash screen and the number in a built wheel cannot drift apart.
  • The basemap user agent identifies the real repository and reports the running version. It previously sent a bare https://github.com/ and a hardcoded 0.1 to OpenFreeMap, which is the only handle a tile operator has on a client.
  • macOS and Bluetooth are spelled one way across the project. BLE stays where it is an identifier — the transport name, the config keys, the CLI.
  • meshterm --help opens with the same description as the package summary and the PyPI page, instead of a fourth wording of its own.

Fixed

  • The three commands CONTRIBUTING.md asks contributors to run all pass: python -m pytest -q, ruff check ., and ruff format --check .. The lint gate had been reporting 2222 errors and the format gate 154 files, so a first contributor could not tell their own diff from the background.
  • Two TYPE_CHECKING imports the test suite referenced but never imported, on paths that happened never to run.