Open-source project
charmbracelet/wish avatar
charmbracelet/wish

charmbracelet/wish: building SSH apps in Go with middleware

Make SSH apps, just like that! 💫

5,601 stars127 forksGoMIT

At a glance

What is it?
Wish is an SSH server library for Go built on gliderlabs/ssh, with middlewares for Bubble Tea, git, logging and access control. It suits Go developers who want a terminal app reachable over SSH, and it is the wrong choice if you need a general-purpose shell server.
Who is it for?
Adopt Wish if you are writing a Go program that should be reachable over SSH, and you want the SSH plumbing and terminal handling handled by a library rather than by openssh-server. Do not adopt it if you need a conventional remote shell, a non-Go stack, or a turnkey product: Wish is a library, and the README documents no built-in authentication policy beyond the always-authenticating default server.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 13 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • charmbracelet/wish5,601 ★ · +84
Star history
Repository2026-09-232026-11-14
charmbracelet/wish5.5k5.6k

84 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

What Wish solves, and who it is for

The README makes the case directly: SSH is a protocol, not just a way to get a shell on a server. Because it is a protocol, you can write your own SSH server without touching openssh-server, and use it for things other than remote shell access. Wish is the library that makes that practical in Go.

The pitch lists three properties of SSH that matter for application authors: secure communication without HTTPS certificate management, user identification through SSH keys, and access from any terminal. Those are the reasons someone would pick SSH as a transport rather than a browser or a socket. If your application is a terminal UI, the third point is the whole argument.

The audience is narrow and specific. You need to be writing Go, and you need your program to be reachable over a network by terminal clients. The README points at Soft Serve, Wishlist, VHS, SSHWordle, clidle, pico.sh and others as apps built with Wish, which shows the shape of the intended use: interactive terminal programs, git hosting, and small networked toys rather than general server infrastructure.

If you are not writing Go, Wish has nothing for you. It is a library, not a server binary you configure and run.

The middleware model and how a session flows

Wish middlewares are described in the README as analogous to HTTP framework middleware: SSH handlers that do a specific task and then call the next middleware. The ordering rule is the part worth reading twice. Middlewares are composed from first to last, which means the last one is executed first. That is the inverse of how most people read a list, and it is the detail most likely to cause a confusing bug when you add access control in the wrong position.

The Bubble Tea middleware is the centerpiece. It serves any Bubble Tea application over SSH, and each SSH session gets its own tea.Program with the SSH pty input and output connected. Client window dimension and resize messages are handled natively by the tea.Program, which means you do not write resize plumbing yourself.

The git middleware adds git server functionality to any SSH server, supports repo creation on initial push, and supports custom public key based auth. It requires that git is installed on the server, which is a real deployment constraint rather than a library detail. The logging middleware records the remote address, invoked command, TERM setting, window dimensions and whether auth was public key based on connect, and the remote address plus connection duration on disconnect.

Access control is split across two middlewares. activeterm allows only connections with active terminals, and accesscontrol lets you specify allowed commands. Neither is enabled by default; the README presents them as things you opt into.

Installing Wish and serving a first Bubble Tea app

Wish is a Go module. The go.mod in the repository declares the module path as charm.land/wish/v2, so the import path for the current line carries the v2 suffix. Add it to a Go project with go get, then build a server that starts a Bubble Tea program per session.

The README points to examples/bubbletea and examples/git in the examples folder, and the repository also contains examples/bubbletea-exec, examples/bubbleteaprogram, examples/cat, examples/pty and others. Those are the reference implementations to copy from, since the README itself does not inline a full program.

bash
go get charm.land/wish/v2

Wish uses gliderlabs/ssh for its server implementation and the README states that OpenSSH is never used nor needed, so a standard ssh client is enough to reach your app.

When you rebuild locally and hit host key warnings, the README suggests adding this to ~/.ssh/config so you do not have to clear localhost entries from ~/.ssh/known_hosts each time:

bash
Host localhost
    UserKnownHostsFile /dev/null

That is a development convenience only. Do not carry it into a machine you use for anything else.

Running a Wish app under systemd

The README documents a systemd unit for a Wish app, which is more deployment guidance than most Go libraries provide. The unit is a plain Type=simple service with Restart=on-failure and a dedicated user and group.

service
[Unit]
Description=My App
After=network.target

[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/home/myapp/
ExecStart=/usr/bin/myapp
Restart=on-failure

[Install]
WantedBy=multi-user.target

The README warns that you need to run systemctl daemon-reload every time you change the unit file, and gives sudo systemctl start myapp for the start, restart and stop family. It also recommends a separate user per app and gives the command to create one:

bash
useradd --system --user-group --create-home myapp

There is a gap here worth naming. The README does not document where the server key lives, how to back it up, or what happens to clients when the key changes. For a production SSH endpoint, host key persistence is the operational question, and the documentation is silent on it beyond the automatic key generation mentioned for the default server.

Where Wish is the wrong tool

Wish is not a replacement for openssh-server, and the README does not pretend otherwise. It describes openssh-server as a perfectly valid use of SSH, just a different one. If what you want is remote shell access to a machine, install a shell server. Writing a Go program to do that is more work for a worse result.

The default server is the sharpest limitation. Wish includes the ability to create an always authenticating default SSH server with automatic server key generation. Always authenticating means no credential check. That is convenient for a demo and dangerous for anything reachable from the internet. The README notes that there is no risk of accidentally sharing a shell because there is no default behavior that does that, which is true, but it is a different guarantee from authentication. The accesscontrol and activeterm middlewares restrict access, and you have to add them yourself.

The git middleware requires git installed on the server. If your deployment target is a minimal container image, that is an extra dependency to account for.

Finally, the README is a library overview, not a manual. It does not document rollback, key rotation, rate limiting configuration, or how the ratelimiter, recover and scp packages should be composed. Those packages exist in the repository layout, but the README does not explain them. Expect to read source and examples.

How Wish compares to gliderlabs/ssh and a hand-rolled server

The most direct alternative is gliderlabs/ssh, which Wish is built on. Wish does not replace it; it wraps it. If you already have a gliderlabs/ssh server, the README states that Wish should be easy to integrate into any existing projects. The difference in approach is the middleware layer. With gliderlabs/ssh you write handlers and wire session setup yourself. With Wish you get named middlewares for the common cases: serving a Bubble Tea program with pty wiring and resize handling, running a git server, logging connections, restricting to active terminals, restricting allowed commands.

The second alternative is writing against golang.org/x/crypto/ssh directly. That gives you the most control and the most work. You own pty allocation, window resize propagation, key generation and session lifecycle. Wish's dependency list includes golang.org/x/crypto, github.com/charmbracelet/keygen and github.com/charmbracelet/x/xpty, which is a fair summary of what you would otherwise assemble yourself.

The third comparison is not a library at all. If your goal is to expose an existing HTTP service or a static site, SSH is the wrong transport and Wish adds nothing. The README's argument for SSH rests on terminal access and key-based identity, and neither applies to a browser-facing service.

Maintenance, licence and upgrade cost

The repository is not archived, and the last push was on 2026-11-01. Releases v2.0.3, v2.0.4 and v2.0.5 all landed between 2026-07-31 and 2026-10-01, so the v2 line has seen recent activity. There is no published support window or long-term release branch.

The licence is MIT. That is permissive and short, and it means you can use Wish in closed-source products. It also means there is no warranty and no obligation on the maintainers to fix anything. Nothing here is legal advice; read the LICENSE file in the repository if the terms matter to your organisation.

Upgrade cost is the part to plan for. The repository ships UPGRADE_GUIDE_V2.md at the top level, which is a signal that the v1 to v2 transition involved breaking changes worth writing down. The module path is charm.land/wish/v2, and the import paths for middlewares follow it, so a v1 codebase will need import rewrites in addition to whatever the guide covers. Since Wish is a library, every upgrade is a rebuild and redeploy of your own binary rather than a package manager operation.

Editorial conclusion

Adopt Wish if you are writing a Go program that should be reachable over SSH, and you want the SSH plumbing and terminal handling handled by a library rather than by openssh-server. Do not adopt it if you need a conventional remote shell, a non-Go stack, or a turnkey product: Wish is a library, and the README documents no built-in authentication policy beyond the always-authenticating default server. Before committing, verify what the accesscontrol and activeterm middlewares allow on your target version, and read UPGRADE_GUIDE_V2.md if you are coming from v1, because the module path is charm.land/wish/v2 and the v2 line is what the current releases track.

Frequently asked questions

What is charmbracelet/wish?

Wish is an SSH server library for Go with sensible defaults and a collection of middlewares that make building SSH apps easier. It is built on gliderlabs/ssh and is intended for writing custom SSH servers, not for replacing openssh-server.

How do I install charmbracelet/wish?

It is a Go module, so you add it to a Go project with go get. The module path declared in the repository go.mod is charm.land/wish/v2, and the examples folder contains runnable references such as examples/bubbletea and examples/git.

Does charmbracelet/wish require OpenSSH on the server?

No. The README states that Wish uses gliderlabs/ssh to implement its SSH server, and that OpenSSH is never used nor needed, so you can uninstall it if you want.

What does the Bubble Tea middleware in charmbracelet/wish do?

It serves any Bubble Tea application over SSH, giving each SSH session its own tea.Program with the SSH pty input and output connected. Client window dimension and resize messages are handled natively by the tea.Program.

Is charmbracelet/wish secure by default?

The README documents an always authenticating default SSH server with automatic server key generation, which performs no credential check. The accesscontrol and activeterm middlewares exist to restrict allowed commands and require an active terminal, and you have to add them yourself.

Official sources

  1. charmbracelet/wish on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Community

Where developers are discussing it

Posts on Hacker News, dev.to and Lobsters that link to this repository, found by our scan of those communities. 1 in the last 30 days; newest first.

Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/charmbracelet-wish.svg)](https://hysenlabs.com/projects/charmbracelet-wish)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/charmbracelet-wish.svg)](https://hysenlabs.com/projects/charmbracelet-wish)

Was this useful?