Termcourse is a Go 1.26.6 terminal UI for browsing and posting to Discourse forums. It replaces the original Ruby implementation while retaining its browsing, posting, realtime, rendering, localization, theming, and image features.
- Browse Latest, Unread, Private Messages, Hot, New, and Top topic lists.
- Cycle Top periods: daily, weekly, monthly, quarterly, and yearly.
- Rounded, paginated compact/category/stats topic-list layouts, including PM-specific users/replies columns.
- Read complete topics with lazy post-stream loading, scrolling, read-state updates, and a progress footer.
- Create topics, select categories, reply to topics or posts, and like/unlike posts.
- Search posts and open the matching post context.
- Browse/filter notifications and jump to their topic/post.
- Persistent folder tabs above each screen panel: Topics, Search, Notifications, and Compose. Open topics and images remain within the destination that led to them.
- A contextual second rail for topic/notification filters plus search and composition stages, with one spacer row below the masthead.
- Theme-colored folder rails and responsive, clickable footer controls for screen-specific hotkeys, with pointer-hover highlighting and the active theme anchored at bottom right.
- Mouse-clickable tabs, footer controls, and rows plus wheel scrolling, with keyboard navigation retained throughout.
- Cookie login with username/password, TOTP, or backup codes.
- API-key fallback for sites where browser login is unsuitable.
- MessageBus list/topic updates, notification and PM badges, resume positions, and watchdog recovery for cookie sessions.
- Inline multiline composer with cursor movement, line breaks, character validation, submit/cancel controls.
- Built-in English, French, German, and Spanish UI translations.
- Built-in
default,slate,fairground,rust, andhackerthemes plus YAML overrides. - Truecolor, 256-color, and 16-color output.
- GFM Markdown rendering (including lists, quotes, code, tasks, and tables), OSC8 links, emoji substitution, and ANSI/grapheme-aware sizing.
- High-quality inline/fullscreen images through Kitty Unicode placements, with colored
chafasymbols (orviu) as the portable fallback and explicit size/quality limits. - Incremental screen repainting and resize-responsive layouts, with a versioned branded masthead, wide-terminal logo, rounded titled panels, and themed block gauges.
- Rate-limit errors show the server-provided retry duration and local deadline when available, and explicitly identify untimed responses.
On Linux or macOS, the release installer selects the archive for the current operating system and architecture, verifies its SHA-256 checksum, checks the binary's reported version, and installs it as /usr/local/bin/termcourse:
curl -fsSL https://raw.githubusercontent.com/merefield/termcourse/master/install-release.sh | shThe installer requires curl or wget, tar, and either sha256sum (Linux) or shasum (macOS). It uses sudo only when the destination is not writable. For a user-local installation that does not require sudo:
curl -fsSL https://raw.githubusercontent.com/merefield/termcourse/master/install-release.sh |
TERMCOURSE_BIN_DIR="$HOME/.local/bin" shEnsure $HOME/.local/bin is on PATH when using that location. To install a particular release reproducibly, replace vX.Y.Z with a tag from Releases:
release_tag=vX.Y.Z
curl -fsSL https://raw.githubusercontent.com/merefield/termcourse/master/install-release.sh |
sh -s -- --version "$release_tag"On Windows, download and inspect the PowerShell installer, then run it for the current process without changing the machine-wide execution policy:
Invoke-WebRequest https://raw.githubusercontent.com/merefield/termcourse/master/install-release.ps1 -OutFile install-release.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\install-release.ps1The Unix installer supports --help, --version TAG, and --bin-dir DIR; the PowerShell installer accepts -Version, -BinDir, and -Repository. Both also support the corresponding TERMCOURSE_* environment variables.
The Windows installer defaults to %LOCALAPPDATA%\Programs\termcourse\bin and reports when that directory must be added to PATH.
Prebuilt releases do not require Go. Each GitHub Release contains these assets:
| Operating system | Architectures | Archive | Installation |
|---|---|---|---|
| Linux | AMD64, ARM64 | .tar.gz |
Installer or manual |
| macOS | Intel (AMD64), Apple Silicon (ARM64) | .tar.gz |
Installer or manual |
| Windows | AMD64, ARM64 | .zip |
Installer or manual |
For a manual installation, verify the selected archive against the release's checksums.txt, extract termcourse (or termcourse.exe on Windows), and place it on PATH.
Run Termcourse with the hostname or URL of any Discourse site:
termcourse meta.discourse.orgIf no credentials are configured, Termcourse prompts for the missing username and password. Password input is hidden. Confirm the installed release at any time with termcourse --version.
Go 1.26.6 or newer is required when installing from source. go install compiles Termcourse locally:
go install github.com/merefield/termcourse/cmd/termcourse@latest
termcourse meta.discourse.orgIf the shell cannot find termcourse, add the Go binary directory to PATH. go install uses GOBIN when configured and otherwise uses $(go env GOPATH)/bin:
export PATH="$(go env GOPATH)/bin:$PATH"git clone https://github.com/merefield/termcourse.git
cd termcourse
make build
./termcourse meta.discourse.orgmake build creates ./termcourse in the repository root. Without make, use go build -o ./termcourse ./cmd/termcourse. You can also run directly from a checkout without keeping a binary:
go run ./cmd/termcourse meta.discourse.orgmake install is also available and honours DESTDIR and PREFIX:
make install PREFIX="$HOME/.local"For repeat use, credentials can be supplied in .env or the host-mapped credentials file described under Configuration. The examples below use an installed termcourse; replace it with ./termcourse when running a binary built in the repository. Username/password login enables realtime MessageBus updates:
DISCOURSE_USERNAME="you@example.com" \
DISCOURSE_PASSWORD="your_password" \
termcourse --theme slate --lang fr https://your.discourse.hostAPI-key login (HTTP features only):
DISCOURSE_API_KEY="your_key" \
DISCOURSE_API_USERNAME="your_username" \
termcourse --theme fairground https://your.discourse.hostList the built-in and configured themes, or preview one theme:
termcourse themes
termcourse themes hackerA local .env is loaded automatically. CLI credentials override host credentials from YAML, which override generic environment variables. If both login and API pairs exist, login is tried first unless the host entry selects auth: api.
For contributors, make check runs formatting validation, vet, race-enabled Go tests, installer integration tests, and a local build.
VERSION is the maintained release-version source of truth. Go embeds it for local builds, tagged module installs can report their module version, and GoReleaser injects the validated tag into release binaries. termcourse --version and the wide masthead subtitle use the same resolved build version. Untagged development builds append their embedded commit and dirty state to the maintained version.
GoReleaser builds static Linux, macOS, and Windows archives for AMD64 and ARM64, plus checksums.txt. Test the configuration locally without publishing:
goreleaser release --snapshot --clean --skip=publishPushing a semantic-version tag that matches VERSION runs the release workflow. It validates the tag syntax and source version, confirms the tagged commit is reachable from master, runs the complete cross-platform check suite, verifies that the tag did not move between validation and publication, and then creates the GitHub Release. No package manager, container registry, or announcement publisher is configured.
For a new release, update VERSION in the release PR, merge it, then create and push the matching vX.Y.Z tag from that merge commit. Do not maintain the release number in any other source or workflow file.
An existing unpublished tag containing the release configuration can also be published explicitly with gh workflow run release.yml --ref master -f tag=TAG.
The Go version is a replacement rather than a separately configured application. Existing .env files, generic Discourse credential variables, and host entries in credentials.yml remain compatible. Authentication precedence is unchanged, so most users can replace bundle exec bin/termcourse HOST with termcourse HOST and keep their credentials as they are.
Be mindful of these differences:
- Ruby, Bundler, and the gem bundle are no longer required. The Go build produces one
termcourseexecutable; usetermcoursefor an installed binary or./termcoursefor one built in the repository. ./theme.ymlis no longer discovered automatically. Move it to the platform user configuration directory (~/.config/termcourse/theme.ymlon Linux), setTERMCOURSE_THEME_FILE, or pass--theme-file PATH. This prevents the active theme changing with the launch directory.- Existing theme names, all 12 color fields, partial overrides, and the legacy top-level theme-map YAML format remain supported. The newer format can also contain
theme: NAMEand athemes:map. Remove or leaveTERMCOURSE_THEMEblank if the file'stheme:selection should take effect. - Theme files are now checked strictly. Unknown fields, invalid colors, unreadable explicitly selected files, and unknown themes stop startup with an explanatory error instead of being silently ignored or replaced with the default theme.
- If an old
.envcontainsTERMCOURSE_IMAGE_MODE=stable, replace it withbalanced. The supported values arecompat,balanced, andhigh. - The default symbol-thumbnail size changed from 14 lines to 48 columns by 6 lines. Existing explicit
TERMCOURSE_IMAGE_LINESvalues still work; useTERMCOURSE_IMAGE_COLUMNSto set the width. - Automatic color handling now detects terminal capabilities instead of using the Ruby version's platform heuristic.
TERMCOURSE_COLOR_MODE=truecolor,256, or16still forces a specific mode. - New optional controls include
TERMCOURSE_MOUSE,TERMCOURSE_IMAGE_PROTOCOL, andTERMCOURSE_IMAGE_COLUMNS. They require no migration because their defaults preserve automatic behaviour.
There is no new monolithic Go configuration file. Configuration remains split between environment variables, credentials YAML, and theme YAML, and no effective Ruby theme color or authentication option has been removed.
The program looks for host credentials in:
TERMCOURSE_CREDENTIALS_FILE./credentials.yml~/.config/termcourse/credentials.yml
See credentials.example.yml and .env.example.
Theme selection uses the first available value:
--theme NAMETERMCOURSE_THEME- The
theme:value in the theme file default
The five built-in themes are default, slate, fairground, rust, and hacker; they work without any files. Theme files are loaded from --theme-file PATH, then TERMCOURSE_THEME_FILE, then the platform user configuration directory (~/.config/termcourse/theme.yml on Linux). The launch directory is deliberately not consulted.
See theme.example.yml for partial built-in overrides and custom themes. Supported keys are primary, background, highlighted, highlighted_text, borders, bar_backgrounds, separators, list_numbers, list_text, post_username, list_meta, and accent. Colors accept #rrggbb, indexes 0–255, black, white, red, green, blue, yellow, cyan, magenta, gray/grey, or none. Invalid fields, colors, files, and theme names produce actionable errors.
With TERMCOURSE_IMAGE_PROTOCOL=auto, Termcourse probes for Kitty graphics support and uses Unicode virtual placements when available in truecolor mode. This gives inline thumbnails that remain part of the terminal cell layout, plus resize-responsive fullscreen images. Kitty commands are passed through tmux and GNU Screen automatically.
When Kitty is unavailable, Termcourse uses chafa for colored symbol rendering and can use Sixel for fullscreen output on compatible terminals. If chafa is not installed, viu is the secondary fallback. These programs are optional external tools and must be available on PATH; image-free operation needs no external image-rendering tool.
Use TERMCOURSE_IMAGE_PROTOCOL=kitty to force Kitty or TERMCOURSE_IMAGE_PROTOCOL=symbols to disable it. TERMCOURSE_IMAGE_BACKEND selects the fallback tool. Image downloads retain the active Discourse authentication and are constrained by the configured byte, pixel, and terminal-cell limits.
| Variable | Purpose |
|---|---|
DISCOURSE_USERNAME, DISCOURSE_PASSWORD |
Cookie login credentials. |
DISCOURSE_API_KEY, DISCOURSE_API_USERNAME |
API authentication fallback. |
TERMCOURSE_CREDENTIALS_FILE |
Credentials YAML override. |
TERMCOURSE_THEME, TERMCOURSE_THEME_FILE |
Theme name/file; overridden by --theme and --theme-file. |
TERMCOURSE_LANG |
en, fr, de, or es; then LC_ALL, LC_MESSAGES, LANG. |
TERMCOURSE_COLOR_MODE |
auto, truecolor, 256, or 16; auto detects output capabilities. |
TERMCOURSE_LINKS, TERMCOURSE_EMOJI |
Set to 0 to disable. |
TERMCOURSE_MOUSE |
Set to 0 to disable click/wheel capture and retain ordinary terminal text selection. |
TERMCOURSE_IMAGES |
Set to 0 to disable previews. |
TERMCOURSE_IMAGE_PROTOCOL |
auto, kitty, or symbols; auto probes Kitty and falls back safely. |
TERMCOURSE_IMAGE_BACKEND |
auto, chafa, viu, or off. |
TERMCOURSE_IMAGE_MODE |
compat, balanced (default), or high for symbol fallback. |
TERMCOURSE_IMAGE_COLORS |
auto, none, 16, 240, 256, or full. |
TERMCOURSE_IMAGE_COLUMNS, TERMCOURSE_IMAGE_LINES |
Maximum thumbnail size, defaults 48×6 cells. |
TERMCOURSE_IMAGE_MAX_BYTES |
Per-image limit, default 5,242,880. |
TERMCOURSE_IMAGE_QUALITY_FILTER |
Set to 0 to allow noisy previews. |
TERMCOURSE_TICK_MS |
Input/resize poll interval, default 100ms. |
TERMCOURSE_HTTP_DEBUG |
Set to 1 for request status, timing, retry, and rate-limit diagnostics. |
TERMCOURSE_DEBUG, TERMCOURSE_IMAGE_DEBUG |
Set to 1 for UI/MessageBus or image diagnostics. |
Global navigation:
tcycles through the built-in and configured themes; the footer button is also available while typing.TabandShift+Tabmove between the four first-row destinations.- Click a primary or contextual folder tab to select it.
- Click a responsive footer button or use its displayed keyboard shortcut.
- Click a post or row to select it; click a selected list row again to open it.
- The mouse wheel moves through lists and scrolls the expanded post body.
- Hold the terminal's mouse-bypass modifier (commonly Shift) for text selection, or set
TERMCOURSE_MOUSE=0. - Navigation is visibly locked while editing topic titles and post bodies so an accidental click cannot discard a draft; the transient search query remains navigable.
Topic list:
- Arrows move; Enter or
1–0opens. ccreates,nopens notifications,ssearches.fcycles filters;pcycles Top periods.grefreshes;qor Escape quits.
Topic view:
- Up/Down selects posts; Left/Right scrolls the expanded post.
- Click the read-progress track to jump to the corresponding position in the complete topic.
ltoggles like;rreplies to the topic;preplies to the post.ssearches;nopens notifications;xopens an image.- Escape/Backspace goes back;
qquits.
Fullscreen image:
xor Escape closes the image and restores the topic view.- Kitty fullscreen images redraw at the new terminal dimensions when the terminal is resized.
Composer:
- Enter adds a line; arrows move; Backspace deletes.
- Ctrl+D submits; Escape cancels.
Notifications and search use arrows, Enter to open, Escape to return, and q to quit. Notifications use f to cycle filters; search results use n to open notifications.
Realtime updates require a browser-style cookie session, so they are enabled after username/password login. API-key mode retains all HTTP operations but intentionally does not create a realtime session. Login auth follows Discourse's CSRF/cookie flow and prompts for TOTP or backup codes when the server requests a second factor.
For Discourse rate-limit responses, Termcourse prefers the HTTP Retry-After value and falls back to JSON extras.wait_seconds or extras.time_left. The error panel shows a countdown and local retry time when possible. It says explicitly when the server reports that retry is already available or when the server provides no timing information; Termcourse does not invent an unreliable delay.
Set TERMCOURSE_DEBUG=1 to include the server's Discourse-Rate-Limit-Error-Code in the error panel. More detail is available through these opt-in logs in the system temporary directory:
| Variable | Log file |
|---|---|
TERMCOURSE_HTTP_DEBUG=1 |
termcourse_http_debug.txt |
TERMCOURSE_DEBUG=1 |
termcourse_debug.txt |
TERMCOURSE_IMAGE_DEBUG=1 |
termcourse_image_debug.txt |
On Linux the system temporary directory is normally /tmp, unless TMPDIR selects another location. HTTP diagnostics include response status, request duration, Retry-After, and the Discourse limiter code; credentials and response bodies are not logged.
The interface runs on the current Charm v2 stack. Bubble Tea owns raw mode, the alternate screen, synchronized incremental rendering, resize events, cursor state, terminal queries, color downsampling, window metadata, and supported native progress metadata. Bubbles supplies the themed single-line editor and multiline composer, including bracketed paste, word navigation, soft wrapping, cursor behavior, and viewport scrolling. Lip Gloss v2 provides pure theme/layout styles, while Glamour v2/Goldmark renders GFM and x/ansi provides ANSI-safe grapheme measurement, truncation, Kitty graphics encoding, capability responses, and virtual image placement. x/term remains limited to pre-TUI password input, terminal detection, and sizing fallback.
The Discourse MessageBus client remains protocol-specific because MessageBus uses chunk-framed HTTP long polling rather than WebSockets. Its lifecycle and resume semantics remain domain code, while terminal I/O is delegated to Bubble Tea.
Termcourse is available under the MIT License. See COPYRIGHT for the copyright notice.