diff --git a/meshterm/tools/advert.py b/meshterm/tools/advert.py index a044697..0385472 100644 --- a/meshterm/tools/advert.py +++ b/meshterm/tools/advert.py @@ -27,6 +27,7 @@ class SendAdvertTool(Tool): name = "advert" title = "Send advert" + icon = "📡" help = "Announce this node to the mesh — zero-hop, flood, or share as QR" category = "Device" order = 10 # right after Device actions, before Device info diff --git a/meshterm/tools/base.py b/meshterm/tools/base.py index 6984932..1d6446f 100644 --- a/meshterm/tools/base.py +++ b/meshterm/tools/base.py @@ -42,6 +42,10 @@ class Tool(ABC): window. Capitalized, and kept identical to the screen the item opens so the menu never promises one name and delivers another. Falls back to ``name``. help: One-line description shown in the menu and ``--help`` (no trailing period). + icon: Emoji drawn before the title in the interactive menu (and only there — the + result window keeps the plain title). Single-codepoint, emoji-presentation + glyphs only, so the two-cell width holds across terminals (see + :mod:`meshterm.ui.tui.emoji_width`). category: Grouping label used to organize the interactive menu. order: Sort key within a category (lower sorts first). menu_visible: Whether the tool appears in the interactive menu. Set ``False`` for @@ -52,6 +56,7 @@ class Tool(ABC): name: str = "" title: str = "" help: str = "" + icon: str = "" category: str = "General" order: int = 100 menu_visible: bool = True diff --git a/meshterm/tools/channels.py b/meshterm/tools/channels.py index ca99990..6fb6ecc 100644 --- a/meshterm/tools/channels.py +++ b/meshterm/tools/channels.py @@ -36,6 +36,7 @@ class ChannelsTool(Tool): name = "channels" title = "Channels" + icon = "📻" help = "Create, join, and share mesh channels (with QR codes)" category = "Messaging" order = 20 diff --git a/meshterm/tools/chat.py b/meshterm/tools/chat.py index 054aaf6..eab2625 100644 --- a/meshterm/tools/chat.py +++ b/meshterm/tools/chat.py @@ -44,6 +44,7 @@ class ChatTool(Tool): name = "chat" title = "Chat" + icon = "💬" help = "Channel and direct messaging — live chat, with stored history" category = "Messaging" order = 10 diff --git a/meshterm/tools/config.py b/meshterm/tools/config.py index c016044..784a93d 100644 --- a/meshterm/tools/config.py +++ b/meshterm/tools/config.py @@ -36,6 +36,7 @@ class ConfigTool(Tool): name = "config" title = "Device configuration" + icon = "🔧" help = "View and change device settings — identity, radio, tuning, …" category = "Device" order = 8 diff --git a/meshterm/tools/device_actions.py b/meshterm/tools/device_actions.py index a07158b..7fa05de 100644 --- a/meshterm/tools/device_actions.py +++ b/meshterm/tools/device_actions.py @@ -30,6 +30,7 @@ class DeviceActionsTool(Tool): name = "device-actions" title = "Device actions" + icon = "🔨" help = "Act on the device itself — advert, backup, identity key, reboot, reset" category = "Device" order = 9 diff --git a/meshterm/tools/info.py b/meshterm/tools/info.py index bda8d31..a72fbcd 100644 --- a/meshterm/tools/info.py +++ b/meshterm/tools/info.py @@ -26,6 +26,7 @@ class InfoTool(Tool): name = "info" title = "Device info" + icon = "📋" help = "Show device status, identity, and radio config" category = "Device" order = 11 # after Send advert (10), which slots in behind Device actions diff --git a/meshterm/tools/map.py b/meshterm/tools/map.py index a90914f..267e7d0 100644 --- a/meshterm/tools/map.py +++ b/meshterm/tools/map.py @@ -35,6 +35,7 @@ class MapTool(Tool): name = "map" title = "Map" + icon = "🌍" help = "Show mesh nodes on a street map (pannable; repeaters highlighted)" category = "Messaging" order = 30 diff --git a/meshterm/tools/monitor.py b/meshterm/tools/monitor.py index aed4fc6..06eb44c 100644 --- a/meshterm/tools/monitor.py +++ b/meshterm/tools/monitor.py @@ -36,6 +36,7 @@ class MonitorTool(Tool): name = "monitor" title = "Monitor" + icon = "🎧" help = "Capture overheard packets live for a while and summarize them" category = "Diagnostics" order = 20 diff --git a/meshterm/tools/nodes.py b/meshterm/tools/nodes.py index 53dfacf..abc3e65 100644 --- a/meshterm/tools/nodes.py +++ b/meshterm/tools/nodes.py @@ -16,6 +16,7 @@ class NodesTool(Tool): name = "nodes" title = "Nodes" + icon = "👥" help = "List this node and known contacts (last heard, packets, type, key)" category = "Device" order = 12 diff --git a/meshterm/tools/trace.py b/meshterm/tools/trace.py index a974d5f..aae6a04 100644 --- a/meshterm/tools/trace.py +++ b/meshterm/tools/trace.py @@ -48,6 +48,7 @@ class TraceTool(Tool): name = "trace" title = "Trace target" + icon = "🎯" help = "Trace a target over a mirrored route and watch per-hop SNR" category = "Diagnostics" order = 10 @@ -222,6 +223,7 @@ class TracePathTool(Tool): name = "trace-path" title = "Trace path" + icon = "👣" help = "Walk a hand-composed route — out and back your way" category = "Diagnostics" order = 11 diff --git a/meshterm/tools/tx_optimize.py b/meshterm/tools/tx_optimize.py index e3321b3..c3fc683 100644 --- a/meshterm/tools/tx_optimize.py +++ b/meshterm/tools/tx_optimize.py @@ -37,6 +37,7 @@ class TxOptimizeTool(Tool): name = "tx-optimize" title = "TX optimize" + icon = "📶" help = "Tune a remote node's TX power for the best signal at a target" category = "Diagnostics" order = 15 # right after Trace, its measurement sibling diff --git a/meshterm/ui/menu.py b/meshterm/ui/menu.py index 5a991ed..b6f39f7 100644 --- a/meshterm/ui/menu.py +++ b/meshterm/ui/menu.py @@ -322,20 +322,25 @@ async def _menu_loop(ctx: AppContext, session: TuiSession) -> None: # Two aligned columns — tool name, then its muted description — with no header # line: these are commands, not tabular data, so the alignment alone carries it. tools = [tool for tool in all_tools() if tool.menu_visible] - name_w = max((cell_len(tool.title or tool.name) for tool in tools), default=0) + + def _label(tool) -> str: # noqa: ANN001 - registry Tool; typed at the source + title = tool.title or tool.name + return f"{tool.icon} {title}" if tool.icon else title + + name_w = max((cell_len(_label(tool)) for tool in tools), default=0) items: list = [] current_category: str | None = None for tool in tools: if tool.category != current_category: current_category = tool.category items.append(Separator(f"── {current_category} ──", style="accent")) - label = tool.title or tool.name + label = _label(tool) row = Text(label) row.append(" " * (name_w - cell_len(label) + 2)) row.append(tool.help, style="muted") items.append(Choice(title=row, value=tool.name)) items.append(Separator(" ")) - items.append(Choice(title="Quit", value="__quit__")) + items.append(Choice(title="🚪 Quit", value="__quit__")) # Drive the menu list ourselves (rather than via session.select) so it stays on the # stack while the quit dialog floats over it: the confirm is drawn as a centered box