github charmbracelet/wish v2.0.0

9 hours ago

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 automatic
  • MiddlewareWithColorProfile() — No longer needed with automatic color detection
  • QueryTerminalFilter — Terminal querying is now handled by Bubble Tea v2

Kept:

  • Middleware() — Still the easiest way to serve Bubble Tea apps
  • MiddlewareWithProgramHandler() — For when you need access to tea.Program
  • MakeOptions() — Returns the correct tea.WithInput and tea.WithOutput options
  • Handler and ProgramHandler types — 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 KeyPressMsg and KeyReleaseMsg
  • 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 v2
  • charm.land/lipgloss/v2 — Lip Gloss v2
  • charm.land/log/v2 — Charm Log v2
  • charm.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!

Fixed

Docs

Other stuff


Feedback

Have thoughts on Wish v2? We'd love to hear about it. Let us know on…


Part of Charm.

The Charm logo

Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة

Don't miss a new wish release

NewReleases is sending notifications on new releases.