durthang 0.2.0

A modern, terminal-based MUD client with TLS, GMCP, automap, aliases, triggers, and a sidebar panel system
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
<div align="center">
  <img src="docs/images/durthang-icon.png" alt="Durthang" width="128" />
</div>

# ๐Ÿฐ Durthang

> *"A few miles north, high up in the angle where the western spur branched away from the main range, stood the old castle of Durthang, now one of the many orc-holds that clustered about the dale of Udรปn."*
> โ€” J.R.R. Tolkien, *The Return of the King*

**Durthang** is a modern, terminal-based MUD client written in Rust ๐Ÿฆ€.
It runs entirely in your terminal, requires no graphical environment, and ships as a single
statically-linked binary with no extra runtime dependencies.

---

## โœจ Features

| Category | Details |
|---|---|
| ๐Ÿ”Œ **Connection** | Plain TCP and TLS (system root certs); clean disconnect and reconnect |
| ๐Ÿ“ก **Telnet** | IAC negotiation โ€” ECHO, NAWS, GMCP; all unknown options refused |
| ๐Ÿ—บ๏ธ **GMCP** | Room.Info parsing for automap; extensible for other modules |
| ๐ŸŽจ **ANSI / VT100** | Full 16- and 256-colour rendering via ratatui |
| ๐Ÿ“œ **Scrollback buffer** | 5 000 lines; scroll with PgUp/PgDn or mouse wheel |
| โŒจ๏ธ **Input** | Shell-style history (โ†‘/โ†“), Home/End/Left/Right cursor movement |
| โšก **Aliases** | Per-character short-command expansions, stored in config |
| ๐Ÿ” **Triggers** | Regex โ†’ highlight colour and/or auto-send, stored per character |
| ๐Ÿ“‹ **Copy mode** | Scroll and copy output text to clipboard via OSC 52 |
| ๐Ÿ—บ๏ธ **Automap** | Real-time ASCII map built from GMCP `Room.Info` or heuristic `Exits:` parsing; persisted to disk per server |
| ๐ŸชŸ **Sidebar** | Right-side panel column with an **Automap** minimap and a **Notes** panel; toggleable, resizable, configurable per character |
| ๐Ÿ“ **Notes panel** | Create, edit, delete, and reorder personal free-text notes without leaving the game |
| ๐Ÿ” **Secure credentials** | Passwords stored in the OS keyring (Secret Service / macOS Keychain / Windows Credential Manager); never written to the config file |
| ๐Ÿ’พ **Config persistence** | TOML config at `~/.config/durthang/config.toml` (XDG respected); sidebar layout and notes are saved automatically |
| ๐Ÿ“ถ **Latency display** | Rolling-average latency shown in the status bar |
| ๐Ÿ–ฑ๏ธ **Mouse support** | Scroll wheel for scrollback |
| ๐Ÿชต **Logging** | Structured logs to `~/.local/share/durthang/durthang.log` via `tracing` |

---

## ๐Ÿ“ฆ Installation

| Method | Platform | Command / Link |
|---|---|---|
| <img src="docs/images/crates-io.png" height="16" alt=""> **[crates.io]https://crates.io/crates/durthang** | Any (needs Rust โ‰ฅ 1.85) | `cargo install durthang` |
| <img src="docs/images/archlinux.svg" height="16" alt=""> **[AUR]https://aur.archlinux.org/packages/durthang** (source) | Arch Linux | `yay -S durthang` |
| <img src="docs/images/archlinux.svg" height="16" alt=""> **[AUR]https://aur.archlinux.org/packages/durthang-bin** (binary) | Arch Linux | `yay -S durthang-bin` |
| <img src="docs/images/appimage.svg" height="16" alt=""> **[AppImage]https://github.com/Pommersche92/durthang/releases/latest** | Linux x86-64 | Download `.AppImage`, `chmod +x`, run |
| <img src="docs/images/windows.svg" height="16" alt=""> **[Windows Binary]https://github.com/Pommersche92/durthang/releases/latest** | Windows x86-64 | Download `.zip` from GitHub Releases |
| <img src="docs/images/github-darkmode.svg" height="16" alt=""> **From source** | Any | See below |

### From crates.io

```bash
cargo install durthang
```

### AUR (Arch Linux)

```bash
# Build from source
yay -S durthang

# Or install the pre-built binary
yay -S durthang-bin
```

### AppImage (Linux)

Download the latest `.AppImage` from [GitHub Releases](https://github.com/Pommersche92/durthang/releases/latest):

```bash
chmod +x durthang-*.AppImage
./durthang-*.AppImage
```

### Windows Binary

Download the `.zip` archive from [GitHub Releases](https://github.com/Pommersche92/durthang/releases/latest),
extract it and run `durthang.exe`.

### From source

```bash
git clone https://github.com/Pommersche92/durthang.git
cd durthang
cargo build --release
# The binary is at target/release/durthang
```

**Minimum Rust version:** 1.85 (edition 2024)

### ๐Ÿง Linux: password storage dependency

Durthang uses the OS keyring to store passwords securely.
On most Linux desktops this works out of the box via the Secret Service D-Bus API
(GNOME Keyring, KWallet, etc.).

On a **headless** or minimal system you need a running Secret Service daemon:

```bash
# Debian/Ubuntu โ€” GNOME Keyring
sudo apt install gnome-keyring
eval $(gnome-keyring-daemon --start --components=secrets)

# or use the kwallet-based stack on KDE
```

Durthang will still start without a keyring, but password saving will fail with a
runtime error. You can work around this by leaving the password field blank and
using the server's own login prompt.

---

## ๐Ÿš€ Quick Start

Launch Durthang:

```bash
durthang
```

An optional `--config` flag points to a custom config file:

```bash
durthang --config ~/my-muds.toml
```

You will land on the **server/character selection screen**.

### ๐Ÿ—‚๏ธ Selection screen keys

| Key | Action |
|---|---|
| `โ†‘` / `โ†“` | Move cursor |
| `Space` / `โ†` / `โ†’` | Expand or collapse a server |
| `Enter` | Connect with the selected character |
| `N` | Add a new server |
| `n` | Add a new character to the selected server |
| `e` | Edit the selected server or character |
| `d` | Delete the selected server or character (with confirmation) |
| `q` / `Ctrl+Q` | Quit |

---

## ๐ŸŽฎ Game Screen

Once connected, the game screen shows:

```
โ”Œโ”€ MUME โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                                            โ”‚  Automap     โ”‚
โ”‚  [scrollable MUD output]                   โ”‚  . . .@. .   โ”‚
โ”‚                                            โ”‚  @ Rivendell โ”‚
โ”‚                                            โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                            โ”‚  Notes       โ”‚
โ”‚                                            โ”‚  - buy food  โ”‚
โ”‚                                            โ”‚  - repair eq โ”‚
โ”‚                                            โ”‚              โ”‚
โ”‚                                            โ”‚              โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚              โ”‚
โ”‚ โ–ถ input line                              โ”‚              โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ†‘42 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ MUME / Berejorn   lat 12ms   Ctrl+Q disconnect            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
```

### โŒจ๏ธ Game screen keys

| Key | Action |
|---|---|
| `Enter` | Send the current input line |
| `โ†‘` / `โ†“` | Input history |
| `PgUp` / `PgDn` | Scroll the output buffer |
| `Ctrl+End` | Jump back to the live view |
| `F3` | Toggle the right sidebar |
| `F4` | Cycle focus to the next sidebar panel |
| `F1` / `Esc` (in sidebar) | Return focus to the game input |
| `Ctrl+C` | Enter copy mode (scroll + copy a line) |
| `Ctrl+Q` | Disconnect and return to the selection screen |

### ๐Ÿ’ฌ Meta-commands (type in the input line)

| Command | Effect |
|---|---|
| `/connect` | Reconnect to the current server |
| `/disconnect` | Disconnect and return to the selection screen |
| `/quit` | Exit Durthang |
| `/alias <name> <expansion>` | Add or update an alias |
| `/unalias <name>` | Remove an alias |
| `/trigger <regex> [color=<c>] [send=<cmd>]` | Add a trigger |
| `/untrigger <id-prefix>` | Remove a trigger |
| `/sidebar right` | Toggle the right sidebar |

---

## ๐ŸชŸ Sidebar panels

The sidebar is toggled with **F3**. Focus cycles through panels with **F4**.
Press **`o`** while a panel is focused to open the **Sidebar Options** overlay,
where you can toggle panel visibility, reorder panels, and adjust the sidebar width.

### ๐Ÿ—บ๏ธ Automap panel

The automap builds a live ASCII grid as you move through the world:

- `@` marks your current room.
- `.` marks known adjacent rooms.
- The legend line shows the current room name and Z-level.

Map data is loaded from GMCP `Room.Info` when available, with a fallback heuristic
that reads `Exits:` lines from the server output.
Maps are saved automatically to `~/.local/share/durthang/<server-id>.map.json`.

### ๐Ÿ“ Notes panel

A personal scratchpad attached to each character.

| Key | Action |
|---|---|
| `n` / `a` | Add a new note |
| `e` / `Enter` | Edit the selected note inline |
| `d` / `Delete` | Delete the selected note |
| `K` | Move the selected note up |
| `J` | Move the selected note down |
| `Esc` | Cancel editing |

Notes are persisted in the character's sidebar config automatically on every change.

---

## โšก Aliases & Triggers

Aliases and triggers are stored per character in the config file.

**Alias example** โ€” type `k orc` in game and it expands to `kill orc`:

```
/alias k kill
```

**Trigger example** โ€” highlight lines containing "You are hungry" in yellow:

```
/trigger You are hungry color=yellow
```

**Trigger with auto-response** โ€” auto-buy food when a shopkeeper says it's available:

```
/trigger "You see food on sale" send="buy bread"
```

---

## โš™๏ธ Configuration

Config file: `~/.config/durthang/config.toml`

```toml
[[servers]]
id = "โ€ฆ"            # auto-generated UUID
name = "MUME"
host = "mume.org"
port = 4242
tls  = true

[[characters]]
id        = "โ€ฆ"
name      = "Eomer"
server_id = "โ€ฆ"    # references servers[].id
login     = "xX_H0R538055_Xx"
password_hint = "mother's maiden name"   # reminder only, not the actual password
notes     = "Rohirrim, warrior"

[characters.sidebar]
right_visible = true
right_width   = 26

[[characters.sidebar.panels]]
kind     = "automap"
side     = "right"
height_pct = 35

[[characters.sidebar.panels]]
kind     = "notes"
side     = "right"
height_pct = 65

[[characters.aliases]]
name      = "k"
expansion = "kill"

[[characters.triggers]]
id      = "โ€ฆ"
pattern = "You are hungry"
color   = "yellow"
```

Passwords are **never** stored here ๐Ÿ”’; they live in the OS keyring under the key
`durthang/<server-id>/<character-name>`.

---

## ๐Ÿ—‚๏ธ Project Structure

```
src/
  main.rs        Entry point, CLI parsing, terminal setup, main event loop
  app.rs         Top-level state machine (ServerSelect โ†” Game)
  config/        Serde data model, TOML persistence, keyring helpers
  net/           Async TCP/TLS/Telnet connection task (tokio)
  ui/
    selection.rs Server/character selection tree-view
    game.rs      Game screen, key handling, alias/trigger evaluation
    sidebar.rs   Sidebar panel system (automap + notes)
  map/           Room data model, GMCP/heuristic parsing, JSON persistence
```

---

## ๐Ÿค Contributing

Issues and pull requests are welcome!

```bash
cargo fmt --check
cargo clippy -- -D warnings
cargo test
```

---

## ๐Ÿšข Releasing

All release automation lives in `scripts/`.

### Scripts Overview

| Script | Purpose |
|---|---|
| `scripts/release.sh` | Full pipeline: tests โ†’ crates.io โ†’ GitHub โ†’ AUR |
| `scripts/release-github.sh` | Build assets and create/update a GitHub release |
| `scripts/build-appimage.sh` | Build a Linux AppImage (auto-downloads `linuxdeploy`) |
| `scripts/deploy-aur.sh` | Update and push `durthang` + `durthang-bin` PKGBUILDs to AUR |

### Prerequisites

| Tool | Purpose | Install |
|---|---|---|
| `gh` | GitHub CLI โ€” creates releases and uploads assets | [cli.github.com]https://cli.github.com/ โ†’ `gh auth login` |
| `gcc-mingw-w64-x86-64` | Windows cross-compile linker | `sudo apt install gcc-mingw-w64-x86-64` / `sudo pacman -S mingw-w64-gcc` |
| `zip` | Windows zip packaging | `sudo apt install zip` / `sudo pacman -S zip` |
| `makepkg` | Generates `.SRCINFO` for AUR (optional) | Arch Linux or an Arch-based container |
| AUR SSH key | Authenticates pushes to AUR | Add public key at [aur.archlinux.org/account]https://aur.archlinux.org/account/ |

> `linuxdeploy` (AppImage builder) is downloaded automatically into
> `target/appimage-build/` on first use โ€” no manual install needed.

**Recommended SSH config** for AUR (`~/.ssh/config`):

```
Host aur.archlinux.org
    IdentityFile ~/.ssh/id_ed25519_aur
    User aur
```

**Optional โ€” AppImage icon:**
Place a `docs/durthang.png` (256 ร— 256 px or larger) in the repo. If absent,
a placeholder is generated via ImageMagick (`convert`), or a 1ร—1 fallback is
used so the build does not abort.

### AUR one-time setup

Before the first `--push` to AUR, clone both package repos:

```bash
mkdir -p aur/durthang
git clone ssh://aur@aur.archlinux.org/durthang.git aur/durthang/aur-repo

mkdir -p aur/durthang-bin
git clone ssh://aur@aur.archlinux.org/durthang-bin.git aur/durthang-bin/aur-repo
```

### Running a release

```bash
# Full release: crates.io + GitHub + AppImage + Windows zip + AUR
./scripts/release.sh

# Draft GitHub release (safe to iterate on)
./scripts/release.sh --draft

# Skip individual steps
./scripts/release.sh --skip-crates        # already published to crates.io
./scripts/release.sh --skip-github        # skip GitHub release
./scripts/release.sh --skip-aur           # skip AUR push
```

All build artefacts land in `target/dist/` (git-ignored):

```
target/dist/
  durthang-<ver>-x86_64.tar.gz           โ† Linux tarball (GitHub + AUR source)
  durthang-<ver>-x86_64.AppImage         โ† Linux AppImage
  durthang-<ver>-x86_64-windows.zip      โ† Windows binary zip
```

### AUR-only update

To push only a version bump to AUR without a full release:

```bash
# Dry-run (no push) โ€” inspect the changes first
./scripts/deploy-aur.sh

# Push both packages
./scripts/deploy-aur.sh --push

# Push only one package
./scripts/deploy-aur.sh --push --package durthang-bin
```

The script reads the version from `Cargo.toml`, downloads the relevant source
or binary to compute `sha256sums`, updates both PKGBUILDs, regenerates
`.SRCINFO` (via `makepkg --printsrcinfo`), and pushes to the AUR.

---

## ๏ฟฝ๐Ÿ“œ License

GNU General Public License v3.0 โ€” see [LICENSE](LICENSE) for the full text.

---

*"Even in Mordor, there were those who endured." ๐Ÿ”ฅ*