- C# 96.4%
- PowerShell 3.6%
| .forgejo/workflows | ||
| .idea/.idea.WayfarerConsole/.idea | ||
| packaging | ||
| WayfarerConsole | ||
| .gitignore | ||
| README.md | ||
| wayfarer-console-plan.md | ||
| WayfarerConsole.sln | ||
Wayfarer Console
A standalone Windows desktop terminal (C#, .NET, WPF) that hosts PowerShell
(pwsh.exe) inside a themed retro-future "skin" — complete with a custom
pre-shell boot animation, a 16-color palette per persona, a Nerd Font, and
toggleable CRT/phosphor visual effects. Built as the piece a regular
Windows Terminal profile can't do: something that runs before any shell
process exists.
Two personas ship today: PipBoy (Fallout-style green monochrome) and Starware (a cooler sci-fi blue/gold palette).
Features
- Real PowerShell underneath. Spawns
pwsh.exevia ConPTY (Porta.Pty); everything you'd normally do in a terminal — PSReadLine history/completion, colors, scrollback — works as-is. The terminal screen buffer is parsed with VtNetCore and painted with a custom cell-gridDrawingContextrenderer (not aTextBox), so full per-cell color/attribute control comes for free. - Animated boot splash, tied to real startup work. Before/while the
shell spawns, an in-window sequence (ASCII art, "INITIALIZING..." lines,
progress bars, a random flavor quote) plays out with typewriter timing.
The pty actually starts concurrently with the splash, not after it — a
progress-bar check can optionally name a real
step, in which case its fill is tied to a matching marker your own$PROFILEemits (see Boot-step markers below) instead of faking a duration; checks with nostepstay purely cosmetic. A final safety-net wait ("ready") always gates the handoff to the terminal, so the splash never drops you into a shell that's still mid-$PROFILE. Each persona also picks its own presentation viabootStylein its manifest — a static/noise burst before the art types in, a boot sound, and how it hands off to the terminal (fade,static-flicker, or a directionalwipe). No shell process exists yet when this starts — this is the part a Windows Terminal profile fundamentally cannot do, since Windows Terminal always launches straight into a shell. - Manifest-based theming. Each persona is a single self-contained JSON
file under
Theme/Manifests/bundling palette, font, header/subtitle text, boot sequence content and timing, and default visual effects — nothing to wire up by hand across separate theme/boot-sequence files. Pick the active one viaactiveManifestinTheme/app.config.json. - Toggleable visual effects. An extensible, per-manifest effects
registry (
Effects/) that applies to the whole window for the whole session:crt— scanline overlay with a subtle looping flicker, plus a vignette.glow— soft phosphor bloom on the terminal text, colored from the active theme. Each manifest opts into its own defaults (PipBoy:crt+glow; Starware:glowonly). Toggle any effect at runtime by typing:crt,:glow, or:effects(lists all effects and their on/off state) and pressing Enter — these are intercepted client-side before ever reaching the shell, so they never show up in your PowerShell history or run as a real command. Feedback appears in a small reserved strip between the header and the terminal, not overlaid on your session's output.
- Desktop notifications. A themed, borderless popup (not a native
Windows toast, so it can be fully restyled) fires when the window isn't
focused:
- Command finished — after a command that ran longer than a configurable threshold, with duration/exit code/command text.
- Needs input — when a wrapped interactive prompt (e.g.
Read-Host) is waiting on you; persists until dismissed or handled. Notifications stack (newest prominent, older ones offset behind like a card deck) and animate out with a "collapsing into a black hole" scale/rotate/fade. Clicking one both dismisses it and focuses the main window. The taskbar/title-bar icon also swaps to an alert variant on any terminal bell while unfocused, resetting once the window is activated. All of this is driven by a private marker protocol emitted from your own PowerShell$PROFILE(Send-WayfarerMarkeron prompt, aRead-Hostproxy for needs-input) and stripped out of the raw output stream before VtNetCore ever parses it — nothing shows up as literal terminal text.
- Boot sound. Each persona can play a short sound once at boot start —
PipBoy gets a CRT static burst, Starware a clean retro-terminal beep
sequence — muteable globally via
bootSoundEnabledregardless of what the active manifest opts into. See Sound assets below. - Mouse-wheel scrollback, using VtNetCore's screen-buffer viewport
directly (no
ScrollViewerinvolved). - CI-built releases. A Forgejo Actions pipeline publishes a
self-contained
win-x64single-file build and attaches it to a GitHub Release-style tag push — no manual publish step.
Configuration
WayfarerConsole/Theme/app.config.json:
{
"activeManifest": "starware",
"skipBootAnimation": false,
"bootSoundEnabled": true,
"notifications": {
"enabled": true,
"notifyOnCommandFinished": true,
"notifyOnNeedsInput": true,
"minCommandDurationMs": 3000,
"finishedDisplayMs": 6000,
"corner": "BottomRight",
"offsetX": 16,
"offsetY": 16,
"showCommandText": true,
"showExitCode": true
}
}
activeManifest— which file underTheme/Manifests/to load (currentlypipboyorstarware).skipBootAnimation— skip straight to the terminal (also skips the boot sound).bootSoundEnabled— master mute for boot sound, independent of what the active manifest'sbootStyle.soundopts into.notifications.*— enable/disable per trigger type, thresholds, popup corner/offset, and how much detail to show in the body.
Adding a persona
Drop a new Theme/Manifests/{name}.json (palette, font, boot sequence
text/timing, defaultEffects) — see pipboy.json/starware.json for the
full schema — and set activeManifest to its filename (without .json).
Each check in checks is now an object, not a bare string:
"checks": [
{ "label": "CHECKING RADIATION LEVELS" },
{ "label": "SYNCING HOMELAB LXC CONTAINERS", "step": "lxc-sync" }
]
label is always shown; step, if present, ties that bar's fill to a real
bootstep marker instead of faking a duration — see Boot-step markers
below. Leave it out for purely cosmetic checks (most of them).
bootStyle controls presentation, separate from content:
"bootStyle": {
"staticBurst": true,
"sound": "pipboy-static.wav",
"transition": "static"
}
staticBurst— brief noise-glyph flicker before the ASCII art types in.sound— filename underAssets/Sounds/, or omit/nullfor silence.transition—"fade"(plain opacity cross-fade),"static"(reuses the noise-glyph burst as a "channel change" flicker), or"wipe"(a directional left-to-right reveal).
Boot-step markers ($PROFILE wiring)
Like the notification markers above, a checks entry with a step name
waits on a private OSC marker your $PROFILE emits — same wire protocol,
different type. Add this alongside your existing Send-WayfarerMarker:
function Send-WayfarerBootStep {
param([Parameter(Mandatory)][string]$Name)
if ($env:WAYFARER_CONSOLE -eq '1') {
$b64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($Name))
Write-Host -NoNewline "$([char]27)]9731;bootstep;$b64$([char]7)"
}
}
Call it once a real piece of work finishes, with a -Name matching the
manifest check's step:
# ... slow module import, dotfile sourcing, whatever actually takes time ...
Send-WayfarerBootStep -Name "lxc-sync"
And once at the very end of $PROFILE, regardless of whether any check
names it explicitly — this is the safety-net gate the splash always waits
on before handing off to the terminal:
Send-WayfarerBootStep -Name "ready"
If a named step never arrives (unwired $PROFILE, or an older one without
this call), its bar falls back to simulated pacing after an ~8s timeout
instead of hanging the splash indefinitely.
Sound assets
Assets/Sounds/pipboy-static.wav and Assets/Sounds/starware-terminal.wav
are placeholders, procedurally synthesized (filtered noise for PipBoy,
a clean sine-tone beep sequence for Starware) rather than sourced from any
real recording — there was no way to license/rip real reference audio
(e.g. an actual game's terminal sound) as part of this build. To replace
either with something better: drop a .wav at the same path with the same
filename (referenced by each manifest's bootStyle.sound), or point
bootStyle.sound at a new filename entirely.
Installing
Download a release zip, extract it anywhere, then run Install.ps1 from
inside the extracted folder:
powershell -ExecutionPolicy Bypass -File .\Install.ps1
This copies the app to %LocalAppData%\Programs\WayfarerConsole and adds a
Start Menu shortcut. Windows will likely flag the script as downloaded from
the internet (SmartScreen / "Windows protected your PC", or a blocked-file
warning) since it isn't code-signed — click "More info > Run anyway", or
run Unblock-File .\Install.ps1 first if PowerShell refuses to run it at
all. To remove it later, run Uninstall.ps1 (a copy is left in the install
directory) — it deletes the Start Menu shortcut and the installed folder.
Both scripts live in packaging/ in this repo and are bundled into every
release zip by the CI workflow.
Building
dotnet build WayfarerConsole/WayfarerConsole.csproj
Requires the .NET SDK with Windows targeting (WPF is Windows-only — there's
no Linux runtime for it). Pushing a v*.*.* tag triggers the release
workflow, which publishes a self-contained win-x64 build and attaches the
zip to the corresponding release.
Architecture
- Pty engine: Porta.Pty —
spawns
pwsh.exevia ConPTY, exposes read/write streams. - VT parsing: VtNetCore — consumes the raw ConPTY output and maintains a screen buffer (rows/cells/cursor/colors/attributes/scrollback viewport). Rendering is not its job — that's ours.
- Rendering/UI: WPF, a custom
DrawingContext-based painter (Controls/TerminalCanvas) that reads VtNetCore's screen buffer each frame, bypassingTextBox/RichTextBoxfor full styling control and performance. - Input: WPF
KeyDown/TextInputevents translated to the byte sequences pwsh/PSReadLine expect and written to the pty's input stream.
WayfarerConsole/
Assets/
Fonts/, Icons/ embedded Nerd Font + app icons
Sounds/ boot sound clips (placeholders -- see Sound assets above)
Boot/ boot splash sequencing (BootSplashRunner, BootStepSignal,
StaticNoiseText)
Configuration/ AppConfig/ConfigLoader/ManifestDefinition/BootCheckDefinition/
BootStyleDefinition
Controls/ TerminalCanvas (custom renderer), DecorativeBackground
Effects/ ITerminalEffect + CRT/glow implementations + registry
Interop/ pty session wrapper
Notifications/ marker scanner (notifications + boot-step markers),
popup window, stacking/anchoring manager
Rendering/ font resolution
Terminal/ VtNetCore controller/theme glue
Theme/
Manifests/ pipboy.json, starware.json — one file per persona
app.config.json
packaging/
Install.ps1 copies a release to %LocalAppData%, adds a Start Menu shortcut
Uninstall.ps1 removes both
Status / what's left
See wayfarer-console-plan.md for the full
milestone-by-milestone build log, including bugs found and fixed along the
way. Short version — done: pty wiring, custom rendering, boot splash (now
tied to real $PROFILE progress, with per-persona presentation and sound),
manifest-based theming, notifications, the CRT/glow effects system, and
CI-built releases with an installer/uninstaller script. Open:
- Real, licensed sound assets to replace the two synthesized placeholders (see Sound assets above).
- More effects beyond
crt/glow(the registry is built to make adding new ones cheap). - Windows 11's
ITerminalHandoffregistration (to appear in Settings > Apps > Default apps > Terminal alongside Windows Terminal) — investigated and deliberately deferred, not just unstarted. It needs package identity (a signed MSIX/sparse package) and a second terminal-hosting code path (attaching to console handles Windows hands you, instead of spawning your own pty), on top of an interface that's already had three breaking revisions. See Milestone 8 in the plan doc for the full findings — this is a scope change, not an incremental feature.
Explicitly out of scope for v1: tabs/multiple panes, SSH/WSL profiles (PowerShell-only), full xterm compliance beyond what VtNetCore handles, and a Linux build (WPF is Windows-only; a cross-platform port would mean swapping the rendering layer for something like Avalonia — see the plan doc for the full breakdown).