217625dcf7f4d7fff205f1fde2f4160254056caf
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e3ae668f4a |
Give the person at the prompt back what a redirect never wanted
Five small things the seam left standing, each one a rule applied a step too far or a question asked of the wrong flag. ack and a tool's closing message go to stderr instead of being dropped. The reason they were dropped was sound as far as it went — an acknowledgement is not the answer, and `meshterm contacts > f` must catch only the answer — but stdout is not the only stream. Someone watching a five-minute `config restore` gets the setting-by-setting ✓ back, and the redirect still catches nothing extra. tx-optimize asked --json whether it could prompt. That is a question about output format, and it answered this one wrong in both directions: with the flag a piped run failed cleanly, without it a scheduled run on a terminal sat waiting for a password nobody was going to type. It now asks stdin — and asks it properly on Windows, where NUL is a character device that isatty() calls a terminal and getpass then reads the console directly and blocks forever. A missing password is a usage error, since nothing was transmitted. courier send on an id nobody has raised a device failure, which told a scheduled sender to retry a typo. It is a bad argument: the outbox is a local file. --mock alongside --port/--ble/--tcp used to let --mock win silently, so a scheduled `--port COM7 --mock info` reported on a simulator while reading as though it had reached the radio. And stderr stops folding at 80 columns off a terminal, which is what made `grep 'not already connected' errors.log` come back empty — the sentence was there, with a newline through the middle of it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
109fe7645d |
Move every command onto the seam, and give the plain face its own premise
Forty-nine leaf commands now state a Report instead of printing one, so --json works on all of them at once rather than on the two that grew a branch for it. The two hand-rolled JSON paths are gone: devices no longer builds a payload dict of its own, and its empty scan exits 5 like the plain path always did rather than 0 — a rendering flag has no business moving the report. The plain face changes premise along with it. It used to be a serialisation format a person could squint at, and several of its rules existed only to make splitting safe. Splitting is the machine face's job now, so: - Names are bare. Alignment is the delimiter; the escaping under the quoting stays, because nothing else stops a name a node broadcast from ending its own record. - Times are ages. "Heard recently?" is the question a contacts listing is opened to ask, and an ISO instant makes the reader do arithmetic to answer it. --absolute puts the instants back; an appointment, the device clock and a live capture's own column were never ages and still aren't. - A route takes arrows and a path keeps its commas. The lexicon has always split these and the output never did, so a route line looked pasteable into --path, which it is not. - The listings gain what was cut for parser hygiene: contacts gets the HASH --path takes and a LOCATION the model has always carried, preferences gets the DESCRIPTION already written in its registry, and the two live streams get pinned lanes — which is what let them have columns at all, and with them the last call site for the quoting. - The four written pages wrap at 72 instead of printing 600-cell paragraphs. It is the one exception to "no wrapping", and the rule's own reason says why: a paragraph has no fields to land under the wrong headings. info keeps its keys and gains a gloss on the four readings nobody can hold in their head (93784 (1d 2h)); it is the one key/value block with no set behind it, so there is no round trip to protect. config show is untouched, deliberately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
59f10f904c |
The escape table was a list where it needed to be a category
Turning markup off on the scripted console stopped a node name being read as a style. It also stopped every tag MeshTerm writes *itself* from being consumed — so `config export-key > key.hex` wrote a file with `[muted]` and `[/muted]` around the private key that was supposed to be its whole content. `PlainUi.note` is documented to take markup and now resolves it to text, which fixes the leak wherever it is written rather than only where it was found; the key itself is bare on both faces. Escaping covered the newline, the tab and the return, and let ESC through — a live colour run written into the caller's file, which is the one thing "not a single escape sequence" exists to prevent. BEL was worse: 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 two Unicode separators, named escapes where one reads and numeric everywhere else. Legitimate non-ASCII is untouched. The covered-command list is now walked out of the Typer app instead of kept by hand. It was six commands of forty-nine, and both violations above sat in the forty-three — the `---` in `about` drawn as a rule the width of a 16384-cell console, and the tags above. Verified by putting each bug back and watching the sweep name it. Three assertions could not fail. `len(set(MEANINGS)) == len(MEANINGS)` is true of every dict, so "no two outcomes share a status" was unenforced — renumbering NO_DEVICE to 4 left all three green while status 3 vanished from --help. `" " * script.GUTTER in record` reads the constant it checks, narrowing to `" " in record` exactly when the gutter narrows. Both are literals now. The four rule tests read `result.output`, which is stdout and stderr mixed, while the rules they enforce are about stdout — so a heading that only ever appeared on stderr would have satisfied them. And $MESHTERM_HOME is isolated for every test, not just the CLI ones: --db moves the history and nothing else, so any test touching a store, a preference or the logger was reading the developer's own directory. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
98c635a62e |
Refuse the wrong answer instead of quietly giving it
Four places where the CLI answered a question the caller had not asked. `--profile yagi` naming no profile fell through to ordinary discovery, so a typo in a scheduled advert transmitted from whichever radio happened to be attached. Naming a profile is a claim about which device; an unkeepable claim is a bad argument, not a default. `--sort bogus` sorted by the default and `--category long_hual` selected every discipline — which then came back as exit 5, the code that means a real empty result. A caller branching on 5 could not tell "no records" from "you misspelled the discipline". `ContactsSort.from_name` still forgives an unknown name, because a saved preference from an older build must not wedge the menu's list; the command line refuses instead. A `--port` that would not open reported exit 4 and "the connection to the device was lost", describing a connection there had never been. Every failure while acquiring the device is the other status: nothing was transmitted. It now says which port, and why. A message body could hold a newline, which ended its record early and left the remainder indented under the other columns as a second record with an empty TIME — the one field a stranger fills in, breaking the one rule the format has. script.oneline folds it flat. And an unnamed node read as `""` in one listing and `-` in the next: seven of the ten places that print a name were claiming "called nothing" where they meant "never said". script.name draws the distinction once, which is also the one the JSON rendering will need. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
369e29e44a |
Five ways the scripted output lied, and the name that crashed it
The review pass found the CLI breaking its own written rules. A node broadcasts its own name, so every name the CLI prints is remote data — and Rich read `[...]` in it as a style tag. A node called `[bold]Loud` printed as `Loud`, silently no longer the string that identifies it; a node called `[/]Bob` raised MarkupError and took the command down. The log handler then failed the same way while reporting it, replacing the diagnostic with a traceback about the diagnostic. No log line and no scripted line is ever marked up, so all three consoles now say so. `right=` never right-aligned anything. Rich's `Text.wrap` returns early on `overflow="ignore"`, above the justify step, so asking for one threw the other away — every numeric lane in the CLI was left-aligned while CLAUDE.md, script.py's own docstring and the manual all said otherwise. "crop" refuses to elide just as firmly and still aligns. `config show` printed an empty string as `""` and `config set` read those two characters literally, so feeding a dump back in 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 six bullets, telling the reader the radio was PIN-locked when it was not: `conceal` already declined to mask the menu's absence token and had simply never been shown the CLI's. `meshterm about` drew the `---` above its colophon as a rule sized to the console, which on a 16384-cell console is one 48KB line in a 4KB page. A rule is a frame; the scripted copy drops it with the QR. The contract fixture set only `--db`, which moves the history and nothing else — so the suite read whatever was in the developer's own ~/.meshterm. That is how `[/]Bob`, planted by hand on one machine, could crash the suite there and pass everywhere else. $MESHTERM_HOME moves the whole directory. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
da05dd402f |
Give the CLI its own voice, meant for a pipe rather than an eye
The CLI had grown the menu's manners -- colour, boxes, section headings, glyph columns, a closing check mark -- and every one of them is in the way of the thing it is actually for. It now prints like a standard Unix utility: plain text, no escape sequences at all, no frames, one record per line, and nothing wrapped, so a long line runs off the right instead of folding its fields onto a second one. meshterm/ui/script.py is the whole vocabulary. Listings are an uppercase header and space-aligned records; key/value blocks are sysctl's shape, and a `get` prints the bare value alone. Names are quoted wherever they share a line, because a name can hold a space or a comma and a bare one is not a field. A route reads "Origin" (3d),"Relay" (f2),"Us" (a1) -- the hash outside the quotes, our own node a hop like any other rather than a star, since the hash is what carries the identity that colour carries on screen. Times are absolute local ISO-8601 and "-" is the one token for absent. The return value is now half the report: 0 ok, 1 failure, 2 usage, 3 no device, 4 the device was reached and the operation failed, 5 nothing to report. That last one is the point -- a command that ran fine and found nothing prints nothing and says so in $?, so a script never has to count output lines to tell an empty mesh from a full one. Errors and progress moved to stderr, and an expected failure no longer arrives as a traceback. Two things lose their scripted face rather than get a degraded one. The map is a picture whose nodes are told apart by colour: monochrome it would be unreadable, coloured it could not be piped anywhere useful, and the nodes themselves are still in `contacts`. The QR codes are a second rendering of a link the output already prints. ui.ack joins ui.note to draw the line the change needed: a note is output, an acknowledgement is reassurance a person wants and a script must filter out, so the menu prints it and the CLI drops it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |