mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-24 22:55:13 +00:00
fix(cli): copy uses OSC52 only in SSH/WSL (#16554)
Signed-off-by: assagman <ahmetsercansagman@gmail.com>
This commit is contained in:
@@ -73,6 +73,9 @@ Slash commands provide meta-level control over the CLI itself.
|
|||||||
- **`/copy`**
|
- **`/copy`**
|
||||||
- **Description:** Copies the last output produced by Gemini CLI to your
|
- **Description:** Copies the last output produced by Gemini CLI to your
|
||||||
clipboard, for easy sharing or reuse.
|
clipboard, for easy sharing or reuse.
|
||||||
|
- **Behavior:**
|
||||||
|
- Local sessions use system clipboard tools (pbcopy/xclip/clip).
|
||||||
|
- Remote sessions (SSH/WSL) use OSC 52 and require terminal support.
|
||||||
- **Note:** This command requires platform-specific clipboard tools to be
|
- **Note:** This command requires platform-specific clipboard tools to be
|
||||||
installed.
|
installed.
|
||||||
- On Linux, it requires `xclip` or `xsel`. You can typically install them
|
- On Linux, it requires `xclip` or `xsel`. You can typically install them
|
||||||
|
|||||||
@@ -239,11 +239,12 @@ describe('commandUtils', () => {
|
|||||||
expect(mockClipboardyWrite).not.toHaveBeenCalled();
|
expect(mockClipboardyWrite).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('wraps OSC-52 for tmux', async () => {
|
it('wraps OSC-52 for tmux when in SSH', async () => {
|
||||||
const testText = 'tmux-copy';
|
const testText = 'tmux-copy';
|
||||||
const tty = makeWritable({ isTTY: true });
|
const tty = makeWritable({ isTTY: true });
|
||||||
mockFs.createWriteStream.mockReturnValue(tty);
|
mockFs.createWriteStream.mockReturnValue(tty);
|
||||||
|
|
||||||
|
process.env['SSH_CONNECTION'] = '1';
|
||||||
process.env['TMUX'] = '1';
|
process.env['TMUX'] = '1';
|
||||||
|
|
||||||
await copyToClipboard(testText);
|
await copyToClipboard(testText);
|
||||||
@@ -257,12 +258,13 @@ describe('commandUtils', () => {
|
|||||||
expect(mockClipboardyWrite).not.toHaveBeenCalled();
|
expect(mockClipboardyWrite).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('wraps OSC-52 for GNU screen with chunked DCS', async () => {
|
it('wraps OSC-52 for GNU screen with chunked DCS when in SSH', async () => {
|
||||||
// ensure payload > chunk size (240) so there are multiple chunks
|
// ensure payload > chunk size (240) so there are multiple chunks
|
||||||
const testText = 'x'.repeat(1200);
|
const testText = 'x'.repeat(1200);
|
||||||
const tty = makeWritable({ isTTY: true });
|
const tty = makeWritable({ isTTY: true });
|
||||||
mockFs.createWriteStream.mockReturnValue(tty);
|
mockFs.createWriteStream.mockReturnValue(tty);
|
||||||
|
|
||||||
|
process.env['SSH_CONNECTION'] = '1';
|
||||||
process.env['STY'] = 'screen-session';
|
process.env['STY'] = 'screen-session';
|
||||||
|
|
||||||
await copyToClipboard(testText);
|
await copyToClipboard(testText);
|
||||||
@@ -358,6 +360,21 @@ describe('commandUtils', () => {
|
|||||||
expect(tty.end).not.toHaveBeenCalled();
|
expect(tty.end).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('uses clipboardy in tmux when not in SSH/WSL', async () => {
|
||||||
|
const tty = makeWritable({ isTTY: true });
|
||||||
|
mockFs.createWriteStream.mockReturnValue(tty);
|
||||||
|
const text = 'tmux-local';
|
||||||
|
mockClipboardyWrite.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
process.env['TMUX'] = '1';
|
||||||
|
|
||||||
|
await copyToClipboard(text);
|
||||||
|
|
||||||
|
expect(mockClipboardyWrite).toHaveBeenCalledWith(text);
|
||||||
|
expect(tty.write).not.toHaveBeenCalled();
|
||||||
|
expect(tty.end).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it('skips /dev/tty on Windows and uses stderr fallback for OSC-52', async () => {
|
it('skips /dev/tty on Windows and uses stderr fallback for OSC-52', async () => {
|
||||||
mockProcess.platform = 'win32';
|
mockProcess.platform = 'win32';
|
||||||
const stderrStream = makeWritable({ isTTY: true });
|
const stderrStream = makeWritable({ isTTY: true });
|
||||||
|
|||||||
@@ -113,9 +113,7 @@ const isWSL = (): boolean =>
|
|||||||
const isDumbTerm = (): boolean => (process.env['TERM'] ?? '') === 'dumb';
|
const isDumbTerm = (): boolean => (process.env['TERM'] ?? '') === 'dumb';
|
||||||
|
|
||||||
const shouldUseOsc52 = (tty: TtyTarget): boolean =>
|
const shouldUseOsc52 = (tty: TtyTarget): boolean =>
|
||||||
Boolean(tty) &&
|
Boolean(tty) && !isDumbTerm() && (isSSH() || isWSL());
|
||||||
!isDumbTerm() &&
|
|
||||||
(isSSH() || inTmux() || inScreen() || isWSL());
|
|
||||||
|
|
||||||
const safeUtf8Truncate = (buf: Buffer, maxBytes: number): Buffer => {
|
const safeUtf8Truncate = (buf: Buffer, maxBytes: number): Buffer => {
|
||||||
if (buf.length <= maxBytes) return buf;
|
if (buf.length <= maxBytes) return buf;
|
||||||
|
|||||||
Reference in New Issue
Block a user