The machine you develop on stops being written into the repo

Every device address, login, key path, password and local venv path in the
.claude/ skills moves to dev.env at the repo root: gitignored, with a tracked
dev.env.example showing the shape. The skills now name $DEV_* keys and say to
read dev.env first, so they still run -- against whoever's hardware.

Two of the values were not secrets so much as a wrong assumption. The device
paths under that home directory are now $HOME-relative in run_instrumented.sh,
instrument.py and drive_console.py, and the deploy docs point at $DEPLOY_USER
and $KEY_PATH, which those scripts already parameterised. Hardcoding a home
directory was only ever correct for one machine.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jean-Pierre MartineauandClaude Opus 5 committed 2026-09-06 01:33:38 -04:00
1 parent 4d8ba0fc5e
commit 892aefa11b
12 files changed
+83 -28

No files matched your search

+5 -2
View File
@@ -34,8 +34,11 @@ the commonest answer to "please add this one".
1. **Classify the emoji.** Run, from the repo root:
```
$DEV_VENV_PYTHON .claude\skills\add-narrow-emoji\classify.py <emoji>
The interpreter below is `$DEV_VENV_PYTHON` from `dev.env` at the repo root (gitignored;
`dev.env.example` shows the shape) — read it first.
```
$DEV_VENV_PYTHON .claude/skills/add-narrow-emoji/classify.py <emoji>
```
It prints the codepoints, how each authority measures it now (`Rich now`, `pt now`)
+5 -2
View File
@@ -15,9 +15,12 @@ stays exactly where it was, so the file's length never changes.
## Run
The interpreter below is `$DEV_VENV_PYTHON` from `dev.env` at the repo root (gitignored;
`dev.env.example` shows the shape) — read it first.
```
$DEV_VENV_PYTHON .claude\skills\fix-ans-nulls\denul.py --dry-run --width 71 meshterm/assets/splash/logo.71.ans
$DEV_VENV_PYTHON .claude\skills\fix-ans-nulls\denul.py --width 71 meshterm/assets/splash/logo.71.ans
$DEV_VENV_PYTHON .claude/skills/fix-ans-nulls/denul.py --dry-run --width 71 meshterm/assets/splash/logo.71.ans
$DEV_VENV_PYTHON .claude/skills/fix-ans-nulls/denul.py --width 71 meshterm/assets/splash/logo.71.ans
```
- Always `--dry-run` first and read the counts.
+17 -12
View File
@@ -14,18 +14,23 @@ on came from the real app on `/dev/tty1`.
## Reaching the device
`ssh -i $DEV_PICOCALC_SSH_KEY $DEV_PICOCALC_USER@$DEV_PICOCALC_HOST` (key installed; `scp` with the same flags
pushes files byte-exact). Password fallback: `plink -batch -pw $DEV_PICOCALC_PASSWORD`. `sudo` needs the
password piped: `echo $DEV_PICOCALC_PASSWORD | sudo -S …`.
The address, login, key and password are **not in this file**. Read `dev.env` at the
repo root first (gitignored; `dev.env.example` shows the shape) — the `$DEV_*` names
below are its keys, and everything here is written to be run with them substituted.
The repo lives at `$HOME/MeshTerm`, venv at `.venv`. Push changed modules with `scp`
`ssh -i $DEV_PICOCALC_SSH_KEY $DEV_PICOCALC_USER@$DEV_PICOCALC_HOST` (key installed;
`scp` with the same flags pushes files byte-exact). Password fallback:
`plink -batch -pw "$DEV_PICOCALC_PASSWORD"`. `sudo` needs the password piped:
`echo "$DEV_PICOCALC_PASSWORD" | sudo -S …`.
The repo lives at `$DEV_PICOCALC_REPO`, venv at `.venv`. Push changed modules with `scp`
straight into the tree — no reinstall, it is a `pip install -e .`.
**One-time**: reading the console back needs the `tty` group. Busybox's `addgroup $DEV_PICOCALC_USER tty`
misparses; edit `/etc/group` instead:
misparses; edit `/etc/group` instead (the trailing name is the deploy user):
```sh
echo $DEV_PICOCALC_PASSWORD | sudo -S sed -i 's/^tty:\(.*\):\(.*\)$/tty:\1:\2,$DEV_PICOCALC_USER/' /etc/group
echo "$DEV_PICOCALC_PASSWORD" | sudo -S sed -i 's/^tty:\(.*\):\(.*\)$/tty:\1:\2,'"$DEV_PICOCALC_USER"'/' /etc/group
```
Re-open the SSH session for it to take effect. Verify: `dd if=/dev/vcs1 bs=1378 count=1 | wc -c`
@@ -34,9 +39,9 @@ should print `1378` (53×26).
## Running a tour
```sh
scp -i $DEV_PICOCALC_SSH_KEY scripts/drive_console.py tours/nav_all.txt $DEV_PICOCALC_USER@…:$HOME/MeshTerm/
ssh -i … $DEV_PICOCALC_USER@… 'cd $HOME/MeshTerm && .venv/bin/python drive_console.py \
--script nav_all.txt --boot-wait 20 --shots $HOME/tmp/shots'
scp -i $DEV_PICOCALC_SSH_KEY scripts/drive_console.py tours/nav_all.txt "$DEV_PICOCALC_USER@$DEV_PICOCALC_HOST:$DEV_PICOCALC_REPO/"
ssh -i $DEV_PICOCALC_SSH_KEY "$DEV_PICOCALC_USER@$DEV_PICOCALC_HOST" "cd $DEV_PICOCALC_REPO && .venv/bin/python drive_console.py \
--script nav_all.txt --boot-wait 20 --shots ~/tmp/shots"
```
`drive_console.py` forks a pty sized to the panel, runs MeshTerm inside it, mirrors every
@@ -106,12 +111,12 @@ When a page is slow and you need to know *which stage*, run the app under `instr
instead of the binary:
```sh
ssh … 'cd $HOME/MeshTerm && .venv/bin/python drive_console.py --script nav_all.txt \
--exe $HOME/MeshTerm/run_instrumented.sh --boot-wait 20'
ssh … "cd $DEV_PICOCALC_REPO && .venv/bin/python drive_console.py --script nav_all.txt \
--exe $DEV_PICOCALC_REPO/run_instrumented.sh --boot-wait 20"
```
where `run_instrumented.sh` is a two-line wrapper `exec .venv/bin/python instrument.py "$@"`.
It appends a CSV row per repaint to `$HOME/tmp/keytrace.csv`: handle, header, compose,
It appends a CSV row per repaint to `~/tmp/keytrace.csv` on the device: handle, header, compose,
dialog, parse, paint, total, and the `render_to_ansi` call count. Rows tagged `~tick` are
idle repaints — what the app burns doing nothing.
@@ -89,7 +89,7 @@ def main():
ap.add_argument("--mock", action="store_true")
ap.add_argument("--boot-wait", type=float, default=12.0)
ap.add_argument("--out", default="")
ap.add_argument("--exe", default="$HOME/MeshTerm/.venv/bin/meshterm")
ap.add_argument("--exe", default=os.path.expanduser("~/MeshTerm/.venv/bin/meshterm"))
ap.add_argument("--quiet-ms", type=float, default=QUIET_S * 1000,
help="silence that ends a keystroke; raise it to time screen opens")
ap.add_argument("--tee", default="", help="also copy the app's output to this file")
@@ -1,7 +1,7 @@
"""Run the real MeshTerm with per-keystroke stage timing, logged to a CSV.
Wraps the stages a keystroke passes through and appends one row per repaint to
$MESHTERM_TRACE (default $HOME/tmp/keytrace.csv):
$MESHTERM_TRACE (default ~/tmp/keytrace.csv):
t, screen, action, dispatch_ms, handle_ms, header_ms, compose_ms, dialog_ms,
parse_ms, render_ms, flush_ms, total_ms, out_bytes, rta_calls, rta_ms
@@ -19,7 +19,7 @@ import os
import sys
import time
TRACE = os.environ.get("MESHTERM_TRACE", "$HOME/tmp/keytrace.csv")
TRACE = os.environ.get("MESHTERM_TRACE") or os.path.expanduser("~/tmp/keytrace.csv")
_acc: dict[str, float] = {}
_rows: list[str] = []
@@ -1,6 +1,10 @@
#!/bin/sh
# Hand this to drive_console.py as --exe to get a per-repaint stage breakdown.
# It runs MeshTerm through instrument.py, which appends CSV rows to
# $MESHTERM_TRACE (default $HOME/tmp/keytrace.csv).
cd $HOME/MeshTerm
exec $HOME/MeshTerm/.venv/bin/python $HOME/MeshTerm/instrument.py "$@"
# $MESHTERM_TRACE (default ~/tmp/keytrace.csv).
#
# The checkout is $MESHTERM_REPO, or ~/MeshTerm -- never a hardcoded /home/<someone>,
# since this runs as whatever user the device deployed under.
REPO="${MESHTERM_REPO:-$HOME/MeshTerm}"
cd "$REPO" || exit 1
exec "$REPO/.venv/bin/python" "$REPO/instrument.py" "$@"
+5 -2
View File
@@ -13,9 +13,12 @@ carrying the simulator's fingerprints and nothing else.
## Run
The interpreter below is `$DEV_VENV_PYTHON` from `dev.env` at the repo root (gitignored;
`dev.env.example` shows the shape) — read it first.
```
$DEV_VENV_PYTHON .claude\skills\purge-test-data\purge.py --dry-run
$DEV_VENV_PYTHON .claude\skills\purge-test-data\purge.py
$DEV_VENV_PYTHON .claude/skills/purge-test-data/purge.py --dry-run
$DEV_VENV_PYTHON .claude/skills/purge-test-data/purge.py
```
- Always `--dry-run` first and sanity-check the counts (real-session tables
+5 -2
View File
@@ -8,13 +8,16 @@ model: haiku
## Launch
- Machine-specific values (this venv, the BLE companions below) live in `dev.env` at
the repo root — gitignored, with `dev.env.example` showing the shape. Read it first;
the `$DEV_*` names here are its keys.
- Venv interpreter: `$DEV_VENV_PYTHON`
- Run the app as a module: `python -m meshterm …` (no install step needed).
- `--mock` swaps in the `MockDevice` simulator (four fake nodes streaming
adverts/telemetry, packets within a second) — no hardware required. It still
writes to the **real default database**, so expect run ids / totals to grow.
- Real hardware is available too: two BLE MeshCore companions
(`$DEV_BLE_COMPANION_PAIRED` is PIN-protected, `$DEV_BLE_COMPANION_OPEN` is open).
- Real hardware is available too: two BLE MeshCore companions —
`$DEV_BLE_COMPANION_PAIRED` is PIN-protected, `$DEV_BLE_COMPANION_OPEN` is open.
## Surfaces
+4
View File
@@ -22,6 +22,10 @@ venv/
*.db
output/
# Developer environment: the machine-specific half of the .claude/ skills (device
# addresses, logins, the local venv path). dev.env.example is tracked and shows the shape.
dev.env
# Built radio firmware (scripts/xiao-radio/build-firmware.sh writes it here)
scripts/xiao-radio/*.uf2
scripts/xiao-radio/_meshcore-build/
+28
View File
@@ -0,0 +1,28 @@
# MeshTerm developer environment — the machine-specific half of the .claude/ skills.
#
# Copy to `dev.env` (gitignored) and fill in your own values. Nothing in here is read by
# the app: `meshterm` is configured by `config.toml` and its own `MESHTERM_*` variables.
# These are only for driving *your* hardware from a dev session, so every name is
# `DEV_`-prefixed and cannot collide with the app's.
#
# set -a; . ./dev.env; set +a # sh/bash
# Get-Content dev.env | ... # or just read the values; nothing sources it for you
# --- this workstation ---------------------------------------------------------------
# Full path to the venv interpreter, so a skill can run the app without an install step.
DEV_VENV_PYTHON=/path/to/MeshTerm/.venv/bin/python
# --- the PicoCalc / Luckfox Lyra profiling device -----------------------------------
# Reachable over the LAN; see the `profile-picocalc` skill for what is done with it.
DEV_PICOCALC_HOST=192.0.2.10
DEV_PICOCALC_USER=youruser
DEV_PICOCALC_SSH_KEY=~/.ssh/your_key_ed25519
# Fallback only — key auth is installed. Rotate this if it ever reaches a shared machine.
DEV_PICOCALC_PASSWORD=changeme
# Where the repo is checked out on the device (a `pip install -e .` tree).
DEV_PICOCALC_REPO=/home/youruser/MeshTerm
# --- BLE test companions ------------------------------------------------------------
# Two MeshCore companions to verify against: one paired/PIN-protected, one open.
DEV_BLE_COMPANION_PAIRED=MeshCore-Example
DEV_BLE_COMPANION_OPEN=Example-Open
+3 -1
View File
@@ -32,7 +32,9 @@ sh calculinux-setup.sh
Two prerequisites the setup script checks for but cannot embed:
- **Deploy key** — place the read-only GitHub deploy key at `$HOME/.ssh/id_ed25519`
- **Deploy key** — place the read-only GitHub deploy key at
`/home/$DEPLOY_USER/.ssh/id_ed25519` (`DEPLOY_USER` defaults to `meshterm`; `KEY_PATH`
overrides the location outright)
(mode 600) before running; the clone uses it over SSH.
- **Wi-Fi credentials** — the kick only nudges a network iwd already knows. Either connect
once by hand (`iwctl station wlan0 connect <SSID>`) or export `WIFI_SSID` and `WIFI_PSK`
+1 -1
View File
@@ -34,7 +34,7 @@
# rounded frame corners + the list cursor the bare console can't draw).
#
# Two prerequisites this script cannot safely embed and will check for / guide you through:
# * the read-only deploy key at $HOME/.ssh/id_ed25519 (never commit a private key);
# * the read-only deploy key at $KEY_PATH (never commit a private key);
# * Wi-Fi credentials known to iwd. The kick only nudges an ALREADY-known network. Either
# provision it once by hand (`iwctl station wlan0 connect <SSID>`), or export
# WIFI_SSID and WIFI_PSK before running and this script writes the iwd config for you.