Files
viandante/README.md
T
truetype b20f3c0f31
ci / lint (ruff) (push) Has been cancelled
ci / pytest (py3.10) (push) Has been cancelled
ci / pytest (py3.11) (push) Has been cancelled
ci / pytest (py3.12) (push) Has been cancelled
ci(actions): add Gitea Actions ci+release workflows + fix ruff findings
- .gitea/workflows/ci.yml: ruff + matrix pytest on 3.10/3.11/3.12
  on push and PR to main; concurrency cancels superseded runs
- .gitea/workflows/release.yml: gate (ruff + pytest) + build sdist/wheel
  on tag 'v*', uploads via actions/upload-artifact@v4
- README.md: add ci and version badges right under H1
- src/viandante/cli.py: drop unused prompt_toolkit.patch_stdout import
- tests/test_deadsouls.py: add missing RoomBlock import (F821)
- tests/test_logger.py: drop unused local 'raw' in unicode test
- tests/test_mapper.py: rename unused locals to _a/_b/_c (F841) to
  preserve intent (B has 's' back-edge; C is orphan)
2026-07-07 17:15:54 +00:00

266 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# viandante
[![ci](http://192.168.0.183:8085/truetype/viandante/badges/ci/main.svg)](http://192.168.0.183:8085/truetype/viandante/actions?workflow=ci)
[![version](https://img.shields.io/badge/version-v0.2.0-blue)](http://192.168.0.183:8085/truetype/viandante/releases/tag/v0.2.0)
`viandante` ospita un **client MUD CLI** pensato per collegarsi a un MUD
LP‑MUD basato sul driver **FluffOS** (per esempio **Dead Souls**, libreria
3.8.6). Il client parla telnet con estensioni standard: `ECHO`, `NAWS`,
`TTYPE`, e rifiuta (`WONT`) tutte le altre (`GMCP`, `MSDP`, `MCCP`, `MXP`
per ora). Tiene traccia delle stanze visitate e le renderizza come **mappa
ASCII** on-demand.
---
> **viandante** — *Italian for "wanderer"*.
> A telnet client + ASCII mapper for LP-MUDs (FluffOS, Dead Souls),
> with GMCP vitals/room decoder and append-only session logging.
```
[ Town ] -- n -- [ Bank ]
| |
s e
| |
[ Gate ] ---------- [ Inn ]
```
> *Mapping the world, one step at a time.*
## Changelog
Le novità di `viandante` rispetto alla release 0.1 (bootstrap del client, prima del rename a `viandante`).
### v0.2.0 — GMCP, mapper BFS, EventLogger, rename
**Aggiunto**
- **GMCP decoder** (`src/viandante/gmcp.py`): validazione del module name via regex `^[A-Za-z][A-Za-z0-9_.]*$`, decoder per `Char.Vitals` con sinonimi (`hp`/`health`/`life`, `sp`/`mana`/`magic`, `mv`/`move`/`stam`), `Room.Info` con vnum del server come id stabile (`vnum{int(num)}`).
- **Mapper multi-source BFS** (`src/viandante/mapper.py`): coordinate calcolate solo al render (lazy layout), phase 1 BFS dal `current`, phase 2 orphan placement iterato (gestisce `n→s`), phase 3 stack nell'angolo in basso a destra per stanze isolate. Snapshot di `coords.items()` prima di mutare per evitare collisioni.
- **EventLogger** (`src/viandante/logger.py`): due sink opt‑in paralleli — `--log-json` JSONL append‑only (un evento per riga) e `--log-md` Markdown streamed con heading `## HH:MM:SS.mmm — kind`. Lock difensivo + idempotenza di `close()` + redazione password in tre punti (`line_sent` / `local_cmd` / `login`).
- **Comandi slash locali** in `cli._handle_local_command`: `/map` `/where` `/look` `/vitals` `/stats` `/password PWD` `/clear` `/help` `/quit`. Le `/...` non riconosciute vengono inoltrate al MUD; `/password PWD` logga `local_cmd` + `line_sent` + `login` tutti esplicitamente redatti.
- **Auto‑login regex** ancorate a riga (`_LOGIN_RE` / `_PASSWORD_RE`) con varianti `>` / `*` / `:` / `?` e invio one‑shot del nome; suggerimento `/password PWD` se la password è assente.
- **Status bar** `prompt_toolkit` con `FormattedText` rinfrescato 2×/s (`refresh_interval=0.5`) che mostra stanza corrente, GMCP on/off, hp/sp/mv.
- **Documentazione** completa in `docs/` (8 file: architecture, modules, protocol, usage, events, development, gitea + README index) con analisi architetturale, data flow, schema eventi del logger, flag CLI, workflow dev e flusso Gitea con warning sicurezza token.
**Modificato**
- **Hardening asyncio** in `cli._run`: `asyncio.wait(FIRST_COMPLETED)` + `conn.close()` *prima* del drain delle pending task. Sblocco via EOF invece che `CancelledError`.
- **Strip delle credenziali** in `.git/config` dopo ogni push: URL remoto resta pulito, il token viaggia solo inline nella URL del singolo `git push`.
**Rinominato**
- **`progetto2` → `viandante`** (Italian for *wanderer*) ovunque: package Python (`src/progetto2/` → `src/viandante/`), pyproject `name` + `description` + `keywords`, repo Gitea (`truetype/progetto2` → `truetype/viandante`), documentazione. L'entry‑point script resta `mudclient` (un solo nome da ricordare).
**Tests**
- **66 test** (precedenti 46 + 20 nuovi per `EventLogger`).
- Nuovo `tests/test_logger.py` per roundtrip JSONL + rendering MD, redazione password, idempotenza di `close()`, robustness su byte ESC non validi.
### v0.1.0
- Bootstrap iniziale: client telnet minimale (IAC state machine + GMCP marker), mapper ASCII naive, supporto Dead Souls via `localhost:6666`. Package e repo chiamati `progetto2`.
## Requisiti
- Python ≥ 3.10
- Una libreria Python: [`prompt_toolkit`](https://pypi.org/project/prompt-toolkit/) ≥ 3.0
## Setup
```bash
cd viandante
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
```
Verrà installato il comando `mudclient`.
## Uso
```bash
# Default: si collega a localhost:6666 con un finestra iniziale 120×40
mudclient
# Espliciti
mudclient --host localhost --port 6666 --cols 120 --rows 40
# Auto-login: passa il nome; il client lo invia quando il server chiede
# "What name do you wish?". Se serve la password, il client chiede
# /password PWD oppure la puoi passare con --password (o env MUDCLIENT_PASSWORD).
mudclient --user zork --password hunter2
# Log di sessione: ogni evento (testo, GMCP, stanze, comandi locali, linee inviate)
# viene serializzato in append‑only, flushed dopo ogni write — adatto a `tail -f`.
mudclient --log-json ~/mud-session.jsonl --log-md ~/mud-session.md
```
> I file `--log-json` / `--log-md` vengono **truncati** all'apertura:
> ogni nuova sessione ricomincia da zero. Per ruotare manualmente
> prima della sessione:
> ```bash
> mv ~/mud-session.jsonl{,.prev} && mudclient --log-json ~/mud-session.jsonl
> ```
Schema eventi (un oggetto JSON per riga):
| `kind` | Scopo | Note |
| --- | --- | --- |
| `session_start` | apertura sessione | `host`, `port`, `user` |
| `session_end` | chiusura normale | `reason` |
| `disconnect` | chiusura socket | `reason` |
| `text` | chunk ANSI ricevuto dal server | `raw` preservato |
| `room_enter` | mapper ha registrato una stanza | `id`, `name`, `exits` |
| `gmcp` | frame GMCP accettato | `module`, `payload` |
| `vitals` | snapshot hp/sp/mv | solo i campi presenti |
| `line_sent` | riga inoltrata al MUD | password redatte (`***`) |
| `local_cmd` | comando `/...` intercettato | password/segreti redatti |
| `login` | prompt/risposta auto-login | `value` redatto di default; passare `redact=False` per i nomi account |
Una volta connesso:
| Tasto | Effetto |
| --- | --- |
| Testo qualsiasi | inviato al MUD terminato da `\r\n` |
| `/map` | disegna la mappa ASCII delle stanze visitate (multi-source BFS) |
| `/where` | mostra la stanza corrente e le uscite |
| `/look` | invia `look` al MUD |
| `/vitals` | mostra hp/sp/mv (via GMCP `Char.Vitals`) |
| `/stats` | locali scoperti, GMCP stato, vitali |
| `/password PWD` | invia una password al MUD |
| `/clear` | pulisce lo schermo |
| `/help` (`/?`) | mostra l'aiuto |
| `/quit` (`/q`, `Ctrl‑D`) | esce |
Il prompt è sempre del tipo:
```
[NomeStanza]> _
```
con il nome della stanza corrente in cyan brillante. In basso c'è una **status bar** che mostra:
```
[stanza: Outer Courtyard] [GMCP: on] HP 100/100 SP 50/50 MV 80/80
```
Quando il server invia `Char.Vitals` via GMCP, hp/sp/mv compaiono automaticamente.
## Layout dei sorgenti
```
viandante/
├── pyproject.toml # metadata + dipendenze + entry point `mudclient`
├── README.md # questo file
├── .gitignore
├── docs/ # documentazione completa (vedi sotto)
│ ├── README.md # indice della cartella docs
│ ├── architecture.md # analisi architetturale (data flow + boundary)
│ ├── modules.md # descrizione modulo‑per‑modulo
│ ├── protocol.md # telnet, GMCP, ANSI, euristiche Dead Souls
│ ├── usage.md # flag CLI, esempi, auto‑login, comandi slash
│ ├── events.md # schema eventi del logger
│ ├── development.md # setup dev, pytest, ruff, come estendere
│ └── gitea.md # flusso di pubblicazione sul Gitea + sicurezza token
├── src/viandante/ # package
│ ├── __init__.py
│ ├── __main__.py # supporta `python -m viandante`
│ ├── cli.py # entry point + UI a riga di comando
│ ├── telnet.py # state machine IAC + asyncio
│ ├── deadsouls.py # parser di stanze Dead Souls / LP‑MUD
│ ├── gmcp.py # parser GMCP (Char.Vitals / Room.Info)
│ ├── mapper.py # grafo delle stanze + render ASCII
│ ├── logger.py # EventLogger (JSONL + Markdown sinks)
│ └── ansi.py # stripper ANSI per il parser
└── tests/ # unit test (pytest)
├── test_telnet.py
├── test_ansi.py
├── test_deadsouls.py
├── test_mapper.py
└── test_logger.py
```
## 📚 Documentazione completa
La documentazione vive nella cartella [`docs/`](./docs/README.md) e
copre:
- **[`docs/architecture.md`](./docs/architecture.md)** — analisi
architetturale completa: dipendenze tra moduli, data flow di un
frame dal server, boundary concerns, refactor raccomandati.
- **[`docs/modules.md`](./docs/modules.md)** — cosa fa ogni modulo,
API pubblica, responsabilità, tolleranze.
- **[`docs/protocol.md`](./docs/protocol.md)** — IAC state machine,
GMCP wire format, euristiche Dead Souls, ANSI handling.
- **[`docs/usage.md`](./docs/usage.md)** — riferimento flag CLI, esempi
pratici, flusso auto‑login, comandi slash, rotazione log.
- **[`docs/events.md`](./docs/events.md)** — schema completo degli
eventi del logger (JSONL + Markdown), regole di redazione, esempi
`jq`.
- **[`docs/development.md`](./docs/development.md)** — workflow
sviluppatore: setup, test, lint, come aggiungere moduli, debugging.
- **[`docs/gitea.md`](./docs/gitea.md)** — flusso di pubblicazione sul
server Gitea `192.168.0.183:8085` (utente `truetype`) **+ regole di
sicurezza del token**.
## Note sul protocollo
Il client negozia:
- `ECHO`: invia `WILL ECHO` in risposta a `DO ECHO`. Il server non eseguirà
più echo remoto, mentre l'eco locale è gestita da `prompt_toolkit`.
- `NAWS`: se attivo, invia la dimensione della finestra iniziale.
- `TTYPE`: risponde con `XTERM-256COLOR` quando il server lo richiede.
- `GMCP`: viene **offerto proattivamente** (`WILL` al connect). Dead Souls
lo accetta e invia pacchetti `Char.Vitals`, `Room.Info`, `Char.Login`,
ecc. Quando una stanza arriva via `Room.Info`, si usa il `num`
(vnum) del server come id stabile della stanza.
Le righe di stato del parser considerano `IAC GA` (`249`) e `IAC EOR`
(`239`) come delimitatori di "record/prompt", così da poter chiudere un
blocco di stanza anche quando il prompt testuale non è immediatamente
riconoscibile.
La rilevazione automatica della geometria del mondo si basa sul **movimento
inviato dall'utente** + **descrizione della stanza** osservata subito
dopo: quando digiti `n`, il client informa il mapper; quando la risposta
del MUD arriva, `Mapper.visit()` collega automaticamente
`corrente → nuova` lungo la direzione digitata.
Il render della mappa è un **multi-source BFS** dal current room, con
fallback di **posizionamento orfano** per le stanze visitate in sequenza
indietro (es. `n→s`) e stack nell'angolo in basso a destra per stanze
completamente isolate.
## Limitazioni note (v0.2)
- **MSDP / MCCP / MCP / MXP** non sono ancora abilitati: una sessione che
li richiede ottiene `WONT` dal nostro lato. Dead Souls usa GMCP, che è
ben coperto; altri MUD basati su MSDP possono essere aggiunti seguendo
lo schema di `gmcp.py`.
- La detection delle stanze **senza GMCP** resta **euristica** sul testo.
- **Login automatico**: il client riconosce il prompt `What name do you
wish?` / `Password:` e invia automaticamente solo la prima volta.
Per la password senza `--password` viene mostrato un suggerimento
`usa /password <pwd>` (niente prompt annidato, troppo fragile).
- Gli `u`/`d` (verticali) sono rappresentati nella mappa ma non
influenzano le coordinate 2D (offset `(0,0)`).
- La status bar si aggiorna ad ogni rilettura della riga del prompt;
per refresh continui in idle si può aggiungere `refresh_interval`
al `PromptSession` come estensione futura.
## Sviluppo
```bash
pip install -e ".[dev]"
pytest # gira tutta la suite
ruff check src tests # lint
```
## Licenza
MIT per il codice del client. Il server **Dead Souls** non è GPL — assicurati
di rispettarne la licenza prima di redistribuirlo.