No description
  • C# 96.4%
  • PowerShell 3.6%
Find a file
2026-07-12 16:14:56 -04:00
.forgejo/workflows added install and uninstall 2026-07-12 16:13:09 -04:00
.idea/.idea.WayfarerConsole/.idea migrated project from old location to this one 2026-07-07 13:03:37 -04:00
packaging Added install and uninstall scripts to vcs 2026-07-12 16:14:56 -04:00
WayfarerConsole updated boot to v2 2026-07-12 16:00:29 -04:00
.gitignore migrated project from old location to this one 2026-07-07 13:03:37 -04:00
README.md Added install and uninstall scripts to vcs 2026-07-12 16:14:56 -04:00
wayfarer-console-plan.md added install and uninstall 2026-07-12 16:13:09 -04:00
WayfarerConsole.sln migrated project from old location to this one 2026-07-07 13:03:37 -04:00

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.exe via 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-grid DrawingContext renderer (not a TextBox), 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 $PROFILE emits (see Boot-step markers below) instead of faking a duration; checks with no step stay 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 via bootStyle in 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 directional wipe). 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 via activeManifest in Theme/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: glow only). 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-WayfarerMarker on prompt, a Read-Host proxy 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 bootSoundEnabled regardless of what the active manifest opts into. See Sound assets below.
  • Mouse-wheel scrollback, using VtNetCore's screen-buffer viewport directly (no ScrollViewer involved).
  • CI-built releases. A Forgejo Actions pipeline publishes a self-contained win-x64 single-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 under Theme/Manifests/ to load (currently pipboy or starware).
  • skipBootAnimation — skip straight to the terminal (also skips the boot sound).
  • bootSoundEnabled — master mute for boot sound, independent of what the active manifest's bootStyle.sound opts 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 under Assets/Sounds/, or omit/null for 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.exe via 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, bypassing TextBox/RichTextBox for full styling control and performance.
  • Input: WPF KeyDown/TextInput events 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 ITerminalHandoff registration (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).