This is a good fit for a local TypeScript CLI.

The clean architecture is:

1. Watch the EverQuest log file.
2. Parse incoming chat/dialog lines into events.
3. Resolve a stable speaker identity.
4. Look up or assign a voice for that speaker.
5. Send the text to Kokoro-FastAPI.
6. Play the returned audio locally.
7. Persist the speaker-to-voice mapping so the same NPC or guild member keeps the same voice.

A CLI can absolutely emit sound. It just needs to hand audio to something that can play it. On Linux and Windows, the most portable pattern is: have the CLI request WAV/MP3 from your TTS backend, then either play it through a Node audio library or spawn a bundled player such as `ffplay` or `mpv`.

A practical design would be:

* `watcher`: tails the EQ log
* `parser`: classifies lines as NPC speech, guild chat, tells, combat spam, system text
* `speaker-store`: `speakers.json` or SQLite
* `voice-selector`: deterministic initial assignment plus manual overrides
* `tts-client`: calls Kokoro-FastAPI over HTTP
* `audio-out`: queues and plays audio
* `config`: filters, volume, rate limits, voice pool, per-channel rules

For voice reuse, do not key only on raw display text. Use a normalized speaker key such as:

* `npc:<normalized name>`
* `player:guild:<character name>`
* optionally `zone:<zone name>` if the game reuses names across contexts

Then store something like:

```json
{
  "npc:guard_thom": {
    "voice": "en_female_04",
    "last_seen": "2026-07-14T19:22:10"
  },
  "player:guild:sysadmin": {
    "voice": "en_male_07"
  }
}
```

For assignment, a good default is deterministic hashing:

* first encounter of a speaker hashes to one voice from a fixed pool
* manual overrides can replace that later
* the same speaker always gets the same voice on every machine if the voice pool matches

That gives you consistency without needing to curate every NPC by hand.

For NPC dialog versus guild chat, I would split policies:

* NPCs: one stable voice per NPC
* guild chat: one stable voice per guild member
* system/combat spam: usually ignore
* unknown speakers: assign a temporary voice and persist only if it repeats

A few implementation choices matter:

* Use a long-running local HTTP TTS server, not shelling out per line to generate everything from scratch.
* Add a queue so overlapping lines do not talk over each other.
* Cache generated clips for repeated lines.
* Add line deduping, because MMOs often repeat log lines.
* Add optional “interrupt” behavior for urgent lines, and “drop” behavior for spam.

For the TypeScript stack, I would use:

* `commander` or `oclif` for the CLI
* `chokidar` or a small tailing library for file watching
* `zod` for config validation
* `better-sqlite3` if you want a durable speaker database
* `execa` to invoke an audio player if you keep playback external

For cross-platform sharing, a CLI is still fine. The main constraint is audio playback. The simplest distribution path is:

* package the CLI as an npm tool
* have it talk to a locally running Kokoro-FastAPI instance
* require either `ffmpeg`/`mpv` installed, or bundle a playback method
* ship platform-specific startup scripts for Linux and Windows

If you want the widest compatibility, keep the core app platform-neutral and avoid Linux-only assumptions like PipeWire-specific commands. Generate standard WAV and play it with a cross-platform backend.

The likely best prototype is:

```text
eq-tts watch --log ~/.wine/drive_c/.../eqlog.txt --voice-mode hash --playback mpv
```

Then expand it with:

```text
eq-tts map npc "Guard Thomas" --voice en_male_03
eq-tts mute "combat"
eq-tts channel guild --enabled
eq-tts export-speakers
```

My recommendation: build the first version as a Node/TypeScript CLI with three boundaries only:

* file tailing
* speaker/voice mapping
* audio playback

Keep the Kokoro integration behind a single HTTP client. That will make it easy to swap in another local TTS backend later, and easy for Windows users to run the same app.

