diff --git a/meshterm/__init__.py b/meshterm/__init__.py index f759b7d..a861b1e 100644 --- a/meshterm/__init__.py +++ b/meshterm/__init__.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """MeshTerm is a full-featured TUI-based MeshCore client for your terminal. Supports USB, Bluetooth and TCP companion connection. For Windows, macOS and Linux. diff --git a/meshterm/__main__.py b/meshterm/__main__.py index ed77f43..b8c54c4 100644 --- a/meshterm/__main__.py +++ b/meshterm/__main__.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Enable ``python -m meshterm``.""" from .cli import main diff --git a/meshterm/cli.py b/meshterm/cli.py index d8d6db5..3611f09 100644 --- a/meshterm/cli.py +++ b/meshterm/cli.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Typer entry point. Global options are parsed once in the callback, which builds the shared diff --git a/meshterm/context.py b/meshterm/context.py index 346c78b..ad3704b 100644 --- a/meshterm/context.py +++ b/meshterm/context.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The application context: a small dependency container passed to every tool. ``AppContext`` owns the shared, expensive singletons (console, settings, repository, diff --git a/meshterm/core/__init__.py b/meshterm/core/__init__.py index 0b9e91b..6cae4b8 100644 --- a/meshterm/core/__init__.py +++ b/meshterm/core/__init__.py @@ -1 +1,2 @@ +# SPDX-License-Identifier: Apache-2.0 """Core domain layer: models, configuration, and the device connection abstraction.""" diff --git a/meshterm/core/admin_store.py b/meshterm/core/admin_store.py index 0df4286..048f51d 100644 --- a/meshterm/core/admin_store.py +++ b/meshterm/core/admin_store.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Persistence for remembered remote-node admin passwords. The remote-admin optimizer logs in to a repeater to tune its TX power; storing the diff --git a/meshterm/core/advert_store.py b/meshterm/core/advert_store.py index 0f90f37..73a5a43 100644 --- a/meshterm/core/advert_store.py +++ b/meshterm/core/advert_store.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Persistence for the background-advert schedule. MeshTerm announces the connected node on a cadence — a zero-hop (direct) advert for the diff --git a/meshterm/core/atomicwrite.py b/meshterm/core/atomicwrite.py index 4b6b03d..61f47ab 100644 --- a/meshterm/core/atomicwrite.py +++ b/meshterm/core/atomicwrite.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """THE way MeshTerm replaces a file it owns: all of the new contents, or none of them. Every store under ``core/`` keeps its state in one file that it rewrites whole. Writing diff --git a/meshterm/core/channel_probe.py b/meshterm/core/channel_probe.py index 6f4a3de..e1788c1 100644 --- a/meshterm/core/channel_probe.py +++ b/meshterm/core/channel_probe.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Reading the device's channel slots back off the radio. One configured channel slot (:class:`ChannelSlot`) and the two reads that discover them. diff --git a/meshterm/core/channel_store.py b/meshterm/core/channel_store.py index 5056390..5588a79 100644 --- a/meshterm/core/channel_store.py +++ b/meshterm/core/channel_store.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Persistence for channels created through MeshTerm, so they survive a device that forgets. Most companions keep their own channel table in firmware, so MeshTerm reads channels live from diff --git a/meshterm/core/channels.py b/meshterm/core/channels.py index 759f3ec..0b4f1a3 100644 --- a/meshterm/core/channels.py +++ b/meshterm/core/channels.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Pure channel logic: secret derivation, MeshCore share-URL round-tripping, and decrypt. A MeshCore channel is a name plus a 16-byte shared secret. *Public* channels (whose name diff --git a/meshterm/core/config.py b/meshterm/core/config.py index ab72b60..a420997 100644 --- a/meshterm/core/config.py +++ b/meshterm/core/config.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Machine setup and named device profiles. Settings are read from a TOML file (default ``~/.meshterm/config.toml``) and may diff --git a/meshterm/core/config_io.py b/meshterm/core/config_io.py index cde6f27..e40fbc6 100644 --- a/meshterm/core/config_io.py +++ b/meshterm/core/config_io.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """TOML backup and restore for device configuration. A backup captures every registry setting's current value plus experimental custom diff --git a/meshterm/core/connection.py b/meshterm/core/connection.py index bab96d0..c81a315 100644 --- a/meshterm/core/connection.py +++ b/meshterm/core/connection.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Device connection abstraction. Services and tools depend only on the :class:`Device` interface, never on the diff --git a/meshterm/core/consolefont.py b/meshterm/core/consolefont.py index 9d2d6ef..4a96852 100644 --- a/meshterm/core/consolefont.py +++ b/meshterm/core/consolefont.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 r"""Installing and selecting a console font on Windows, for one user, without admin. The classic Windows console does no font fallback: a character its configured font lacks diff --git a/meshterm/core/contact_score.py b/meshterm/core/contact_score.py index 4599e5e..6ddbe89 100644 --- a/meshterm/core/contact_score.py +++ b/meshterm/core/contact_score.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Rating contacts by how much they are worth a slot in the device's contact table. A companion's contact table is finite, and a mesh fills it with whatever adverts happen to diff --git a/meshterm/core/contact_store.py b/meshterm/core/contact_store.py index 54693de..d1aad32 100644 --- a/meshterm/core/contact_store.py +++ b/meshterm/core/contact_store.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Persistence for contacts heard through MeshTerm, so they survive a device that forgets them. Most companions keep their contact table in firmware, so MeshTerm reads contacts live from the diff --git a/meshterm/core/courier_store.py b/meshterm/core/courier_store.py index 1b2ff0d..ba563f5 100644 --- a/meshterm/core/courier_store.py +++ b/meshterm/core/courier_store.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Persistence for the Courier: the store-and-forward outbox. The Courier (see :mod:`meshterm.services.courier`) queues direct messages for contacts diff --git a/meshterm/core/device_config.py b/meshterm/core/device_config.py index 5f280a2..9d8b4eb 100644 --- a/meshterm/core/device_config.py +++ b/meshterm/core/device_config.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Declarative registry of every settable device configuration value. Each :class:`SettingSpec` describes one tunable setting: how to read its current value diff --git a/meshterm/core/device_store.py b/meshterm/core/device_store.py index f345710..7be83b0 100644 --- a/meshterm/core/device_store.py +++ b/meshterm/core/device_store.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Persistence for confirmed companion devices. Every device that has ever spoken the MeshCore protocol to us successfully is remembered diff --git a/meshterm/core/discovery.py b/meshterm/core/discovery.py index a0131ee..a4f3e33 100644 --- a/meshterm/core/discovery.py +++ b/meshterm/core/discovery.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Companion-device discovery across transports (serial + Bluetooth LE + TCP). Discovery enumerates the companion kinds that can be *found* automatically: diff --git a/meshterm/core/events.py b/meshterm/core/events.py index 6be58c3..8a5705b 100644 --- a/meshterm/core/events.py +++ b/meshterm/core/events.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Typed events carried by the always-on mesh event hub. These are the domain-level events that flow from the connected companion device out to diff --git a/meshterm/core/exitcodes.py b/meshterm/core/exitcodes.py index b50047a..b4bad50 100644 --- a/meshterm/core/exitcodes.py +++ b/meshterm/core/exitcodes.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The exit statuses ``meshterm`` returns, and what each one means. A scripted run's *return value is its report*: the CLI says what happened in plain diff --git a/meshterm/core/frames.py b/meshterm/core/frames.py index cad83cb..2c97e1a 100644 --- a/meshterm/core/frames.py +++ b/meshterm/core/frames.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """What an overheard frame is *addressed to* — the packet body nobody else parses. The companion's RX packet log hands every overheard frame to the meshcore library, which diff --git a/meshterm/core/geo.py b/meshterm/core/geo.py index 923ad14..cc853e1 100644 --- a/meshterm/core/geo.py +++ b/meshterm/core/geo.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Pure geographic helpers for the terminal map: Web Mercator, tiles, and viewports. This module is deliberately I/O-free and rendering-free so it can be unit-tested without a diff --git a/meshterm/core/instancelock.py b/meshterm/core/instancelock.py index 73388b7..695d4e6 100644 --- a/meshterm/core/instancelock.py +++ b/meshterm/core/instancelock.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """One interactive MeshTerm per data directory, and a clear answer when you want two. Every copy of MeshTerm on a machine reads and writes the same ``~/.meshterm`` — a diff --git a/meshterm/core/models.py b/meshterm/core/models.py index 342bd2a..bdfad96 100644 --- a/meshterm/core/models.py +++ b/meshterm/core/models.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Domain models shared across services, tools, persistence, and visualizations. These are plain dataclasses with no I/O dependencies so they can be constructed by the diff --git a/meshterm/core/mute_store.py b/meshterm/core/mute_store.py index 4c63900..74e878f 100644 --- a/meshterm/core/mute_store.py +++ b/meshterm/core/mute_store.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Persistence for muted channel notifications. Some channels carry traffic you want *recorded* but not *notified* about — an automated diff --git a/meshterm/core/mvt.py b/meshterm/core/mvt.py index 6c150a4..3159b9a 100644 --- a/meshterm/core/mvt.py +++ b/meshterm/core/mvt.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """A tiny, dependency-free Mapbox Vector Tile (MVT) decoder. Vector tiles are Protocol-Buffer messages (spec: github.com/mapbox/vector-tile-spec). Rather diff --git a/meshterm/core/preferences.py b/meshterm/core/preferences.py index 1a6390a..b4ce2a5 100644 --- a/meshterm/core/preferences.py +++ b/meshterm/core/preferences.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Declarative registry of MeshTerm's own preferences, and the TOML file they live in. The distinction this module draws is between the three kinds of "setting" the app already diff --git a/meshterm/core/remote_config.py b/meshterm/core/remote_config.py index ec0cc65..efd4308 100644 --- a/meshterm/core/remote_config.py +++ b/meshterm/core/remote_config.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The remote-node settings catalog: what a repeater can be asked and told over the mesh. Repeaters and room servers are administered through their text CLI — ``get``/``set`` diff --git a/meshterm/core/remote_store.py b/meshterm/core/remote_store.py index 061659c..65a1765 100644 --- a/meshterm/core/remote_store.py +++ b/meshterm/core/remote_store.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Persistence for remote-node admin state: last-read settings and CLI history. Reading a repeater's configuration costs one paced mesh round trip per value, so the diff --git a/meshterm/core/selection.py b/meshterm/core/selection.py index 3c6b44f..b3af113 100644 --- a/meshterm/core/selection.py +++ b/meshterm/core/selection.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Non-interactive device selection: turn discovery + memory into a chosen port. This is the logic used on the scripted CLI path (and as the fallback when the interactive diff --git a/meshterm/core/settings_store.py b/meshterm/core/settings_store.py index f943d62..8ca3c53 100644 --- a/meshterm/core/settings_store.py +++ b/meshterm/core/settings_store.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Persistence for device settings changed through MeshTerm, so they survive a forgetful device. Most companions keep their configuration in firmware, so MeshTerm reads settings live from the diff --git a/meshterm/core/tracing.py b/meshterm/core/tracing.py index 50280d9..7b90bd3 100644 --- a/meshterm/core/tracing.py +++ b/meshterm/core/tracing.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The arithmetic a trace is sized and flagged by. Two pure functions and the four constants behind them: how a per-hop path-hash width is diff --git a/meshterm/core/transmit_gate.py b/meshterm/core/transmit_gate.py index 7defe2b..f1df91a 100644 --- a/meshterm/core/transmit_gate.py +++ b/meshterm/core/transmit_gate.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The shared transmit clock: how long ago we last put something on the air. MeshTerm has always paced its own bursts — a trace waits between samples, a TX sweep diff --git a/meshterm/core/watch_store.py b/meshterm/core/watch_store.py index fd42357..5f5bf7b 100644 --- a/meshterm/core/watch_store.py +++ b/meshterm/core/watch_store.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Persistence for the Watchtower: watched nodes, their rules, and the alert log. The Watchtower (see :mod:`meshterm.services.watchtower`) is a passive sentinel: the user diff --git a/meshterm/core/win32dll.py b/meshterm/core/win32dll.py index 651c5ef..eabf9ae 100644 --- a/meshterm/core/win32dll.py +++ b/meshterm/core/win32dll.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Private handles on the Windows DLLs, because ``ctypes.windll`` is shared with everyone. ``ctypes.windll.kernel32`` is a cache. Every caller in the process gets the *same* diff --git a/meshterm/core/winterminal.py b/meshterm/core/winterminal.py index 29ede89..d18927b 100644 --- a/meshterm/core/winterminal.py +++ b/meshterm/core/winterminal.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Reopening the session in Windows Terminal, which is where MeshTerm looks like itself. MeshTerm is drawn with emoji icons, braille charts and powerline path chips. Whether any diff --git a/meshterm/persistence/__init__.py b/meshterm/persistence/__init__.py index 31891e0..5e42d20 100644 --- a/meshterm/persistence/__init__.py +++ b/meshterm/persistence/__init__.py @@ -1 +1,2 @@ +# SPDX-License-Identifier: Apache-2.0 """Persistence layer: SQLite schema, repository, and structured logging.""" diff --git a/meshterm/persistence/db.py b/meshterm/persistence/db.py index 4afb743..45f731a 100644 --- a/meshterm/persistence/db.py +++ b/meshterm/persistence/db.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """SQLite database bootstrap and schema. The schema is intentionally generic: a central ``runs`` table records every tool diff --git a/meshterm/persistence/logging.py b/meshterm/persistence/logging.py index 3d19a8d..20c48cf 100644 --- a/meshterm/persistence/logging.py +++ b/meshterm/persistence/logging.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Logging setup: a plain text file you can open, and a console that stays out of the way. Two sinks. The console handler is Rich-formatted and shows what a person watching the diff --git a/meshterm/persistence/repository.py b/meshterm/persistence/repository.py index 60c959c..2cdbe78 100644 --- a/meshterm/persistence/repository.py +++ b/meshterm/persistence/repository.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Repository: the single gateway between the app and the database. Tools and services never issue SQL directly; they call typed methods here. This keeps diff --git a/meshterm/platforms.py b/meshterm/platforms.py index 216e93b..1c29ec3 100644 --- a/meshterm/platforms.py +++ b/meshterm/platforms.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The platform seam: a frozen spec every consumer binds to once, at boot. MeshTerm runs two flavours from one codebase — the regular desktop terminal and the diff --git a/meshterm/services/__init__.py b/meshterm/services/__init__.py index 8f47169..a81edd9 100644 --- a/meshterm/services/__init__.py +++ b/meshterm/services/__init__.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Algorithm layer: pure logic for tracing, TX search, and path exploration. Services may depend on :mod:`meshterm.core` (the :class:`~meshterm.core.connection.Device` diff --git a/meshterm/services/advert_scheduler.py b/meshterm/services/advert_scheduler.py index 2d3bb47..40bb953 100644 --- a/meshterm/services/advert_scheduler.py +++ b/meshterm/services/advert_scheduler.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The background-advert scheduler: keep this node announced without anyone asking. A quiet session-long task that sends a zero-hop (direct) advert and a flood advert on diff --git a/meshterm/services/basemap.py b/meshterm/services/basemap.py index f44bd39..6f7f40d 100644 --- a/meshterm/services/basemap.py +++ b/meshterm/services/basemap.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Fetch and cache OpenStreetMap vector tiles for the terminal map. This is the only networked part of the map. It resolves a tile source's (versioned) tile-URL diff --git a/meshterm/services/battery_service.py b/meshterm/services/battery_service.py index 559db67..4f3f50d 100644 --- a/meshterm/services/battery_service.py +++ b/meshterm/services/battery_service.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The battery poller: keep the status bar's fuel gauge current, quietly. A session-long task that reads the connected companion's battery voltage on a slow diff --git a/meshterm/services/chat_service.py b/meshterm/services/chat_service.py index 47b3b07..e5aab5c 100644 --- a/meshterm/services/chat_service.py +++ b/meshterm/services/chat_service.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The chat service: message persistence, unread tracking, and the send path. Like the passive monitor, this service is not a listener in its own right — the always-on diff --git a/meshterm/services/consolefont.py b/meshterm/services/consolefont.py index 4d1c2b7..044b25f 100644 --- a/meshterm/services/consolefont.py +++ b/meshterm/services/consolefont.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The PicoCalc's console font: remember what was there, switch, put it back. The handheld draws to a Linux virtual terminal, and a VT loads exactly one font for the diff --git a/meshterm/services/courier.py b/meshterm/services/courier.py index 70cc006..a474489 100644 --- a/meshterm/services/courier.py +++ b/meshterm/services/courier.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The Courier: store-and-forward delivery for the outbox. A session-long background loop over the :class:`~meshterm.core.courier_store. diff --git a/meshterm/services/device_state.py b/meshterm/services/device_state.py index 4f5583e..2230bcb 100644 --- a/meshterm/services/device_state.py +++ b/meshterm/services/device_state.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The session's device-state cache: read the radio once, reuse it everywhere. Opening a screen used to re-read the same stable facts from the companion every time — diff --git a/meshterm/services/event_hub.py b/meshterm/services/event_hub.py index 0ed8694..ab502b3 100644 --- a/meshterm/services/event_hub.py +++ b/meshterm/services/event_hub.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The always-on mesh event hub. The hub is the single seam between the raw companion-device event stream and everything diff --git a/meshterm/services/markers.py b/meshterm/services/markers.py index 3f5b60e..748778b 100644 --- a/meshterm/services/markers.py +++ b/meshterm/services/markers.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Collecting the mesh's located nodes into map markers. One list, three surfaces: the ``map`` tool's full-screen map, the node page's preview minimap, diff --git a/meshterm/services/message_paths.py b/meshterm/services/message_paths.py index f08927d..e1c22aa 100644 --- a/meshterm/services/message_paths.py +++ b/meshterm/services/message_paths.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Recover the paths a chat message rode in on, from the stored packet log. The radio's RX log records every frame it decodes — including the relay path each one diff --git a/meshterm/services/modifier_watch.py b/meshterm/services/modifier_watch.py index 3a05793..67179ee 100644 --- a/meshterm/services/modifier_watch.py +++ b/meshterm/services/modifier_watch.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The optional Shift-state watcher behind the PicoCalc F-key lane's live flip. The PicoCalc keyboard's MCU translates Shift+F1..F5 into plain F6–F10 keycodes, so the diff --git a/meshterm/services/monitor_service.py b/meshterm/services/monitor_service.py index ed8f8be..b12c6f4 100644 --- a/meshterm/services/monitor_service.py +++ b/meshterm/services/monitor_service.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The passive-monitor history logger. Passive monitoring records every advert/telemetry frame the companion overhears to the diff --git a/meshterm/services/path_probe.py b/meshterm/services/path_probe.py index ec6302e..2319427 100644 --- a/meshterm/services/path_probe.py +++ b/meshterm/services/path_probe.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Multi-path probing: measure candidate routes to a target and rank what actually works. The topology graph (:mod:`~meshterm.services.topology`) proposes routes from *received* diff --git a/meshterm/services/records.py b/meshterm/services/records.py index d57400b..bd46502 100644 --- a/meshterm/services/records.py +++ b/meshterm/services/records.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Walk records: how a completed trace is measured and scored for the trophy case. A *walk* is one trace whose route starts and ends at our node — we transmit the first diff --git a/meshterm/services/topology.py b/meshterm/services/topology.py index 7bcae3f..6dd4dd6 100644 --- a/meshterm/services/topology.py +++ b/meshterm/services/topology.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The observed mesh topology: a link graph distilled from every path we ever received. MeshCore never tells us the network's shape outright, but almost everything we *receive* diff --git a/meshterm/services/trace_runner.py b/meshterm/services/trace_runner.py index 835f3c6..90dc2eb 100644 --- a/meshterm/services/trace_runner.py +++ b/meshterm/services/trace_runner.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Repeated-trace execution with duty-cycle pacing, plus trace parsing helpers. :func:`run_traces` is the measurement primitive the TX optimizer and the path probe diff --git a/meshterm/services/tx_optimizer.py b/meshterm/services/tx_optimizer.py index 91bfdcf..d475a42 100644 --- a/meshterm/services/tx_optimizer.py +++ b/meshterm/services/tx_optimizer.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Remote-admin TX-power optimization. This tunes the transmit power of a *remote* node we have admin rights on — the node diff --git a/meshterm/services/watchtower.py b/meshterm/services/watchtower.py index 76d1c2c..2c08b97 100644 --- a/meshterm/services/watchtower.py +++ b/meshterm/services/watchtower.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The Watchtower: passive rules over what the hub already hears, raising alerts. A sentinel, not a prober — it transmits nothing and asks the radio for nothing. It rides diff --git a/meshterm/tools/__init__.py b/meshterm/tools/__init__.py index 50ea32b..ff7c644 100644 --- a/meshterm/tools/__init__.py +++ b/meshterm/tools/__init__.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Pluggable tools (menu options / CLI subcommands). Importing this package eagerly imports every tool module so that each one's ``@register`` diff --git a/meshterm/tools/about.py b/meshterm/tools/about.py index 5e8927d..37e0df4 100644 --- a/meshterm/tools/about.py +++ b/meshterm/tools/about.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The About MeshTerm tools: the four written pages in the menu's last section. *About MeshTerm*, *About the author*, *Join Discord*, *Support MeshTerm* — in that diff --git a/meshterm/tools/advert.py b/meshterm/tools/advert.py index c95f921..e7bfef2 100644 --- a/meshterm/tools/advert.py +++ b/meshterm/tools/advert.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The ``advert`` tool: announce this node to the mesh, straight from the main menu. Opens the Send advert flow (see :func:`meshterm.ui.config_editor.send_advert`) — a diff --git a/meshterm/tools/base.py b/meshterm/tools/base.py index 2c66442..c37cf80 100644 --- a/meshterm/tools/base.py +++ b/meshterm/tools/base.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The tool plugin contract and registry. A *tool* is one menu option / CLI subcommand. Subclass :class:`Tool`, decorate it with diff --git a/meshterm/tools/channels.py b/meshterm/tools/channels.py index c0fb4b6..64179c1 100644 --- a/meshterm/tools/channels.py +++ b/meshterm/tools/channels.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The ``channels`` tool: create, join, and manage mesh channels. Interactively it opens a full-screen channel manager (see :mod:`meshterm.ui.channels`) — a diff --git a/meshterm/tools/chat.py b/meshterm/tools/chat.py index a1c761c..beaeabf 100644 --- a/meshterm/tools/chat.py +++ b/meshterm/tools/chat.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The ``chat`` tool: channel and direct messaging over the mesh. Interactively it opens a conversation picker and then a live, full-screen chat (see diff --git a/meshterm/tools/config.py b/meshterm/tools/config.py index e3f533a..9455803 100644 --- a/meshterm/tools/config.py +++ b/meshterm/tools/config.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The ``config`` tool: every device setting, and the operations on the box itself. Interactively it opens the Device config page (see :mod:`meshterm.ui.config_editor`) — diff --git a/meshterm/tools/contacts.py b/meshterm/tools/contacts.py index 4a273fb..95d6bd3 100644 --- a/meshterm/tools/contacts.py +++ b/meshterm/tools/contacts.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The ``contacts`` tool: list the contacts this node knows about. The menu opens the sortable Contacts screen, which leads with our own node; the CLI diff --git a/meshterm/tools/courier.py b/meshterm/tools/courier.py index 4320253..69111d6 100644 --- a/meshterm/tools/courier.py +++ b/meshterm/tools/courier.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The ``courier`` tool: store-and-forward messaging for contacts that aren't there yet. Interactively it opens the outbox screen (see :mod:`meshterm.ui.courier_screen`): diff --git a/meshterm/tools/dashboard.py b/meshterm/tools/dashboard.py index e75058d..e10873b 100644 --- a/meshterm/tools/dashboard.py +++ b/meshterm/tools/dashboard.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The ``dashboard`` tool: the live mesh overview, at the top of the Watch section. Opens the full-screen dashboard (see :mod:`meshterm.ui.dashboard_screen`): the two-hour diff --git a/meshterm/tools/devices.py b/meshterm/tools/devices.py index b6657ba..9f9a3f3 100644 --- a/meshterm/tools/devices.py +++ b/meshterm/tools/devices.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The ``devices`` tool: enumerate attached serial and in-range Bluetooth companions. Discovery never opens the radio — it only lists what is attached or advertising. This is a diff --git a/meshterm/tools/info.py b/meshterm/tools/info.py index b440f7f..d6017bf 100644 --- a/meshterm/tools/info.py +++ b/meshterm/tools/info.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The ``info`` tool: show the connected companion device's identity and radio config.""" from __future__ import annotations diff --git a/meshterm/tools/livefeed.py b/meshterm/tools/livefeed.py index 9485620..9f250ed 100644 --- a/meshterm/tools/livefeed.py +++ b/meshterm/tools/livefeed.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The ``livefeed`` tool: every packet as it arrives, right under the dashboard. Opens the full-screen live feed (see :mod:`meshterm.ui.livefeed_screen`): the diff --git a/meshterm/tools/map.py b/meshterm/tools/map.py index d078636..3819bfb 100644 --- a/meshterm/tools/map.py +++ b/meshterm/tools/map.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The ``map`` tool: plot the mesh's located nodes on an OpenStreetMap terminal map. Nodes that share their location in adverts are collected from the passive-monitor history diff --git a/meshterm/tools/monitor.py b/meshterm/tools/monitor.py index ed8ca98..bfd65e9 100644 --- a/meshterm/tools/monitor.py +++ b/meshterm/tools/monitor.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The ``monitor`` tool: a bounded foreground capture of what the mesh is saying. Passive monitoring records every advert and telemetry frame the companion overhears — diff --git a/meshterm/tools/preferences.py b/meshterm/tools/preferences.py index 352bf9a..82791e7 100644 --- a/meshterm/tools/preferences.py +++ b/meshterm/tools/preferences.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The ``preferences`` tool: how MeshTerm itself behaves. Interactively it opens the Preferences page (see :mod:`meshterm.ui.preferences`), which diff --git a/meshterm/tools/records.py b/meshterm/tools/records.py index b7d4eeb..3358728 100644 --- a/meshterm/tools/records.py +++ b/meshterm/tools/records.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The Trophy case tool: the record-setting walks the trace tools have turned up. The menu face opens the browser (:mod:`meshterm.ui.records_screen`): every discipline's diff --git a/meshterm/tools/repeater_admin.py b/meshterm/tools/repeater_admin.py index 87377d1..223ef5a 100644 --- a/meshterm/tools/repeater_admin.py +++ b/meshterm/tools/repeater_admin.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The ``repeater-admin`` tool: set up remote repeaters and room servers over the mesh. Interactively it opens the repeater-admin flow (see :mod:`meshterm.ui.repeater_admin`): diff --git a/meshterm/tools/timemachine.py b/meshterm/tools/timemachine.py index c90d624..05acd42 100644 --- a/meshterm/tools/timemachine.py +++ b/meshterm/tools/timemachine.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The ``timemachine`` tool: explore everything the recorder ever heard. Opens the Time Machine (see :mod:`meshterm.ui.timemachine_screen`): pick the whole mesh diff --git a/meshterm/tools/trace.py b/meshterm/tools/trace.py index 702b4b8..8e9c17b 100644 --- a/meshterm/tools/trace.py +++ b/meshterm/tools/trace.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The trace tools: path traces, live in the menu, one-shot on the CLI. A trace is one walked path — the protocol has no destination field, so "target" is diff --git a/meshterm/tools/tx_optimize.py b/meshterm/tools/tx_optimize.py index 79acaf3..2eea79a 100644 --- a/meshterm/tools/tx_optimize.py +++ b/meshterm/tools/tx_optimize.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The ``tx-optimize`` tool: tune a remote node's TX power for the best signal at a target. You force a path (just like ``trace``) ending at the **target** node where SNR is diff --git a/meshterm/tools/walk.py b/meshterm/tools/walk.py index 48df97b..db67c60 100644 --- a/meshterm/tools/walk.py +++ b/meshterm/tools/walk.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The ``walk`` tool: the mesh's observed shape, explored one node at a time. Opens the full-screen Mesh walk (see :mod:`meshterm.ui.walk_screen`): a walkable diff --git a/meshterm/tools/watchtower.py b/meshterm/tools/watchtower.py index f4678f9..3da564b 100644 --- a/meshterm/tools/watchtower.py +++ b/meshterm/tools/watchtower.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The ``watchtower`` tool: a passive sentinel over the nodes you care about. Opens the Watchtower screen (see :mod:`meshterm.ui.watchtower_screen`): the alert log diff --git a/meshterm/ui/__init__.py b/meshterm/ui/__init__.py index 285f671..40c4a3b 100644 --- a/meshterm/ui/__init__.py +++ b/meshterm/ui/__init__.py @@ -1 +1,2 @@ +# SPDX-License-Identifier: Apache-2.0 """Presentation layer: Rich theme/widgets and the prompt_toolkit full-screen session.""" diff --git a/meshterm/ui/about.py b/meshterm/ui/about.py index 30bc01d..0ac44ab 100644 --- a/meshterm/ui/about.py +++ b/meshterm/ui/about.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The About MeshTerm pages: the app, the person who wrote it, and how to help it along. Four read-only pages hung under the main menu's *About MeshTerm* section — the one diff --git a/meshterm/ui/admin_picker.py b/meshterm/ui/admin_picker.py index de40c88..d06d4f3 100644 --- a/meshterm/ui/admin_picker.py +++ b/meshterm/ui/admin_picker.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The admin-node picker: choose a remote node you hold (or will enter) credentials for. Shared by the tools that administer remote nodes over the mesh — TX optimize and the diff --git a/meshterm/ui/archived_screen.py b/meshterm/ui/archived_screen.py index 0459375..740fb2d 100644 --- a/meshterm/ui/archived_screen.py +++ b/meshterm/ui/archived_screen.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The Archived contacts list: what the sweep took off the device, and when. A purge (see :mod:`~meshterm.ui.purge_screen`) removes a contact from the *device* — the diff --git a/meshterm/ui/braillechart.py b/meshterm/ui/braillechart.py index d43616d..04cc753 100644 --- a/meshterm/ui/braillechart.py +++ b/meshterm/ui/braillechart.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Braille charts: the one way MeshTerm draws a value-over-time row or a meter. Every timeline in the app — the header's activity pulse, the channel manager's diff --git a/meshterm/ui/channels.py b/meshterm/ui/channels.py index 2d5d485..435a896 100644 --- a/meshterm/ui/channels.py +++ b/meshterm/ui/channels.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The interactive channel manager, rendered in the full-screen session. This is the user-facing channel-management experience for the ``channels`` tool: a live diff --git a/meshterm/ui/chat.py b/meshterm/ui/chat.py index b3ab2fb..ec04ec2 100644 --- a/meshterm/ui/chat.py +++ b/meshterm/ui/chat.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The live chat screen and its launcher. This is the interactive, full-screen chat experience: a scrolling transcript with an input diff --git a/meshterm/ui/config_editor.py b/meshterm/ui/config_editor.py index f772ed9..d3c0c55 100644 --- a/meshterm/ui/config_editor.py +++ b/meshterm/ui/config_editor.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The Device config page: every setting of the companion, and the operations on the box. Behind the ``config`` tool (:func:`edit_config`), and shaped deliberately like the diff --git a/meshterm/ui/contactlist.py b/meshterm/ui/contactlist.py index 5dbf47e..4c68972 100644 --- a/meshterm/ui/contactlist.py +++ b/meshterm/ui/contactlist.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The shared contact list: one sortable, filterable lane layout for every screenful of contacts. Extracted from the Time Machine's subject picker so the app has exactly one way to draw "a diff --git a/meshterm/ui/contacts_screen.py b/meshterm/ui/contacts_screen.py index d231ee2..69376c6 100644 --- a/meshterm/ui/contacts_screen.py +++ b/meshterm/ui/contacts_screen.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The interactive Contacts screen: the device's contacts in the shared sortable contact list. The Time Machine picker's presentation pointed at the companion's contact table: the same diff --git a/meshterm/ui/cooldown.py b/meshterm/ui/cooldown.py index ea45404..4e6ba06 100644 --- a/meshterm/ui/cooldown.py +++ b/meshterm/ui/cooldown.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Waiting out a transmit cooldown, visibly and cancellably. MeshTerm holds itself back between transmissions (see diff --git a/meshterm/ui/courier_screen.py b/meshterm/ui/courier_screen.py index 4608828..946c699 100644 --- a/meshterm/ui/courier_screen.py +++ b/meshterm/ui/courier_screen.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The Courier screens: the outbox, queueing flow, and per-message actions. The interactive face of the ``courier`` tool. One select-list screen carries the whole diff --git a/meshterm/ui/dashboard_screen.py b/meshterm/ui/dashboard_screen.py index c8df5e8..db318bf 100644 --- a/meshterm/ui/dashboard_screen.py +++ b/meshterm/ui/dashboard_screen.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The live mesh dashboard: everything going on around this node, on one screen. The interactive face of the ``dashboard`` tool. One full-screen, always-repainting view diff --git a/meshterm/ui/device_info_screen.py b/meshterm/ui/device_info_screen.py index 771c23a..752d06f 100644 --- a/meshterm/ui/device_info_screen.py +++ b/meshterm/ui/device_info_screen.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The Device info page: everything the companion reports, its pairing PIN concealed. The page itself is the ``info`` tool's — a live status panel over the full configuration diff --git a/meshterm/ui/device_picker.py b/meshterm/ui/device_picker.py index 863fc64..7e8fa27 100644 --- a/meshterm/ui/device_picker.py +++ b/meshterm/ui/device_picker.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Interactive companion-device picker: the startup splash shown before the menu. Shown once at the start of the interactive menu when no port was given explicitly. Unlike the diff --git a/meshterm/ui/fields.py b/meshterm/ui/fields.py index 831d628..3c8504c 100644 --- a/meshterm/ui/fields.py +++ b/meshterm/ui/fields.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The shared shapes a report is built out of — one constructor per concept. A node means the same thing in ``contacts``, ``monitor``, ``courier list``, ``trace`` and diff --git a/meshterm/ui/fontset.py b/meshterm/ui/fontset.py index 603966d..81f6088 100644 --- a/meshterm/ui/fontset.py +++ b/meshterm/ui/fontset.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The PicoCalc console font's codepoint inventory — the glyph contract. The PicoCalc panel draws through the kernel console with one 512-glyph PSF font diff --git a/meshterm/ui/livefeed_screen.py b/meshterm/ui/livefeed_screen.py index 1591b94..98a1e82 100644 --- a/meshterm/ui/livefeed_screen.py +++ b/meshterm/ui/livefeed_screen.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The Live feed: every packet as it arrives, full-screen and newest first. The interactive face of the ``livefeed`` tool — the dashboard's old feed panel, diff --git a/meshterm/ui/logo.py b/meshterm/ui/logo.py index 5148af9..65401f3 100644 --- a/meshterm/ui/logo.py +++ b/meshterm/ui/logo.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The MeshTerm wordmark for the startup splash, loaded from the assets folder. The art itself lives in ``meshterm/assets/splash`` as pre-coloured ``.ans`` files — the diff --git a/meshterm/ui/map_render.py b/meshterm/ui/map_render.py index 7c3efb3..b997113 100644 --- a/meshterm/ui/map_render.py +++ b/meshterm/ui/map_render.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Compose a rendered map frame: basemap geometry, labels, and mesh nodes. Given a :class:`~meshterm.core.geo.Viewport` and the decoded vector tiles covering it, this diff --git a/meshterm/ui/map_screen.py b/meshterm/ui/map_screen.py index a7d76dc..12ecbac 100644 --- a/meshterm/ui/map_screen.py +++ b/meshterm/ui/map_screen.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The interactive full-screen map: a pannable, zoomable slippy map in the terminal. This drives the braille street map inside the TUI. It owns a :class:`~meshterm.core.geo. diff --git a/meshterm/ui/mapcanvas.py b/meshterm/ui/mapcanvas.py index 4daa6e8..0fdca13 100644 --- a/meshterm/ui/mapcanvas.py +++ b/meshterm/ui/mapcanvas.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """A colored Unicode-braille canvas for the terminal map. Each character cell holds a 2×4 grid of braille dots, so the drawable resolution is twice diff --git a/meshterm/ui/markdown.py b/meshterm/ui/markdown.py index c4d91bb..4edb6f9 100644 --- a/meshterm/ui/markdown.py +++ b/meshterm/ui/markdown.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Markdown drawn in MeshTerm's own hand — the renderer behind the About pages. A page of prose is the one kind of screen the app can't build out of rows, lanes and diff --git a/meshterm/ui/marks.py b/meshterm/ui/marks.py index 51ab3f4..2b96c8a 100644 --- a/meshterm/ui/marks.py +++ b/meshterm/ui/marks.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The shared mark constants: node-type glyphs, their colours, and path sentinels. The app's spatial surfaces — the map, the path graph, the braille rasters, the node diff --git a/meshterm/ui/menu.py b/meshterm/ui/menu.py index 1c57580..a0574df 100644 --- a/meshterm/ui/menu.py +++ b/meshterm/ui/menu.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The interactive main menu, built dynamically from the tool registry. The menu groups registered tools by category and runs the selected tool's interactive flow diff --git a/meshterm/ui/menus.py b/meshterm/ui/menus.py index bd549b3..78fb64d 100644 --- a/meshterm/ui/menus.py +++ b/meshterm/ui/menus.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Shared select-menu chrome: the pieces every list screen builds the same way. MeshTerm's list screens (the config editors, the channel manager, Watchtower, Courier, diff --git a/meshterm/ui/message_paths_screen.py b/meshterm/ui/message_paths_screen.py index 2944a22..c2517e2 100644 --- a/meshterm/ui/message_paths_screen.py +++ b/meshterm/ui/message_paths_screen.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The Message paths dialog: every way one chat message reached this radio. The interactive face of :mod:`~meshterm.services.message_paths`, opened from the chat diff --git a/meshterm/ui/minimap.py b/meshterm/ui/minimap.py index 7dfb61d..a4a079f 100644 --- a/meshterm/ui/minimap.py +++ b/meshterm/ui/minimap.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """A small, static, non-interactive map region embeddable inside any screen. The full-screen :class:`~meshterm.ui.map_screen.MapScreen` is a whole layer of its own — diff --git a/meshterm/ui/node_detail_screen.py b/meshterm/ui/node_detail_screen.py index ac830ac..587fe94 100644 --- a/meshterm/ui/node_detail_screen.py +++ b/meshterm/ui/node_detail_screen.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The Node detail screen: one node's whole story on a single page, and the ways into it. Reached by pressing Enter on any contact in the Contacts list (see diff --git a/meshterm/ui/packet_viewer.py b/meshterm/ui/packet_viewer.py index 803b4b7..7355703 100644 --- a/meshterm/ui/packet_viewer.py +++ b/meshterm/ui/packet_viewer.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The packet viewer: one floating popup that can open any packet a list shows. Everywhere MeshTerm lists packets (the dashboard feed today; any future packet list) diff --git a/meshterm/ui/path_composer.py b/meshterm/ui/path_composer.py index 70aa8d9..d9abd5a 100644 --- a/meshterm/ui/path_composer.py +++ b/meshterm/ui/path_composer.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The trace path composer: build a forced route hop by hop, guided by observed links. A floating dialog over a live trace screen. The route under construction reads across diff --git a/meshterm/ui/pathgraph.py b/meshterm/ui/pathgraph.py index 64df2b1..1364479 100644 --- a/meshterm/ui/pathgraph.py +++ b/meshterm/ui/pathgraph.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """THE route-graph widget: hop sequences drawn as a left-to-right flow of paths. Extracted from the Message paths dialog so every screen that draws walked (or planned) diff --git a/meshterm/ui/pathline.py b/meshterm/ui/pathline.py index afc36d1..70dd771 100644 --- a/meshterm/ui/pathline.py +++ b/meshterm/ui/pathline.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Path lines — the flexible hop-sequence text widget (powerline chips or arrows). This is the successor to :func:`~meshterm.ui.widgets.path_text` as THE way a hop diff --git a/meshterm/ui/preferences.py b/meshterm/ui/preferences.py index cded8c8..a0a9caf 100644 --- a/meshterm/ui/preferences.py +++ b/meshterm/ui/preferences.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The Preferences page: MeshTerm's own behaviour, staged and saved. The app-side twin of the device-configuration editor (:mod:`meshterm.ui.config_editor`), diff --git a/meshterm/ui/purge_screen.py b/meshterm/ui/purge_screen.py index 0df26c6..eb49236 100644 --- a/meshterm/ui/purge_screen.py +++ b/meshterm/ui/purge_screen.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The Contacts sweep: rank the whole contact table, archive the weakest off the device. A companion's contact table is finite and a busy mesh fills it with whatever adverts diff --git a/meshterm/ui/qr.py b/meshterm/ui/qr.py index 961407c..ea068ff 100644 --- a/meshterm/ui/qr.py +++ b/meshterm/ui/qr.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Render a string as a scannable QR code in the terminal, using half-block characters. Each character cell stacks two vertical modules — ``▀`` (upper only), ``▄`` (lower only), diff --git a/meshterm/ui/records_screen.py b/meshterm/ui/records_screen.py index cc81234..f3bc5e9 100644 --- a/meshterm/ui/records_screen.py +++ b/meshterm/ui/records_screen.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The Trophy case: the record-setting walks the trace tools have turned up. A read-only board over the ``discovered_paths`` table (:meth:`Repository.discoveries`), diff --git a/meshterm/ui/remote_cli.py b/meshterm/ui/remote_cli.py index 47fe9da..b1304f6 100644 --- a/meshterm/ui/remote_cli.py +++ b/meshterm/ui/remote_cli.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The remote command line: talk to a repeater's CLI over the mesh, readline-style. A full-screen terminal onto a logged-in remote node (part of the repeater-admin diff --git a/meshterm/ui/renderers.py b/meshterm/ui/renderers.py index 06d7a40..ab3cc1a 100644 --- a/meshterm/ui/renderers.py +++ b/meshterm/ui/renderers.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The two faces of one answer: a renderer turns a report into bytes. A tool states its answer as a :mod:`~meshterm.ui.report`; this module is where that diff --git a/meshterm/ui/repeater_admin.py b/meshterm/ui/repeater_admin.py index 4fa9ff7..678aa33 100644 --- a/meshterm/ui/repeater_admin.py +++ b/meshterm/ui/repeater_admin.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The repeater-admin screens: configure a remote node over the mesh, editor-style. The interactive face of the ``repeater-admin`` tool. Picking a node (the shared diff --git a/meshterm/ui/report.py b/meshterm/ui/report.py index e06e639..eb0d99c 100644 --- a/meshterm/ui/report.py +++ b/meshterm/ui/report.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """What a tool answers with, stated as data before anything decides how it looks. A scripted run used to say its answer by *printing* it: ``Tool.run`` called diff --git a/meshterm/ui/script.py b/meshterm/ui/script.py index 6501978..80a558f 100644 --- a/meshterm/ui/script.py +++ b/meshterm/ui/script.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The plain CLI's output language — text for a person at a prompt, aligned and unadorned. MeshTerm has two front ends and a third face. The menu is read by a person sitting in diff --git a/meshterm/ui/settings_offer.py b/meshterm/ui/settings_offer.py index 5704edc..7954620 100644 --- a/meshterm/ui/settings_offer.py +++ b/meshterm/ui/settings_offer.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The connect-time offer to reconcile a device's settings with what MeshTerm remembers. A firmware-less radio bridge forgets its configuration whenever it restarts, so the settings you diff --git a/meshterm/ui/specimen.py b/meshterm/ui/specimen.py index 37ea70b..c6a8a57 100644 --- a/meshterm/ui/specimen.py +++ b/meshterm/ui/specimen.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The visual-language specimen: every mark, icon, colour and fold on one screen. ``meshterm specimen`` prints this through the platform's real machinery — the active diff --git a/meshterm/ui/surface.py b/meshterm/ui/surface.py index 4002444..0d6147b 100644 --- a/meshterm/ui/surface.py +++ b/meshterm/ui/surface.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The UI surface: one small API tools use for input and output, in either front-end. Tools never touch ``questionary`` or a raw console anymore — they talk to ``ctx.ui``. Two diff --git a/meshterm/ui/termfont.py b/meshterm/ui/termfont.py index 9e55ee6..7830554 100644 --- a/meshterm/ui/termfont.py +++ b/meshterm/ui/termfont.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Terminal capability detection — what can this terminal actually draw? Two questions, both answered out-of-band because a terminal cannot be asked in-band: a diff --git a/meshterm/ui/theme.py b/meshterm/ui/theme.py index fce757e..0257915 100644 --- a/meshterm/ui/theme.py +++ b/meshterm/ui/theme.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Shared Rich theme and console factory for a consistent, modern look. Two palettes, one vocabulary: every style *name* here exists in both diff --git a/meshterm/ui/timemachine_screen.py b/meshterm/ui/timemachine_screen.py index 49d1eab..7b1976a 100644 --- a/meshterm/ui/timemachine_screen.py +++ b/meshterm/ui/timemachine_screen.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The Time Machine: the mesh's recorded history, explorable node by node. The interactive face of the ``timemachine`` tool. The database has been recording every diff --git a/meshterm/ui/trace_screen.py b/meshterm/ui/trace_screen.py index 1dc3dda..f4ade79 100644 --- a/meshterm/ui/trace_screen.py +++ b/meshterm/ui/trace_screen.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The live trace screens: compose a route from observed topology, then watch it answer. This is the interactive face of the two trace tools (the scripted CLI keeps its one-shot diff --git a/meshterm/ui/tui/__init__.py b/meshterm/ui/tui/__init__.py index 2a1436d..fdd14a9 100644 --- a/meshterm/ui/tui/__init__.py +++ b/meshterm/ui/tui/__init__.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """A small, centralized, reusable full-screen text-UI library. Built on prompt_toolkit (for the terminal, input, and resize handling) and Rich (for all diff --git a/meshterm/ui/tui/emoji_width.py b/meshterm/ui/tui/emoji_width.py index 26591f6..5016fd4 100644 --- a/meshterm/ui/tui/emoji_width.py +++ b/meshterm/ui/tui/emoji_width.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Align Rich's emoji cell-width table with the terminal's *actual* rendering. Emoji column width is not standardized. Whether a variation-selector-16 sequence diff --git a/meshterm/ui/tui/fastrender.py b/meshterm/ui/tui/fastrender.py index fc3cd06..7241d6b 100644 --- a/meshterm/ui/tui/fastrender.py +++ b/meshterm/ui/tui/fastrender.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Write an already-composed frame straight to the terminal, one changed row at a time. MeshTerm composes every frame itself: :func:`~meshterm.ui.tui.frame.compose_base` returns diff --git a/meshterm/ui/tui/fkeys.py b/meshterm/ui/tui/fkeys.py index a7dedb6..9c93f2c 100644 --- a/meshterm/ui/tui/fkeys.py +++ b/meshterm/ui/tui/fkeys.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The PicoCalc F-key lane: a fixed footer row of five coloured chips, F1-F5. The PicoCalc keyboard has five dedicated function keys; its MCU translates Shift+F1..F5 diff --git a/meshterm/ui/tui/frame.py b/meshterm/ui/tui/frame.py index 480332b..b1417fe 100644 --- a/meshterm/ui/tui/frame.py +++ b/meshterm/ui/tui/frame.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Composition helpers: turn a screen + terminal size into a bounded, framed ANSI view. The session owns input and the screen stack; this module owns *layout* — slicing a screen's diff --git a/meshterm/ui/tui/overlay.py b/meshterm/ui/tui/overlay.py index f86286c..c27c4cd 100644 --- a/meshterm/ui/tui/overlay.py +++ b/meshterm/ui/tui/overlay.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The busy skeleton: a titled card floated between screens while a load runs. A :class:`BusyOverlay` is the model behind :meth:`~meshterm.ui.tui.session.TuiSession. diff --git a/meshterm/ui/tui/progress.py b/meshterm/ui/tui/progress.py index 1d559fd..7128fc1 100644 --- a/meshterm/ui/tui/progress.py +++ b/meshterm/ui/tui/progress.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """A progress screen whose handle mimics the slice of ``rich.progress`` the tools use. Trace and TX-optimize sweeps drive progress with ``add_task`` / ``advance`` / ``update`` on a diff --git a/meshterm/ui/tui/prompt.py b/meshterm/ui/tui/prompt.py index a5cd9b6..1f22935 100644 --- a/meshterm/ui/tui/prompt.py +++ b/meshterm/ui/tui/prompt.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Input screens: text, confirm, and autocomplete — the ``questionary`` replacements. These reimplement the small slice of ``questionary`` the app uses (a validated line editor, diff --git a/meshterm/ui/tui/render.py b/meshterm/ui/tui/render.py index d91ce8c..9e5cd48 100644 --- a/meshterm/ui/tui/render.py +++ b/meshterm/ui/tui/render.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The single Rich-to-prompt_toolkit bridge. The TUI keeps rendering in Rich (all existing tables, panels, and text widgets are reused diff --git a/meshterm/ui/tui/screen.py b/meshterm/ui/tui/screen.py index 2c02bcc..436023f 100644 --- a/meshterm/ui/tui/screen.py +++ b/meshterm/ui/tui/screen.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The screen abstraction: one layer in the TUI's window stack. A :class:`Screen` knows how to render its body (as Rich, converted to ANSI lines by diff --git a/meshterm/ui/tui/select.py b/meshterm/ui/tui/select.py index 92c2857..b49fded 100644 --- a/meshterm/ui/tui/select.py +++ b/meshterm/ui/tui/select.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The selectable-list screen: the reusable replacement for ``questionary.select``. A :class:`SelectScreen` shows grouped, arrow-navigable choices with type-to-filter, bounded diff --git a/meshterm/ui/tui/session.py b/meshterm/ui/tui/session.py index be6febb..1e1f644 100644 --- a/meshterm/ui/tui/session.py +++ b/meshterm/ui/tui/session.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The persistent full-screen session: the heart of the TUI library. A :class:`TuiSession` owns one prompt_toolkit :class:`Application`, a stack of diff --git a/meshterm/ui/tui/spinner.py b/meshterm/ui/tui/spinner.py index 600dd62..759083b 100644 --- a/meshterm/ui/tui/spinner.py +++ b/meshterm/ui/tui/spinner.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """A reusable text spinner: one animated glyph for a "working" indicator. A small, screen-agnostic animation helper. It holds a cycle of frame glyphs and a current diff --git a/meshterm/ui/tx_screen.py b/meshterm/ui/tx_screen.py index 1e58709..b66555e 100644 --- a/meshterm/ui/tx_screen.py +++ b/meshterm/ui/tx_screen.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The live TX-optimization screen: pick the link, arm the sweep, watch levels land. The interactive face of the ``tx-optimize`` tool (the scripted CLI keeps its one-shot diff --git a/meshterm/ui/walk_screen.py b/meshterm/ui/walk_screen.py index f6ac12c..fbc28e2 100644 --- a/meshterm/ui/walk_screen.py +++ b/meshterm/ui/walk_screen.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The Mesh walk: walk the mesh's observed shape one node at a time. The interactive face of the ``walk`` tool. The trace path composer already distills diff --git a/meshterm/ui/watchtower_screen.py b/meshterm/ui/watchtower_screen.py index 43fa13d..d7c5c70 100644 --- a/meshterm/ui/watchtower_screen.py +++ b/meshterm/ui/watchtower_screen.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The Watchtower screens: the alert log, the watchlist, and per-node rules. The interactive face of the ``watchtower`` tool. One select-list screen carries the diff --git a/meshterm/ui/widgets.py b/meshterm/ui/widgets.py index 3742398..b8c012a 100644 --- a/meshterm/ui/widgets.py +++ b/meshterm/ui/widgets.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Reusable Rich widgets: banner, status pill, trace tables, and progress bars.""" from __future__ import annotations diff --git a/packaging/entry.py b/packaging/entry.py index efc9f5e..97f83ca 100644 --- a/packaging/entry.py +++ b/packaging/entry.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Launcher for the frozen builds. PyInstaller freezes a *script*, not a console-script entry point, so this is the script: diff --git a/packaging/meshterm.spec b/packaging/meshterm.spec index 420acd6..79770cf 100644 --- a/packaging/meshterm.spec +++ b/packaging/meshterm.spec @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 # PyInstaller spec for the downloadable builds. Run from the repository root: # # pyinstaller packaging/meshterm.spec diff --git a/scripts/meshterm-spi-bridge b/scripts/meshterm-spi-bridge index 4dbb142..9545a61 100755 --- a/scripts/meshterm-spi-bridge +++ b/scripts/meshterm-spi-bridge @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 """ meshterm-spi-bridge — expose an SPI-attached MeshCore radio to MeshTerm. diff --git a/scripts/xiao-radio/flash.py b/scripts/xiao-radio/flash.py index 79e4c49..3fb3a73 100644 --- a/scripts/xiao-radio/flash.py +++ b/scripts/xiao-radio/flash.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 """Flash the built .uf2 onto the XIAO nRF52840 via its UF2 bootloader. Runs on your DEV MACHINE with the XIAO's own USB-C plugged into it. Works on diff --git a/scripts/xiao-radio/uart1-mux.py b/scripts/xiao-radio/uart1-mux.py index 7c06b5e..d8ef1ec 100644 --- a/scripts/xiao-radio/uart1-mux.py +++ b/scripts/xiao-radio/uart1-mux.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 """Route the RK3506 UART1 controller to the PicoCalc header pads GP4/GP5. Calculinux enables the UART1 controller (so ``/dev/ttyS1`` exists) but never wires it to any diff --git a/tests/__init__.py b/tests/__init__.py index 04421e5..1f01df2 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1 +1,2 @@ +# SPDX-License-Identifier: Apache-2.0 """The MeshTerm test suite.""" diff --git a/tests/conftest.py b/tests/conftest.py index 2f5c6c6..7c48bc3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Shared fixtures and helpers: plain-text screen reading, and pinned path rendering. :func:`plain` is THE way a test reads a rendered screen — every screen test needs the diff --git a/tests/test_about.py b/tests/test_about.py index bf0eb8d..8886b53 100644 --- a/tests/test_about.py +++ b/tests/test_about.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """About MeshTerm tests: the four pages, their placeholders, and their menu section. The pages carry no state and no controls, so what is worth pinning is what they *say* diff --git a/tests/test_admin_login.py b/tests/test_admin_login.py index 3551131..2661488 100644 --- a/tests/test_admin_login.py +++ b/tests/test_admin_login.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """A repeater that says nothing has not said the password is wrong. THE bug, reported after administering a repeater that happened to be down: MeshTerm forgot diff --git a/tests/test_advert.py b/tests/test_advert.py index 03df465..2ac6afe 100644 --- a/tests/test_advert.py +++ b/tests/test_advert.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for the background advert. The store's schedule arithmetic, the scheduler's one-send-per-pass discipline, and diff --git a/tests/test_battery.py b/tests/test_battery.py index 41b4a8a..d7092bd 100644 --- a/tests/test_battery.py +++ b/tests/test_battery.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Battery gauge tests: the LiPo curve, the one-cell braille glyph, and the poller. The widget is pure (percent + flags → a coloured glyph) and the poller runs against a diff --git a/tests/test_ble_pairing.py b/tests/test_ble_pairing.py index 492e35a..6ee58d4 100644 --- a/tests/test_ble_pairing.py +++ b/tests/test_ble_pairing.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for the BLE connect path's ownership of its meshcore client. The bug these pin down: ``MeshCore.create_ble`` builds a client, calls ``connect()`` on it, diff --git a/tests/test_braillechart.py b/tests/test_braillechart.py index e2c0564..296e39a 100644 --- a/tests/test_braillechart.py +++ b/tests/test_braillechart.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Braille chart widget tests: orientation, the zero baseline, scaling, and styles. The module is pure Text-in/Text-out, so every behaviour is asserted straight off diff --git a/tests/test_channel_store.py b/tests/test_channel_store.py index c6c0451..9454935 100644 --- a/tests/test_channel_store.py +++ b/tests/test_channel_store.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for remembering channels across sessions (``core.channel_store``). Covers the durable store itself (per-device, memory-first JSON), the ``reconcile`` replay that diff --git a/tests/test_channels.py b/tests/test_channels.py index ac9f7b7..d695671 100644 --- a/tests/test_channels.py +++ b/tests/test_channels.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for the channel-management feature: pure logic, QR rendering, and the tool. The logic (secret derivation, share-URL round-tripping) is hardware-free; the tool actions diff --git a/tests/test_chat.py b/tests/test_chat.py index 0d75bf8..284f9cb 100644 --- a/tests/test_chat.py +++ b/tests/test_chat.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for the chat feature: device send, persistence, and the chat service. All run against the :class:`MockDevice` simulator and a temporary database; no hardware. diff --git a/tests/test_cli_contract.py b/tests/test_cli_contract.py index 68df5f7..6373878 100644 --- a/tests/test_cli_contract.py +++ b/tests/test_cli_contract.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """End-to-end tests for what the CLI actually prints and returns. :mod:`tests.test_script_output` covers the vocabulary and :mod:`tests.test_report` the diff --git a/tests/test_config.py b/tests/test_config.py index f2b3755..3afd127 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for machine-setup loading (where things live, and which device to talk to). MeshTerm's *behaviour* settings are preferences and live in ``tests/test_preferences.py``; diff --git a/tests/test_config_editor.py b/tests/test_config_editor.py index c2e6b67..5b9df5c 100644 --- a/tests/test_config_editor.py +++ b/tests/test_config_editor.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for the overhauled config editor: dialogs, staging, and the location picker. The pure pieces (the contact share URL, coordinate parsing, the typed-confirmation diff --git a/tests/test_connection_channels.py b/tests/test_connection_channels.py index 4abbe42..262cb11 100644 --- a/tests/test_connection_channels.py +++ b/tests/test_connection_channels.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Regression tests for serialized, index-validated channel reads. The meshcore library's ``get_channel`` waits for "the next CHANNEL_INFO event" with no diff --git a/tests/test_consolefont.py b/tests/test_consolefont.py index 0b33cac..381e8f9 100644 --- a/tests/test_consolefont.py +++ b/tests/test_consolefont.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for the bundled console font and the machinery that installs and selects it. The Win32 half cannot be exercised off Windows, and should not be exercised *on* it by a diff --git a/tests/test_contact_score.py b/tests/test_contact_score.py index 68925f4..b98fdf5 100644 --- a/tests/test_contact_score.py +++ b/tests/test_contact_score.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for rating contacts by how much they are worth a device slot (``core.contact_score``). The scoring is pure — no device, no database, no screen — so these exercise the rules the diff --git a/tests/test_contact_store.py b/tests/test_contact_store.py index d57bcef..e5b7a7e 100644 --- a/tests/test_contact_store.py +++ b/tests/test_contact_store.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for remembering contacts across sessions (``core.contact_store``). Covers the durable per-device store, the ``RememberedContact`` <-> ``Contact`` round-trip, the diff --git a/tests/test_cooldown.py b/tests/test_cooldown.py index d4ead49..a7853a2 100644 --- a/tests/test_cooldown.py +++ b/tests/test_cooldown.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for the transmit cooldown: the shared clock, the countdown, and the way out. Three layers again — what the clock owes (:mod:`meshterm.core.transmit_gate`), what the diff --git a/tests/test_courier.py b/tests/test_courier.py index bd11051..a912832 100644 --- a/tests/test_courier.py +++ b/tests/test_courier.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Courier tests: the outbox store, delivery eligibility, and the attempt flow. The service is driven synchronously (eligibility) and through stubbed chat sends diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py index 7769124..948e96d 100644 --- a/tests/test_dashboard.py +++ b/tests/test_dashboard.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Dashboard tests: the live screen's state, its chart plumbing, and its data feeds. The screen is driven headless against fake sessions and canned observations, the same diff --git a/tests/test_device_config.py b/tests/test_device_config.py index e8ed57c..ddb785d 100644 --- a/tests/test_device_config.py +++ b/tests/test_device_config.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for the device-configuration registry, backup/restore, and safety gating. All run against :class:`MockDevice` — no hardware required. diff --git a/tests/test_device_state.py b/tests/test_device_state.py index 6e4a9df..73d7913 100644 --- a/tests/test_device_state.py +++ b/tests/test_device_state.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """DeviceState session-cache tests: read once, serve cached, invalidate on write. The cache exists to keep screen navigation off the slow radio round-trips (contacts, diff --git a/tests/test_disconnect.py b/tests/test_disconnect.py index ffc0557..a884d74 100644 --- a/tests/test_disconnect.py +++ b/tests/test_disconnect.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for graceful handling of a lost device connection. Covers the exception classifier that distinguishes a dropped serial link from an ordinary diff --git a/tests/test_discovery.py b/tests/test_discovery.py index 89ee1ce..2994e14 100644 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for device discovery, the remembered-device store, and selection. All run without hardware: serial enumeration is monkeypatched, and the store/selection diff --git a/tests/test_emoji_width.py b/tests/test_emoji_width.py index 123ceed..2dc701c 100644 --- a/tests/test_emoji_width.py +++ b/tests/test_emoji_width.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Terminal-aligned emoji width: VS16 sequences and curated lone-codepoint emoji. The border-alignment story lives in :mod:`meshterm.ui.tui.emoji_width`. These tests pin the diff --git a/tests/test_event_hub.py b/tests/test_event_hub.py index 6d63359..b8234d0 100644 --- a/tests/test_event_hub.py +++ b/tests/test_event_hub.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for the always-on event hub: fan-out, filtering, streaming, and lifecycle. All run against the :class:`MockDevice` simulator; no hardware required. diff --git a/tests/test_frames.py b/tests/test_frames.py index b128fd5..0cead29 100644 --- a/tests/test_frames.py +++ b/tests/test_frames.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Frame-body decoding: what an overheard packet is addressed to. The companion's RX log hands us every frame's header parsed and its body raw; these diff --git a/tests/test_gallery.py b/tests/test_gallery.py index f9bacf0..c9d1f10 100644 --- a/tests/test_gallery.py +++ b/tests/test_gallery.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The dual-platform gallery. Every screen the interactive menu can open, checked for width discipline under both diff --git a/tests/test_header.py b/tests/test_header.py index fa2952d..42a3dc1 100644 --- a/tests/test_header.py +++ b/tests/test_header.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Header tests: the one-line status bar's width-filling activity sparkline.""" from __future__ import annotations diff --git a/tests/test_icon_alignment.py b/tests/test_icon_alignment.py index 2854cf0..8bce79c 100644 --- a/tests/test_icon_alignment.py +++ b/tests/test_icon_alignment.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The icon-column guard: in any one list, every icon-led row starts its words in one cell. The terminal draws some of the app's icons in one cell (``🗑 ✎ ⚙ ▶ ★ ↻ ↕ ⇄ ⌨ # ✓ ✗ ⚠``) diff --git a/tests/test_instancelock.py b/tests/test_instancelock.py index 068f840..e96af9f 100644 --- a/tests/test_instancelock.py +++ b/tests/test_instancelock.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for the one-interactive-session-per-directory guard, and the atomic writer. Both exist for the same reason: every copy of MeshTerm on a machine shares one data diff --git a/tests/test_live_screens.py b/tests/test_live_screens.py index e429207..4bab5a9 100644 --- a/tests/test_live_screens.py +++ b/tests/test_live_screens.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Unit tests for the live Trace and TX-optimize screens. These drive the two full-screen tools' pure logic — trace/sweep state machines, key diff --git a/tests/test_livefeed.py b/tests/test_livefeed.py index 53e4951..da6d7f8 100644 --- a/tests/test_livefeed.py +++ b/tests/test_livefeed.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Live feed tests: the promoted feed screen's state, rows, and windowing. The screen is driven headless against fake sessions and canned observations — the diff --git a/tests/test_logging.py b/tests/test_logging.py index d26618b..0f22a11 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for the log file: plain text, bounded, and holding the things that went wrong. The log's one real job is being opened by a person who has hit a problem — often the diff --git a/tests/test_map.py b/tests/test_map.py index 8bceb07..a009592 100644 --- a/tests/test_map.py +++ b/tests/test_map.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for the node map: MVT decoding, projection, the braille canvas, and the tool. The vector-tile decoder and geometry are exercised against a real cached tile fixture diff --git a/tests/test_markdown.py b/tests/test_markdown.py index 37b9366..3ee3240 100644 --- a/tests/test_markdown.py +++ b/tests/test_markdown.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The markdown renderer: what each construct becomes in MeshTerm's visual language. The About pages are written rather than composed (see :mod:`meshterm.ui.markdown`), so diff --git a/tests/test_menu_quit_row.py b/tests/test_menu_quit_row.py index 160adb0..6b17ed5 100644 --- a/tests/test_menu_quit_row.py +++ b/tests/test_menu_quit_row.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The main menu's Quit row shares the tool rows' icon column (JP, 2026-09-11). The menu closes on a ``🚪 Quit`` row below its tool rows, and for as long as it has existed diff --git a/tests/test_menus.py b/tests/test_menus.py index 4528192..8a204b9 100644 --- a/tests/test_menus.py +++ b/tests/test_menus.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Shared menu-chrome tests: the furniture every select list builds the same way. These pin the app's exit-affordance and lane conventions at their source, so a screen diff --git a/tests/test_message_paths.py b/tests/test_message_paths.py index e22a2af..941667c 100644 --- a/tests/test_message_paths.py +++ b/tests/test_message_paths.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Message-paths tests: matching a chat message back to its logged arrivals. Channel arrivals are matched by decrypting overheard GRP_TXT frames with the channel's diff --git a/tests/test_message_paths_screen.py b/tests/test_message_paths_screen.py index a2500e0..30ead1e 100644 --- a/tests/test_message_paths_screen.py +++ b/tests/test_message_paths_screen.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Message-paths dialog tests: the graph-over-rows view behind the chat's ^P. Driven headless like the mesh walk tests: render_body is pure lines-out, handle() pure diff --git a/tests/test_modifier_watch.py b/tests/test_modifier_watch.py index 9fc44c5..36f5732 100644 --- a/tests/test_modifier_watch.py +++ b/tests/test_modifier_watch.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The F-key lane's Shift-state watcher: the raw flag and the shift-bank latch.""" from __future__ import annotations diff --git a/tests/test_monitor.py b/tests/test_monitor.py index 1909378..accb947 100644 --- a/tests/test_monitor.py +++ b/tests/test_monitor.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for the passive mesh monitor: listening, aggregation, and persistence. All run against the :class:`MockDevice` simulator and an on-disk SQLite database, no diff --git a/tests/test_navigation.py b/tests/test_navigation.py index 81abf6a..ab84b10 100644 --- a/tests/test_navigation.py +++ b/tests/test_navigation.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for the navigation stack: strict push/pop, kept state, and the two global chords. The app's realized path is a stack — the main menu at the bottom, one frame per screen or diff --git a/tests/test_node_detail.py b/tests/test_node_detail.py index 69f5175..66a9475 100644 --- a/tests/test_node_detail.py +++ b/tests/test_node_detail.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Node detail screen tests: the mini-map, the tabbed page, and its route/topology helpers. The screen is a pure render-and-route view, so these drive it headless — display data is diff --git a/tests/test_packet_viewer.py b/tests/test_packet_viewer.py index 054e2b9..dd53471 100644 --- a/tests/test_packet_viewer.py +++ b/tests/test_packet_viewer.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Packet viewer tests: per-kind flavouring, the raw-field dedup, and channel decrypt. The viewer is pure Rich-in, ANSI-lines-out (``render_body``), so it's driven headless diff --git a/tests/test_paired_settings.py b/tests/test_paired_settings.py index 4df200c..c82618f 100644 --- a/tests/test_paired_settings.py +++ b/tests/test_paired_settings.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for settings that share one device command, and for the PIN's readback. Both were found restoring a reflashed companion from its own backup. diff --git a/tests/test_pathgraph.py b/tests/test_pathgraph.py index bf14444..aaf001b 100644 --- a/tests/test_pathgraph.py +++ b/tests/test_pathgraph.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Unit tests for the shared route-graph widget (the diverge/converge braille renderer).""" from __future__ import annotations diff --git a/tests/test_pathline.py b/tests/test_pathline.py index 65b9038..161a48a 100644 --- a/tests/test_pathline.py +++ b/tests/test_pathline.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Path-line widget tests: same words in both modes, three overflow shapes. The widget's contract is exactness — plain mode must reproduce the app-wide arrow diff --git a/tests/test_performance.py b/tests/test_performance.py index 2535bcb..109c319 100644 --- a/tests/test_performance.py +++ b/tests/test_performance.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The P2 performance contracts: what must stay cheap, and what must stay off the boot path. These are behaviour tests, not benchmarks — a wall-clock assertion would be flaky on CI and diff --git a/tests/test_picocalc_chrome.py b/tests/test_picocalc_chrome.py index 23969db..c45b174 100644 --- a/tests/test_picocalc_chrome.py +++ b/tests/test_picocalc_chrome.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """P4 chrome contracts: borderless frame, slim header, the F-key lane, dialog gate.""" from __future__ import annotations diff --git a/tests/test_platforms.py b/tests/test_platforms.py index 0fc1354..c810cb5 100644 --- a/tests/test_platforms.py +++ b/tests/test_platforms.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for the platform seam. Resolution order, the active-platform singleton, and the two platform specs' own diff --git a/tests/test_preferences.py b/tests/test_preferences.py index 35c7e60..2b5bba8 100644 --- a/tests/test_preferences.py +++ b/tests/test_preferences.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for MeshTerm's own preferences: the registry, the TOML file, and the page. Three layers, in that order — what a preference *is* (spec, default, validation), where it diff --git a/tests/test_records.py b/tests/test_records.py index 3d5e33a..d910f26 100644 --- a/tests/test_records.py +++ b/tests/test_records.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Unit tests for the trophy-case records: walk scoring, derivation, and persistence. The scoring layer is pure measurement (the UI owns the radio), so everything here runs diff --git a/tests/test_records_screen.py b/tests/test_records_screen.py index 3bf690e..37a11f2 100644 --- a/tests/test_records_screen.py +++ b/tests/test_records_screen.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Trophy-case dialog tests: one record's full story, headless. Driven like the other screen tests — ``render_body`` is pure lines-out and ``handle`` diff --git a/tests/test_repeater_admin.py b/tests/test_repeater_admin.py index 28bee60..43fe1fe 100644 --- a/tests/test_repeater_admin.py +++ b/tests/test_repeater_admin.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for repeater admin. The settings catalog (checked against the firmware's own CLI), the per-node store, the diff --git a/tests/test_report.py b/tests/test_report.py index 880163e..0dcbeac 100644 --- a/tests/test_report.py +++ b/tests/test_report.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for the seam between a tool's answer and its rendering. :mod:`meshterm.ui.report` is where a tool states what it found, :mod:`meshterm.ui.fields` diff --git a/tests/test_repository.py b/tests/test_repository.py index 3aaac7d..4e8499b 100644 --- a/tests/test_repository.py +++ b/tests/test_repository.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for the Repository persistence layer.""" from __future__ import annotations diff --git a/tests/test_script_output.py b/tests/test_script_output.py index 4d86237..7f07620 100644 --- a/tests/test_script_output.py +++ b/tests/test_script_output.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for the plain CLI's output language (:mod:`meshterm.ui.script`). The plain face is read by a person at a prompt — ``--json`` carries the machine now — and diff --git a/tests/test_selection_colour.py b/tests/test_selection_colour.py index 4d741b9..4540182 100644 --- a/tests/test_selection_colour.py +++ b/tests/test_selection_colour.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """White is what "you picked this" looks like — and nothing else says it. Three surfaces used to answer the question in the wordmark's teal: the cursor row (fixed diff --git a/tests/test_session_color_depth.py b/tests/test_session_color_depth.py index 298de2c..59b36f8 100644 --- a/tests/test_session_color_depth.py +++ b/tests/test_session_color_depth.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Session colour tests: how many colours the terminal is actually sent. prompt_toolkit decides this per *output class*, and its two classes disagree — diff --git a/tests/test_session_width.py b/tests/test_session_width.py index 2c618a4..408ddc2 100644 --- a/tests/test_session_width.py +++ b/tests/test_session_width.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Session width tests: reclaiming the terminal's final column. Some terminals (and prompt_toolkit's size probe on them) report the window one column diff --git a/tests/test_settings_offer.py b/tests/test_settings_offer.py index 8ac1027..ca27268 100644 --- a/tests/test_settings_offer.py +++ b/tests/test_settings_offer.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The connect-time settings offer, driven end to end over a scripted device. The scenario that motivated the position rule (JP, 2026-08-08/09): a GPS-less companion diff --git a/tests/test_settings_store.py b/tests/test_settings_store.py index f87c4a7..f2243e1 100644 --- a/tests/test_settings_store.py +++ b/tests/test_settings_store.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for remembering device settings across sessions (``core.settings_store``). Covers the durable per-device store, the drift detection that compares remembered values to a diff --git a/tests/test_skeleton.py b/tests/test_skeleton.py index f44f578..dd12a8d 100644 --- a/tests/test_skeleton.py +++ b/tests/test_skeleton.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Smoke tests for the skeleton: models, mock device, service, and persistence. These run without hardware against the :class:`MockDevice` simulator. diff --git a/tests/test_spdx.py b/tests/test_spdx.py new file mode 100644 index 0000000..6e9f862 --- /dev/null +++ b/tests/test_spdx.py @@ -0,0 +1,57 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Enforce SPDX license identifiers on all Python files. + +Every Python source file in the MeshTerm project must declare its license with +a SPDX-License-Identifier comment on line 1 (or line 2 if it starts with a +shebang). This test walks the entire codebase and asserts the identifier is +present. A file lifted out of the repository carries no license marking +otherwise, so this gate ensures no file lands unmarked. +""" + +from pathlib import Path + + +def test_spdx_headers(): + """Every Python file must have an SPDX-License-Identifier header.""" + repo_root = Path(__file__).parent.parent + python_files = [] + + # Collect all Python files from the required directories + for directory in ["meshterm", "tests", "packaging", "scripts/xiao-radio"]: + dir_path = repo_root / directory + if dir_path.exists(): + if directory == "packaging": + # For packaging, include both .py files and .spec files + python_files.extend(dir_path.glob("*.py")) + python_files.extend(dir_path.glob("*.spec")) + else: + python_files.extend(dir_path.rglob("*.py")) + + # Also check the extensionless scripts/meshterm-spi-bridge script + spi_bridge = repo_root / "scripts" / "meshterm-spi-bridge" + if spi_bridge.exists(): + python_files.append(spi_bridge) + + missing_spdx = [] + for filepath in sorted(python_files): + try: + content = filepath.read_text(encoding="utf-8") + lines = content.split("\n", 2) # Check first 2 lines max + + # Check for SPDX identifier on line 1 or 2 + has_spdx = False + if lines and "SPDX-License-Identifier:" in lines[0]: + has_spdx = True + elif len(lines) > 1 and "SPDX-License-Identifier:" in lines[1]: + has_spdx = True + + if not has_spdx: + missing_spdx.append(str(filepath.relative_to(repo_root))) + except Exception: + # Skip files that can't be read + continue + + assert not missing_spdx, ( + f"Found {len(missing_spdx)} Python file(s) without SPDX-License-Identifier:\n" + + "\n".join(f" {f}" for f in missing_spdx) + ) diff --git a/tests/test_surface.py b/tests/test_surface.py index 34e03e0..496d585 100644 --- a/tests/test_surface.py +++ b/tests/test_surface.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for the TUI output surface's window-or-popup presentation. :meth:`TuiUi.present` upgrades a short, text-only result ("✓ flood advertisement sent") diff --git a/tests/test_termfont.py b/tests/test_termfont.py index 7ae979f..f958ee0 100644 --- a/tests/test_termfont.py +++ b/tests/test_termfont.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Terminal-font detection tests: the recommended list, the ladder, the verdict. Every verdict runs against injected environments, temp settings files and injected diff --git a/tests/test_theme16.py b/tests/test_theme16.py index 3d438af..3f92ebc 100644 --- a/tests/test_theme16.py +++ b/tests/test_theme16.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """The PicoCalc visual-language contracts: palette, glyph map, fold, and bindings. P3's deliverables are promises about *output* — only 16 palette slots, only glyphs the diff --git a/tests/test_timemachine.py b/tests/test_timemachine.py index 7345f9a..6384514 100644 --- a/tests/test_timemachine.py +++ b/tests/test_timemachine.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Time Machine tests: the history queries, the bucketing math, and the pages. Pages are built against a real (temporary) repository seeded with canned diff --git a/tests/test_topology.py b/tests/test_topology.py index cde90ff..56e70e5 100644 --- a/tests/test_topology.py +++ b/tests/test_topology.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Unit tests for the observed-topology graph, path scenarios, and the path composer. The graph is pure data-in/data-out, so these run headless: evidence rows are built by diff --git a/tests/test_tui.py b/tests/test_tui.py index f768576..7412be0 100644 --- a/tests/test_tui.py +++ b/tests/test_tui.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Unit tests for the reusable text-UI library (``meshterm.ui.tui``). These exercise the pure logic — ANSI rendering/slicing, selection filtering and navigation, diff --git a/tests/test_vt_console_font.py b/tests/test_vt_console_font.py index 97da682..bf61b0a 100644 --- a/tests/test_vt_console_font.py +++ b/tests/test_vt_console_font.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for the PicoCalc's console-font switch: the gate, the three verbs, and the wiring. Nothing here runs ``setfont`` — the point of the module under test is that it shells out, diff --git a/tests/test_walk.py b/tests/test_walk.py index e6cdcb7..0e0e649 100644 --- a/tests/test_walk.py +++ b/tests/test_walk.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Mesh walk tests: the focus-and-walk browser over a hand-built topology. The screen is driven headless against a fake session, the same approach as the diff --git a/tests/test_watchtower.py b/tests/test_watchtower.py index 5f2b76a..549b75b 100644 --- a/tests/test_watchtower.py +++ b/tests/test_watchtower.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Watchtower tests: the store's persistence and the sentinel's rules. The service is driven synchronously through :meth:`note`/:meth:`evaluate` against a diff --git a/tests/test_widgets.py b/tests/test_widgets.py index a22d26a..1770aab 100644 --- a/tests/test_widgets.py +++ b/tests/test_widgets.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Shared-widget helper tests: the canonical formatting primitives every screen leans on. Most widgets are exercised through their host screens' tests; what lives here are the diff --git a/tests/test_win32dll.py b/tests/test_win32dll.py index 0917d37..c00e4f2 100644 --- a/tests/test_win32dll.py +++ b/tests/test_win32dll.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for the private Windows DLL handles. These pin a crash that only ever appeared in a frozen build on Windows, which is the diff --git a/tests/test_winterminal.py b/tests/test_winterminal.py index 77f2fe6..bcaae64 100644 --- a/tests/test_winterminal.py +++ b/tests/test_winterminal.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 """Tests for reopening the session in Windows Terminal. Nothing here starts a terminal. What is pinned is the command that *would* be run — which