czdev starts its branch from the fork's default branch, and the store's
check compares the PR branch with the store's main directly. After
M5Stack removed every app on 2026-10-12, the fork was 10 commits behind,
so the 0.11.0 PR (#176) carried all 40 removed apps as new files, and
the check failed on the first of them. store-publish.py now calls
GitHub's merge-upstream for the fork first, and publishes nothing if
that fails. PR #176 was repaired by hand with "Update branch".
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Store app on the Cardputer knows English, simplified and traditional
Chinese, and Japanese, and shows English for any other language. It falls
back to en and then to zh-CN, so the locales carry en too: without it, an
English user would see the Chinese text. PR #164 already has the same
locales, so later releases keep them.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A release before M5Stack merges the first store PR would open a second
PR that adds the same files, so the two would conflict and both would
wait for a person. store-publish.py now publishes nothing while any
open PR of ours is for the package, and in CI it shows a warning. The
release itself goes out as usual. After the merge, a rerun of that
release's store job publishes its version; the release skill says how.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
installers.yml gains a cardputer-zero job: build-deb.sh in a Debian 13
container on an arm64 runner, the same system as the Cardputer. The
release attaches the .deb with the other downloads and lists it in
SHA256SUMS, and its notes say where Cardputer users get MeshTerm.
github-release.yml gains a store job after the release. It runs
scripts/cardputer-zero/store-publish.py, which calls czdev publish with
two corrections: the manifest's email is johnputer@meshterm.net, not
the GitHub profile's, and the branch name stays the same between
czdev's two calls (for a fork, the second call named a branch that was
never pushed, and GitHub refused the PR). It also sends czdev's git
over HTTPS with the token, and it publishes nothing when the store has
the version or a PR for it is open, so a rerun opens no second PR.
AppBuilder is pinned to the commit whose functions it corrects.
The job needs the CARDPUTER_STORE_TOKEN secret (public_repo scope). The
release skill now checks the store PR and counts fifteen assets.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
scripts/cardputer-zero/screenshot.py reads the display's framebuffer
over SSH and writes it as a PNG of 320x170, the size the store wants.
It only reads, so it does not disturb the app. JP set up each screen
on the device: the splash, the menu, the Public channel, a contact's
routes, the map, and a packet from the live feed. The listing passes
the store's own metadata check.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
scripts/cardputer-zero/build-deb.sh runs on arm64 Debian 13 (the
Cardputer itself) and makes meshterm_<version>-<revision>_arm64.deb.
MeshTerm and all its Python packages, the spi extra included, go in
/usr/lib/meshterm, compiled to bytecode. The package depends only on
python3: the store app installs a missing dependency with apt-get but
never runs apt-get update, so a Debian dependency can fail. For the
same reason the package carries Terminus beside its OFL, because an app
from the launcher has no terminal for --fetch-fonts.
packaging/notices.py gets a site-directory mode, because the walk of
the closure asks for no extras and so misses the spi packages.
scripts/cardputer-zero/store holds the listing that czdev publish reads
and a 256-pixel icon. The screenshots are still to make.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The store package will own meshterm.desktop and meshterm.png. The
entry that launcher-entry.sh makes is now meshterm-dev.desktop with a
negative of the logo, so that the two look different and can be
installed together.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Python scripts, the packaging code, and the full-line comments of the
shell scripts. Heredocs, marker lines, licence lines, and code do not
change. The help text of type-text.py now starts with a complete STE
sentence, and uart1-mux.py now says three writes, as the code does.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
They were committed from Windows, which records no execute bit, so on
the device `scripts/cardputer-zero/launcher-entry.sh` -- run by path, as
its own usage line and the Cardputer guide say -- was refused with
"Permission denied" while the file sat right there. Every script under
scripts/ that starts with #! is now 100755, as the uConsole bridge
already was.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The launcher entry carried a placeholder. Its icon is now meshterm.net's
logo, the red skyline, resized to the launcher's 100x100 the way the
site makes its own small icons (Lanczos from the 410 px source).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On the device MeshTerm ran, drew every frame, and stayed invisible: the
panel kept the launcher's last frame while a splash nobody could see
waited for a key. The panel is a DRM driver whose /dev/fb0 is fbdev
emulation, and the kernel copies to the glass only what it sees change
through the *mapping*. pwrite() put the pixels in the buffer, where
reading /dev/fb0 back showed them, and never on the panel. The frame
now goes through an mmap, as M5's own LVGL apps write theirs. Checked
on the panel.
Also scripts/cardputer-zero/type-text.py: paste a long string (an API
key, a Wi-Fi password) on another computer and it is typed on the
Cardputer's keyboard over SSH, into whatever app has the screen. Input
is hidden and travels on stdin, never on a command line.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Tried on a real Cardputer Zero for the first time. It draws on the
panel, takes the keyboard, and opens from M5's app launcher:
scripts/cardputer-zero/launcher-entry.sh puts a source checkout there
(and `remove` takes it off) until there is a store package.
Three things the hardware showed:
- The splash opened with the highlight on /dev/ttyS0, the Cap's GPS
UART, because serial is listed first as found. Below the companions
we've used, anything that looks like one now comes before a port
nothing vouches for, across transports.
- The title bar's battery is the handheld's own BQ27220, as the
PicoCalc's is. The host pack is now found by what it is (a Battery
supply that isn't a peripheral's), not by the PicoCalc's name.
- While the Cap is powered, I2C bus 1 turns noisy: a quarter to three
quarters of the gauge's reads fail with an I/O error (one in ten of
the clock's), none with the header off. A failed read is asked again,
a status that won't read keeps its last verdict, and a good reading
stands through six failed polls instead of blinking out.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`meshterm emulate cardputer-zero --mock` and `meshterm emulate picocalc-lyra --mock`
open a desktop window drawing MeshTerm as that device's own screen draws it. An
EmulatedDevice (emulator/devices.py) holds what differs per handheld: the panel, a
Linux console's top-left grid and bold-as-bright 4-bit palette for the PicoCalc, the
lane keys drawn under the Cardputer's panel, and how each keyboard spells Shift with
an F-key (the PicoCalc's F6-F10, the Cardputer's xterm-shifted F4-F8).
It is not a strict emulator, and the docs say so wherever they offer it: MeshTerm runs
on the desktop as itself, and the window mimics the display's and keyboard's
constraints, never the CPU or the OS. Inside it the platform resolves with own_display
(platforms.drawn_by_meshterm), so nothing offers to move to another terminal.
Global options typed anywhere (--mock, --port, --db...) reach the emulated MeshTerm.
The command refuses --json like specimen. The Terminus fetch moved into the package as
`meshterm emulate --fetch-fonts` (with --archive for a copy fetched another way),
replacing scripts/cardputer-zero/fetch-terminus.py. A build without Tk says to install
with pip or pipx instead of failing.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
"Host" already meant two things here: a TCP companion's network host,
and battery="host" for this machine itself. And with a PicoCalc preview
coming, the package is less a console host than an emulator of a
handheld's display: its panel, grid, font, colours and keys. So it is
meshterm.emulator, Platform.console_host is own_display, its desktop
window is window.py (EmulatorWindow) and its device front end
framebuffer.py, since "sim" and "device" now mean the radio simulator and
the device being emulated. MESHTERM_HOST_FONTS is MESHTERM_EMULATOR_FONTS.
The package's own description opens on what it is not: nothing runs the
handheld's processor or operating system; it mimics the display's
constraints so the window obeys the device's.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
M5Stack makes three Cardputers and only the Zero runs Linux; the PicoCalc
takes several cores and only the Luckfox Lyra runs MeshTerm. So the bare
names were ambiguous, and every reference to the two devices now carries
the variant: the --platform values and MESHTERM_PLATFORM, the Platform
constants (PICOCALC_LYRA, CARDPUTER_ZERO), their lane decks and every
screen's picocalc_lyra_lane / cardputer_zero_lane, the font inventories,
the lane chips' theme styles, the scripts/ folders, the two chrome test
files, and the docs and CLAUDE.md that name them.
There is no alias: --platform picocalc is refused with the three real
choices listed, rather than falling back to the desktop layout. The
PicoCalc is auto-detected from its device tree, so it needs nothing.
What names hardware or a user's data keeps its name: the PicoCalc
keyboard's input-device name, its battery's sysfs supply, and the serial
profile the Lyra setup script writes. .gitignore follows the firmware's
folder, and the changelog keeps the words each release used.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>