Your Wish v2 is Here!
Finally, Wish v2 is here! This release brings a ton of improvements, especially
for Bubble Tea apps running over SSH. We've rebuilt Wish on top of the all-new
Bubble Tea v2, which includes a blazing fast Cursed Renderer, automatic
color profile detection, better terminal handling, and much more.
Note
This is a major release with breaking changes. Please see the Upgrade Guide for details on how to migrate your apps to Wish v2.
❤️ Charm Land Import Path
We've updated our import paths to use vanity domains and use our domain to import Go packages.
// Before
import "github.com/charmbracelet/wish"
// After
import "charm.land/wish/v2"💨 Built on Bubble Tea v2
Wish v2 is built on top of the all-new Bubble Tea v2, which ships with the Cursed Renderer. The new renderer is based on the ncurses rendering algorithm and is highly optimized for speed, efficiency, and accuracy.
This means SSH apps get massive performance benefits and lower bandwidth usage by orders of magnitude. No more fighting over terminal I/O either—Bubble Tea v2 handles all of that for you.
To take advantage of the new renderer, you don't need to do anything except upgrade to Wish v2.
🎨 Automatic Color Profile Detection
Remember all that color profile detection code you had to write? It's gone! Bubble Tea v2 now automatically detects the terminal's color profile and downsamples colors accordingly using the colorprofile library.
// Before
renderer := bubbletea.MakeRenderer(s)
txtStyle := renderer.NewStyle().Foreground(lipgloss.Color("10"))
// After
txtStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("10"))Colors just work now, no matter where the ANSI styling comes from. The MakeRenderer function has been removed entirely—you can use Lip Gloss styles directly.
🌍 Client Environment Variables
Bubble Tea v2 sends an EnvMsg at startup with the client's environment variables. This is especially handy for SSH apps where os.Getenv would give you the server's environment, not the client's.
Important: Wish automatically passes the client's environment to Bubble Tea when you use bubbletea.MakeOptions(). This means tea.EnvMsg will contain the client's environment, not the server's!
You can access client environment variables in two ways:
Use tea.EnvMsg (Recommended)
func teaHandler(s ssh.Session) (tea.Model, []tea.ProgramOption) {
m := model{}
// bubbletea.MakeOptions(s) passes client environment automatically
return m, bubbletea.MakeOptions(s)
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.EnvMsg:
// These are CLIENT environment variables!
m.term = msg.Getenv("TERM")
m.lang = msg.Getenv("LANG")
m.user = msg.Getenv("USER")
}
return m, nil
}Pass from Handler
If you need environment variables before Init() runs, extract them in the handler:
func teaHandler(s ssh.Session) (tea.Model, []tea.ProgramOption) {
// Get client environment from SSH session
env := s.Environ()
// Make sure we have the TERM= variable
pty, _, ok := s.Pty()
if ok {
env = append(env, "TERM="+pty.Term)
}
m := model{}
return m, append(bubbletea.MakeOptions(s), tea.WithEnvironment(env))
}Warning
Don't use os.Getenv() in SSH apps—it returns the server's environment! Always use tea.EnvMsg or ssh.Session.Environ().
🧘 Declarative Views
In Bubble Tea v1, View() returned a string. In v2, it returns a tea.View struct that lets you declare everything about your view—alt screen, mouse mode, window title, and more:
// Before
func (m model) View() string {
return "Hello, world!"
}
// After
func (m model) View() tea.View {
v := tea.NewView("Hello, world!")
v.AltScreen = true
v.MouseMode = tea.MouseModeAllMotion
v.WindowTitle = "My SSH App"
return v
}No more tea.WithAltScreen() program options—just set the fields you need.
🚫 Suspend Prevention
SSH sessions can't be suspended the same way local terminals can. Wish v2 now automatically filters out tea.SuspendMsg and replaces them with tea.ResumeMsg to prevent issues with programs trying to suspend over SSH.
This is handled automatically in the bubbletea.MakeOptions function—you don't need to do anything.
🪟 Better Terminal Detection
Wish v2 now properly sets the TERM environment variable when a PTY is attached, and sets the initial window size correctly. This means your Bubble Tea apps will have accurate terminal information from the start.
📦 Simplified Bubbletea Middleware
The bubbletea middleware API has been streamlined:
Removed:
MakeRenderer()— Color profiles are now automaticMiddlewareWithColorProfile()— No longer needed with automatic color detectionQueryTerminalFilter— Terminal querying is now handled by Bubble Tea v2
Kept:
Middleware()— Still the easiest way to serve Bubble Tea appsMiddlewareWithProgramHandler()— For when you need access totea.ProgramMakeOptions()— Returns the correcttea.WithInputandtea.WithOutputoptionsHandlerandProgramHandlertypes — Same as before
✌️ All the Bubble Tea v2 Goodness
Since Wish is built on Bubble Tea, you get all the new features:
- Progressive keyboard enhancements — shift+enter, ctrl+m, key release events
- Better key messages — Split into
KeyPressMsgandKeyReleaseMsg - Improved mouse support — Separate messages for clicks, releases, wheel, and motion
- Native clipboard support — Works over SSH with OSC52!
- Terminal colors — Read and set foreground/background colors
- Cursor control — Position, color, shape, and blinking
- Synchronized updates — Reduces tearing with mode 2026
- Better Unicode — Mode 2027 for proper wide character handling
For details on all the Bubble Tea v2 features, see the Bubble Tea v2 What's New document.
🔗 Updated Dependencies
All Charm libraries have been updated to use the new charm.land import paths:
charm.land/bubbletea/v2— Bubble Tea v2charm.land/lipgloss/v2— Lip Gloss v2charm.land/log/v2— Charm Log v2charm.land/wish/v2— Wish v2 (that's us!)
🌈 More on Wish v2
Ready to migrate? Head over to the Upgrade Guide for the full migration checklist.
Changelog
New!
- 60c1caf: feat(examples): add cat example to demonstrate piping input (#444) (@aymanbagabas)
- 577d86b: feat(v2): prevent suspending bubbletea (#457) (@caarlos0)
Fixed
- 5dca543: fix(ci): add revive var-naming linter exclusion (@aymanbagabas)
- 7afbce9: fix(tea): set TERM environment variable when we have a PTY attached (@aymanbagabas)
- ab914a8: fix(tea): set the initial window size (@aymanbagabas)
- 6cd7463: fix: comment (@caarlos0)
- 4f2886a: fix: lint warnings in cmd_windows.go (@aymanbagabas)
- fbddc11: fix: log (@caarlos0)
- 322607f: fix: remove unnecessary nolint directive (@aymanbagabas)
Docs
- 9e37cf2: docs: add contributing guidelines (#473) (@bashbunni)
- 20439f8: docs: add environment variable access section to upgrade guide (@aymanbagabas)
- 0a19d05: docs: add upgrade guide for Wish v2 (#531) (@aymanbagabas)
Other stuff
- 009eb93: ci: sync dependabot config (#454) (@charmcli)
- a4957e6: ci: sync dependabot config (#490) (@charmcli)
- 70cc6c9: ci: sync dependabot config (#500) (@charmcli)
- 97b245c: ci: update dependabot (@caarlos0)
- 6b4494c: ci: use lint action from meta + add lint-sync (#499) (@andreynering)
- 02b9cd9: feat!: v2 (@caarlos0)
- 3d6b4cb: refactor: migrate to charm.land/wish/v2 module path (#510) (@aymanbagabas)
Feedback
Have thoughts on Wish v2? We'd love to hear about it. Let us know on…
Part of Charm.
Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة