How keencli Turns Thousands of Router Log Lines into a Focused Diagnostic Report
Router failures rarely arrive as a clean error message. A PPPoE disconnect, route change, DNS update, and a burst of unrelated service logs can all land in the same file. The useful event may be one line inside several thousand.
I built keencli to make that failure repeatable to inspect. It collects a timestamped snapshot from a Keenetic router, reduces the log to likely network events, masks identifiers, and prepares a diagnostic prompt. AI analysis is optional; the local report is not.
Start with a reproducible snapshot
The fetch command can collect system, PPPoE, ping-check, log, Wi-Fi, and mesh
data into one timestamped directory:
outputs/2026-06-25_18-15-39/
├── system.json
├── interface_PPPoE0.json
├── pingcheck.json
├── log.txt
├── wifi.json
└── mesh.json
This matters more than it first appears. Router state changes while you investigate it. Saving the related inputs together means I can analyze the same evidence again without reconnecting to the device or hoping the failure repeats.
Filter for events, not keywords
The analyze command reads the latest snapshot and keeps events around PPPoE,
modem hangups, ping-check state, WAN link changes, routes, IP addresses, and DNS
changes. Boot noise, driver chatter, provisioning messages, and recurring bridge
updates are filtered out.
In a typical example documented in the project, roughly 3,000 input lines become about 60 lines worth reviewing. That is not a benchmark or a guarantee. It is a useful description of the intended reduction: preserve the sequence around a failure while removing lines that do not help explain it.
A smaller event stream is useful even without an LLM. It is easier to read, diff, attach to an issue, and keep as evidence.
Mask before writing the report
Diagnostic files contain identifiers that should not casually move between
machines or services. keencli masks IPv4 addresses, common MAC-address formats,
and JSON ssid values before writing analyzed output. The same masking step is
applied again while the AI prompt is assembled.
The router password is read from an environment variable or .env file and is
held using Rust’s SecretString. It does not belong in the normal config file
or diagnostic output.
This is scoped protection, not a claim of perfect anonymization. The masking rules cover known formats; unusual identifiers still need review before sharing a report.
Keep AI behind an explicit boundary
When no API key is configured, keencli analyze still produces
prompt_for_ai.txt. With an OpenRouter key, it can send the masked prompt and
save the returned report beside the snapshot.
That boundary is deliberate:
- collection and filtering work locally;
- the user can inspect the prompt before sending it;
- third-party transfer happens only in the optional AI step;
- the AI report remains a hypothesis, not a router command.
Masked data is still data sent to a third party. The tool warns about that before the request, and any suggested network change should be verified against the original logs.
What this changed
The useful part of keencli is not that it can call a model. It is that it turns an unstable troubleshooting session into a small, reviewable artifact. The model is one possible reader of that artifact; a person, issue tracker, or later comparison works just as well.
The next useful validation is broader hardware coverage. The current approach has practical value on the routers it was built around, but it should not be presented as a compatibility guarantee for every Keenetic model or every log format.
The implementation details live in the log filter, masking code, and project README.