on main help sign in
feat(clipboard): migrate clipboard to golang.design/x/clipboard
98d79e9Kieran Klukas
go.mod+5−2
@@ -17,8 +17,6 @@ require (
17 github.com/Microsoft/go-winio v0.6.2
17 github.com/Microsoft/go-winio v0.6.2
18 github.com/PuerkitoBio/goquery v1.12.0
18 github.com/PuerkitoBio/goquery v1.12.0
19 github.com/alecthomas/chroma/v2 v2.26.1
19 github.com/alecthomas/chroma/v2 v2.26.1
20− github.com/atotto/clipboard v0.1.4
21− github.com/aymanbagabas/go-nativeclipboard v0.1.3
22 github.com/aymanbagabas/go-udiff v0.4.1
20 github.com/aymanbagabas/go-udiff v0.4.1
23 github.com/bmatcuk/doublestar/v4 v4.10.0
21 github.com/bmatcuk/doublestar/v4 v4.10.0
24 github.com/charlievieth/fastwalk v1.0.14
22 github.com/charlievieth/fastwalk v1.0.14
@@ -67,6 +65,7 @@ require (
67 github.com/tidwall/sjson v1.2.5
65 github.com/tidwall/sjson v1.2.5
68 github.com/zeebo/xxh3 v1.1.0
66 github.com/zeebo/xxh3 v1.1.0
69 go.uber.org/goleak v1.3.0
67 go.uber.org/goleak v1.3.0
68+ golang.design/x/clipboard v0.8.0
70 golang.org/x/net v0.56.0
69 golang.org/x/net v0.56.0
71 golang.org/x/sync v0.21.0
70 golang.org/x/sync v0.21.0
72 golang.org/x/sys v0.46.0
71 golang.org/x/sys v0.46.0
@@ -88,6 +87,7 @@ require (
88 github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
87 github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
89 github.com/KyleBanks/depth v1.2.1 // indirect
88 github.com/KyleBanks/depth v1.2.1 // indirect
90 github.com/andybalholm/cascadia v1.3.3 // indirect
89 github.com/andybalholm/cascadia v1.3.3 // indirect
90+ github.com/atotto/clipboard v0.1.4 // indirect
91 github.com/aws/aws-sdk-go-v2 v1.42.0 // indirect
91 github.com/aws/aws-sdk-go-v2 v1.42.0 // indirect
92 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.13 // indirect
92 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.13 // indirect
93 github.com/aws/aws-sdk-go-v2/config v1.32.25 // indirect
93 github.com/aws/aws-sdk-go-v2/config v1.32.25 // indirect
@@ -195,9 +195,12 @@ require (
195 go.opentelemetry.io/otel/trace v1.44.0 // indirect
195 go.opentelemetry.io/otel/trace v1.44.0 // indirect
196 go.uber.org/multierr v1.11.0 // indirect
196 go.uber.org/multierr v1.11.0 // indirect
197 go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect
197 go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect
198+ golang.design/x/x11 v0.2.0 // indirect
198 golang.org/x/crypto v0.53.0 // indirect
199 golang.org/x/crypto v0.53.0 // indirect
199 golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
200 golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
201+ golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476 // indirect
200 golang.org/x/image v0.42.0 // indirect
202 golang.org/x/image v0.42.0 // indirect
203+ golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f // indirect
201 golang.org/x/mod v0.36.0 // indirect
204 golang.org/x/mod v0.36.0 // indirect
202 golang.org/x/oauth2 v0.36.0 // indirect
205 golang.org/x/oauth2 v0.36.0 // indirect
203 golang.org/x/term v0.44.0 // indirect
206 golang.org/x/term v0.44.0 // indirect
go.sum+8−2
@@ -86,8 +86,6 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.43.3 h1:VrIhKRCSK1umelSgB9RghvA9RTUY
86github.com/aws/aws-sdk-go-v2/service/sts v1.43.3/go.mod h1:r8wkDOuLaaMFqFiYAb8dGY2A3gJCOujMc6CFOVC4Zhc=
86github.com/aws/aws-sdk-go-v2/service/sts v1.43.3/go.mod h1:r8wkDOuLaaMFqFiYAb8dGY2A3gJCOujMc6CFOVC4Zhc=
87github.com/aws/smithy-go v1.27.2 h1:y9NPmSE6am6LjEFPfqHqG/jJk7AauQvhCJONKh7kpzk=
87github.com/aws/smithy-go v1.27.2 h1:y9NPmSE6am6LjEFPfqHqG/jJk7AauQvhCJONKh7kpzk=
88github.com/aws/smithy-go v1.27.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
88github.com/aws/smithy-go v1.27.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
89−github.com/aymanbagabas/go-nativeclipboard v0.1.3 h1:FmAWHPTwneAixu7uGDn3cL42xPlUCdNp2J8egMn3P1k=
90−github.com/aymanbagabas/go-nativeclipboard v0.1.3/go.mod h1:2o7MyZwwi4pmXXpOpvOS5FwaHyoCIUks0ktjUvB0EoE=
91github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
89github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
92github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
90github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
93github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
91github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
@@ -450,6 +448,10 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8
450go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
448go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
451go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go=
449go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go=
452go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
450go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
451+golang.design/x/clipboard v0.8.0 h1:6VEcH28wwcSgKc+vnxHHDWiRjrakTQIAJnPUrt3aOgg=
452+golang.design/x/clipboard v0.8.0/go.mod h1:s0pwrtA3Q9fgnVtGDmP5ZK/pp55cQKB23esKsjwWhWM=
453+golang.design/x/x11 v0.2.0 h1:Uiwu2guGihsJX/ZCzpoDPFz5gR/Qntm08mvoBCmRydo=
454+golang.design/x/x11 v0.2.0/go.mod h1:/5q1mFkdc1rL8mvB7DsQFi6as4tIkBv4FXjcP07mrkE=
453golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
455golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
454golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
456golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
455golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
457golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
@@ -461,9 +463,13 @@ golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
461golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
463golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
462golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
464golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
463golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
465golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
466+golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476 h1:Wdx0vgH5Wgsw+lF//LJKmWOJBLWX6nprsMqnf99rYDE=
467+golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
464golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
468golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
465golang.org/x/image v0.42.0 h1:1gSs6ehNWXLbkHBIPcWztk3D/6aIA/8hauiAYtlodVY=
469golang.org/x/image v0.42.0 h1:1gSs6ehNWXLbkHBIPcWztk3D/6aIA/8hauiAYtlodVY=
466golang.org/x/image v0.42.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q=
470golang.org/x/image v0.42.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q=
471+golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f h1:/n+PL2HlfqeSiDCuhdBbRNlGS/g2fM4OHufalHaTVG8=
472+golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f/go.mod h1:ESkJ836Z6LpG6mTVAhA48LpfW/8fNR0ifStlH2axyfg=
467golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
473golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
468golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
474golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
469golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
475golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
internal/app/app.go+7
@@ -42,6 +42,7 @@ import (
42 "github.com/charmbracelet/x/ansi"
42 "github.com/charmbracelet/x/ansi"
43 "github.com/charmbracelet/x/exp/charmtone"
43 "github.com/charmbracelet/x/exp/charmtone"
44 "github.com/charmbracelet/x/term"
44 "github.com/charmbracelet/x/term"
45+ "golang.design/x/clipboard"
45)
46)
46
47
47// UpdateAvailableMsg is sent when a new version is available.
48// UpdateAvailableMsg is sent when a new version is available.
@@ -122,6 +123,12 @@ func New(ctx context.Context, conn *sql.DB, store *config.ConfigStore, skillsMgr
122
123
123 app.setupEvents()
124 app.setupEvents()
124
125
126+ // Initialize clipboard support. This is best-effort; if it fails
127+ // (e.g., headless environment), clipboard operations will return nil.
128+ if err := clipboard.Init(); err != nil {
129+ slog.Warn("Clipboard initialization failed", "error", err)
130+ }
131+
125 // Check for updates in the background.
132 // Check for updates in the background.
126 go app.checkForUpdates(ctx)
133 go app.checkForUpdates(ctx)
127
134
internal/cmd/login.go+3−6
@@ -8,7 +8,6 @@ import (
8 "os/signal"
8 "os/signal"
9
9
10 "charm.land/lipgloss/v2"
10 "charm.land/lipgloss/v2"
11− "github.com/atotto/clipboard"
12 "github.com/charmbracelet/crush/internal/client"
11 "github.com/charmbracelet/crush/internal/client"
13 "github.com/charmbracelet/crush/internal/config"
12 "github.com/charmbracelet/crush/internal/config"
14 "github.com/charmbracelet/crush/internal/oauth"
13 "github.com/charmbracelet/crush/internal/oauth"
@@ -17,6 +16,7 @@ import (
17 "github.com/charmbracelet/x/ansi"
16 "github.com/charmbracelet/x/ansi"
18 "github.com/pkg/browser"
17 "github.com/pkg/browser"
19 "github.com/spf13/cobra"
18 "github.com/spf13/cobra"
19+ "golang.design/x/clipboard"
20)
20)
21
21
22var loginCmd = &cobra.Command{
22var loginCmd = &cobra.Command{
@@ -95,11 +95,8 @@ func loginHyper(c *client.Client, wsID string, force bool) error {
95 return err
95 return err
96 }
96 }
97
97
98− if clipboard.WriteAll(resp.UserCode) == nil {
98+ clipboard.Write(clipboard.FmtText, []byte(resp.UserCode))
99− fmt.Println("The following code should be on clipboard already:")
99+ fmt.Println("The following code should be on clipboard already:")
100− } else {
101− fmt.Println("Copy the following code:")
102− }
103
100
104 fmt.Println()
101 fmt.Println()
105 fmt.Println(lipgloss.NewStyle().Bold(true).Render(resp.UserCode))
102 fmt.Println(lipgloss.NewStyle().Bold(true).Render(resp.UserCode))
internal/ui/common/common.go+2−2
@@ -6,12 +6,12 @@ import (
6 "os"
6 "os"
7
7
8 tea "charm.land/bubbletea/v2"
8 tea "charm.land/bubbletea/v2"
9− "github.com/atotto/clipboard"
10 "github.com/charmbracelet/crush/internal/config"
9 "github.com/charmbracelet/crush/internal/config"
11 "github.com/charmbracelet/crush/internal/ui/styles"
10 "github.com/charmbracelet/crush/internal/ui/styles"
12 "github.com/charmbracelet/crush/internal/ui/util"
11 "github.com/charmbracelet/crush/internal/ui/util"
13 "github.com/charmbracelet/crush/internal/workspace"
12 "github.com/charmbracelet/crush/internal/workspace"
14 uv "github.com/charmbracelet/ultraviolet"
13 uv "github.com/charmbracelet/ultraviolet"
14+ "golang.design/x/clipboard"
15)
15)
16
16
17// MaxAttachmentSize defines the maximum allowed size for file attachments (5 MB).
17// MaxAttachmentSize defines the maximum allowed size for file attachments (5 MB).
@@ -112,7 +112,7 @@ func CopyToClipboardWithCallback(text, successMessage string, callback tea.Cmd)
112 return tea.Sequence(
112 return tea.Sequence(
113 tea.SetClipboard(text),
113 tea.SetClipboard(text),
114 func() tea.Msg {
114 func() tea.Msg {
115− _ = clipboard.WriteAll(text)
115+ clipboard.Write(clipboard.FmtText, []byte(text))
116 return nil
116 return nil
117 },
117 },
118 callback,
118 callback,
internal/ui/model/clipboard_not_supported.go+1−1
@@ -1,4 +1,4 @@
1−//go:build !(darwin || linux || windows) || arm || 386 || ios || android
1+//go:build !((darwin || linux || windows || freebsd || openbsd || netbsd) && !ios && !android)
2
2
3package model
3package model
4
4
internal/ui/model/clipboard_supported.go+12−4
@@ -1,15 +1,23 @@
1−//go:build (linux || darwin || windows) && !arm && !386 && !ios && !android
1+//go:build (darwin || linux || windows || freebsd || openbsd || netbsd) && !ios && !android
2
2
3package model
3package model
4
4
5−import "github.com/aymanbagabas/go-nativeclipboard"
5+import "golang.design/x/clipboard"
6
6
7func readClipboard(f clipboardFormat) ([]byte, error) {
7func readClipboard(f clipboardFormat) ([]byte, error) {
8 switch f {
8 switch f {
9 case clipboardFormatText:
9 case clipboardFormatText:
10− return nativeclipboard.Text.Read()
10+ data := clipboard.Read(clipboard.FmtText)
11+ if data == nil {
12+ return nil, errClipboardUnknownFormat
13+ }
14+ return data, nil
11 case clipboardFormatImage:
15 case clipboardFormatImage:
12− return nativeclipboard.Image.Read()
16+ data := clipboard.Read(clipboard.FmtImage)
17+ if data == nil {
18+ return nil, errClipboardUnknownFormat
19+ }
20+ return data, nil
13 }
21 }
14 return nil, errClipboardUnknownFormat
22 return nil, errClipboardUnknownFormat
15}
23}
internal/ui/model/keys.go+1−1
@@ -129,7 +129,7 @@ func DefaultKeyMap() KeyMap {
129 key.WithHelp("ctrl+f", "add image"),
129 key.WithHelp("ctrl+f", "add image"),
130 )
130 )
131 km.Editor.PasteImage = key.NewBinding(
131 km.Editor.PasteImage = key.NewBinding(
132− key.WithKeys("ctrl+v"),
132+ key.WithKeys("ctrl+v", "super+v"),
133 key.WithHelp("ctrl+v", "paste image from clipboard"),
133 key.WithHelp("ctrl+v", "paste image from clipboard"),
134 )
134 )
135 km.Editor.MentionFile = key.NewBinding(
135 km.Editor.MentionFile = key.NewBinding(