The passive monitor has been writing every overheard packet to the database since the first session; nothing surfaced that history beyond the dashboard's two-hour window. A new Tools-section explorer pays it off: pick the whole mesh or any node ever heard and read its past as braille charts over a switchable 24 h / 7 d / 30 d / all-time window (w cycles). A node shows its reception volume, a median-SNR band scaled between its own extremes and coloured by quality, its hour-of-day rhythm, and the roll-up record; the mesh page shows packets and nodes per day, first-ever arrivals, and the all-time ledger. Histories read chronologically — oldest left, now right — unlike the live dashboard's sliding chart. Three repository queries feed it: a windowed per-node observation record (packet rows excluded, as everywhere reception is attributed), per-UTC-day activity totals, and first-ever appearances with names. Everything is stored data; no device is needed and nothing transmits. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
70 lines
2.4 KiB
Python
70 lines
2.4 KiB
Python
"""The ``timemachine`` tool: explore everything the recorder ever heard.
|
|
|
|
Opens the Time Machine (see :mod:`meshterm.ui.timemachine_screen`): pick the whole mesh
|
|
or any node ever observed and read its history as braille charts — reception volume,
|
|
the median-SNR band, the hour-of-day rhythm, packets and nodes per day, first-ever
|
|
arrivals — over a switchable 24 h / 7 d / 30 d / all-time window. The passive monitor
|
|
has been writing this history since the first session; this is where it pays off.
|
|
|
|
Menu-only: the explorer is interactive by nature (subjects, windows, scrolling); the
|
|
``monitor`` and ``nodes`` subcommands remain the scripted views over the same data.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Optional
|
|
|
|
import typer
|
|
|
|
from ..context import AppContext
|
|
from .base import Tool, ToolResult, register
|
|
|
|
|
|
@register
|
|
class TimeMachineTool(Tool):
|
|
"""Read the mesh's recorded past: per-node timelines, daily volumes, arrivals."""
|
|
|
|
name = "timemachine"
|
|
title = "Time machine"
|
|
icon = "⏳"
|
|
help = "Explore the recorded history — timelines, rhythms, arrivals"
|
|
category = "Tools"
|
|
order = 18 # after the measurement tools: they make history, this reads it
|
|
|
|
async def prompt_params(self, ctx: AppContext) -> Optional[dict[str, Any]]:
|
|
"""Run the explorer; there are never parameters to collect.
|
|
|
|
The screen presents everything itself and returns when dismissed, so
|
|
returning ``None`` tells the menu the invocation is complete (the same
|
|
pattern as the ``dashboard`` tool).
|
|
|
|
Args:
|
|
ctx: Shared application context.
|
|
|
|
Returns:
|
|
Always ``None``.
|
|
"""
|
|
from ..ui.timemachine_screen import open_timemachine
|
|
|
|
await open_timemachine(ctx)
|
|
return None
|
|
|
|
async def run(self, ctx: AppContext, params: dict[str, Any]) -> ToolResult:
|
|
"""Nothing runs here: the Time Machine lives inside :meth:`prompt_params`.
|
|
|
|
Args:
|
|
ctx: Shared application context.
|
|
params: Unused.
|
|
|
|
Returns:
|
|
An empty :class:`ToolResult` (only reachable from a scripted call).
|
|
"""
|
|
return ToolResult(summary={})
|
|
|
|
def register_cli(self, app: typer.Typer) -> None:
|
|
"""Register no CLI command — the Time Machine is an interactive explorer.
|
|
|
|
Args:
|
|
app: The Typer application (untouched).
|
|
"""
|