summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorb3nw <[email protected]>2026-01-16 16:57:17 -0600
committerGitHub <[email protected]>2026-01-16 16:57:17 -0600
commit3d095e7fe7087057467d29e78a579ff02d28da14 (patch)
tree52ddc2d366ff434e7261fe934f947960568dbd87
parent632f20558a99ed0f5fccda2dc896da28cb86a987 (diff)
downloadopencode-3d095e7fe7087057467d29e78a579ff02d28da14.tar.gz
opencode-3d095e7fe7087057467d29e78a579ff02d28da14.zip
fix: centralize OSC 52 clipboard support for SSH sessions (#8974)
-rw-r--r--packages/opencode/src/cli/cmd/tui/app.tsx10
-rw-r--r--packages/opencode/src/cli/cmd/tui/routes/session/index.tsx5
-rw-r--r--packages/opencode/src/cli/cmd/tui/ui/dialog.tsx5
-rw-r--r--packages/opencode/src/cli/cmd/tui/util/clipboard.ts16
4 files changed, 16 insertions, 20 deletions
diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx
index 1fea3f4b3..2ec1fb703 100644
--- a/packages/opencode/src/cli/cmd/tui/app.tsx
+++ b/packages/opencode/src/cli/cmd/tui/app.tsx
@@ -200,11 +200,6 @@ function App() {
renderer.console.onCopySelection = async (text: string) => {
if (!text || text.length === 0) return
- const base64 = Buffer.from(text).toString("base64")
- const osc52 = `\x1b]52;c;${base64}\x07`
- const finalOsc52 = process.env["TMUX"] ? `\x1bPtmux;\x1b${osc52}\x1b\\` : osc52
- // @ts-expect-error writeOut is not in type definitions
- renderer.writeOut(finalOsc52)
await Clipboard.copy(text)
.then(() => toast.show({ message: "Copied to clipboard", variant: "info" }))
.catch(toast.error)
@@ -627,11 +622,6 @@ function App() {
}
const text = renderer.getSelection()?.getSelectedText()
if (text && text.length > 0) {
- const base64 = Buffer.from(text).toString("base64")
- const osc52 = `\x1b]52;c;${base64}\x07`
- const finalOsc52 = process.env["TMUX"] ? `\x1bPtmux;\x1b${osc52}\x1b\\` : osc52
- /* @ts-expect-error */
- renderer.writeOut(finalOsc52)
await Clipboard.copy(text)
.then(() => toast.show({ message: "Copied to clipboard", variant: "info" }))
.catch(toast.error)
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
index d91363954..1d64a2ff1 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
@@ -697,11 +697,6 @@ export function Session() {
return
}
- const base64 = Buffer.from(text).toString("base64")
- const osc52 = `\x1b]52;c;${base64}\x07`
- const finalOsc52 = process.env["TMUX"] ? `\x1bPtmux;\x1b${osc52}\x1b\\` : osc52
- /* @ts-expect-error */
- renderer.writeOut(finalOsc52)
Clipboard.copy(text)
.then(() => toast.show({ message: "Message copied to clipboard!", variant: "success" }))
.catch(() => toast.show({ message: "Failed to copy to clipboard", variant: "error" }))
diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog.tsx
index 4477d3015..57375ba09 100644
--- a/packages/opencode/src/cli/cmd/tui/ui/dialog.tsx
+++ b/packages/opencode/src/cli/cmd/tui/ui/dialog.tsx
@@ -141,11 +141,6 @@ export function DialogProvider(props: ParentProps) {
onMouseUp={async () => {
const text = renderer.getSelection()?.getSelectedText()
if (text && text.length > 0) {
- const base64 = Buffer.from(text).toString("base64")
- const osc52 = `\x1b]52;c;${base64}\x07`
- const finalOsc52 = process.env["TMUX"] ? `\x1bPtmux;\x1b${osc52}\x1b\\` : osc52
- /* @ts-expect-error */
- renderer.writeOut(finalOsc52)
await Clipboard.copy(text)
.then(() => toast.show({ message: "Copied to clipboard", variant: "info" }))
.catch(toast.error)
diff --git a/packages/opencode/src/cli/cmd/tui/util/clipboard.ts b/packages/opencode/src/cli/cmd/tui/util/clipboard.ts
index 9c91cf305..2526f4171 100644
--- a/packages/opencode/src/cli/cmd/tui/util/clipboard.ts
+++ b/packages/opencode/src/cli/cmd/tui/util/clipboard.ts
@@ -5,6 +5,21 @@ import { lazy } from "../../../../util/lazy.js"
import { tmpdir } from "os"
import path from "path"
+/**
+ * Writes text to clipboard via OSC 52 escape sequence.
+ * This allows clipboard operations to work over SSH by having
+ * the terminal emulator handle the clipboard locally.
+ */
+function writeOsc52(text: string): void {
+ if (!process.stdout.isTTY) return
+ const base64 = Buffer.from(text).toString("base64")
+ const osc52 = `\x1b]52;c;${base64}\x07`
+ // tmux and screen require DCS passthrough wrapping
+ const passthrough = process.env["TMUX"] || process.env["STY"]
+ const sequence = passthrough ? `\x1bPtmux;\x1b${osc52}\x1b\\` : osc52
+ process.stdout.write(sequence)
+}
+
export namespace Clipboard {
export interface Content {
data: string
@@ -123,6 +138,7 @@ export namespace Clipboard {
})
export async function copy(text: string): Promise<void> {
+ writeOsc52(text)
await getCopyMethod()(text)
}
}