mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-16 04:22:36 +00:00
fix(tui): improve macOS paste and shortcut parity
- support Cmd-as-super and readline-style fallback shortcuts on macOS - add layered clipboard/OSC52 paste handling and immediate image-path attach - add IDE terminal setup helpers, terminal parity hints, and aligned docs
This commit is contained in:
parent
432772dbdf
commit
9556fef5a1
31 changed files with 1303 additions and 100 deletions
|
|
@ -1,26 +1,93 @@
|
|||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { readClipboardText, writeClipboardText } from '../lib/clipboard.js'
|
||||
import { isUsableClipboardText, readClipboardText, writeClipboardText } from '../lib/clipboard.js'
|
||||
|
||||
describe('readClipboardText', () => {
|
||||
it('does nothing off macOS', async () => {
|
||||
const run = vi.fn()
|
||||
|
||||
await expect(readClipboardText('linux', run)).resolves.toBeNull()
|
||||
expect(run).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('reads text from pbpaste on macOS', async () => {
|
||||
const run = vi.fn().mockResolvedValue({ stdout: 'hello world\n' })
|
||||
|
||||
await expect(readClipboardText('darwin', run)).resolves.toBe('hello world\n')
|
||||
expect(run).toHaveBeenCalledWith('pbpaste', [], expect.objectContaining({ encoding: 'utf8', windowsHide: true }))
|
||||
expect(run).toHaveBeenCalledWith(
|
||||
'pbpaste',
|
||||
[],
|
||||
expect.objectContaining({ encoding: 'utf8', maxBuffer: 4 * 1024 * 1024, windowsHide: true })
|
||||
)
|
||||
})
|
||||
|
||||
it('returns null when pbpaste fails', async () => {
|
||||
const run = vi.fn().mockRejectedValue(new Error('pbpaste failed'))
|
||||
it('reads text from PowerShell on Windows', async () => {
|
||||
const run = vi.fn().mockResolvedValue({ stdout: 'from windows\r\n' })
|
||||
|
||||
await expect(readClipboardText('darwin', run)).resolves.toBeNull()
|
||||
await expect(readClipboardText('win32', run)).resolves.toBe('from windows\r\n')
|
||||
expect(run).toHaveBeenCalledWith(
|
||||
'powershell',
|
||||
['-NoProfile', '-NonInteractive', '-Command', 'Get-Clipboard -Raw'],
|
||||
expect.objectContaining({ encoding: 'utf8', maxBuffer: 4 * 1024 * 1024, windowsHide: true })
|
||||
)
|
||||
})
|
||||
|
||||
it('tries powershell.exe first on WSL', async () => {
|
||||
const run = vi.fn().mockResolvedValue({ stdout: 'from wsl\n' })
|
||||
|
||||
await expect(readClipboardText('linux', run, { WSL_INTEROP: '/tmp/socket' } as NodeJS.ProcessEnv)).resolves.toBe('from wsl\n')
|
||||
expect(run).toHaveBeenCalledWith(
|
||||
'powershell.exe',
|
||||
['-NoProfile', '-NonInteractive', '-Command', 'Get-Clipboard -Raw'],
|
||||
expect.objectContaining({ encoding: 'utf8', maxBuffer: 4 * 1024 * 1024, windowsHide: true })
|
||||
)
|
||||
})
|
||||
|
||||
it('uses wl-paste on Wayland Linux', async () => {
|
||||
const run = vi.fn().mockResolvedValue({ stdout: 'from wayland\n' })
|
||||
|
||||
await expect(readClipboardText('linux', run, { WAYLAND_DISPLAY: 'wayland-1' } as NodeJS.ProcessEnv)).resolves.toBe('from wayland\n')
|
||||
expect(run).toHaveBeenCalledWith(
|
||||
'wl-paste',
|
||||
['--type', 'text'],
|
||||
expect.objectContaining({ encoding: 'utf8', maxBuffer: 4 * 1024 * 1024, windowsHide: true })
|
||||
)
|
||||
})
|
||||
|
||||
it('falls back to xclip on Linux when wl-paste fails', async () => {
|
||||
const run = vi
|
||||
.fn()
|
||||
.mockRejectedValueOnce(new Error('wl-paste missing'))
|
||||
.mockResolvedValueOnce({ stdout: 'from xclip\n' })
|
||||
|
||||
await expect(readClipboardText('linux', run, { WAYLAND_DISPLAY: 'wayland-1' } as NodeJS.ProcessEnv)).resolves.toBe('from xclip\n')
|
||||
expect(run).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'wl-paste',
|
||||
['--type', 'text'],
|
||||
expect.objectContaining({ encoding: 'utf8', maxBuffer: 4 * 1024 * 1024, windowsHide: true })
|
||||
)
|
||||
expect(run).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'xclip',
|
||||
['-selection', 'clipboard', '-out'],
|
||||
expect.objectContaining({ encoding: 'utf8', maxBuffer: 4 * 1024 * 1024, windowsHide: true })
|
||||
)
|
||||
})
|
||||
|
||||
it('returns null when every clipboard backend fails', async () => {
|
||||
const run = vi.fn().mockRejectedValue(new Error('clipboard failed'))
|
||||
|
||||
await expect(readClipboardText('linux', run, { WAYLAND_DISPLAY: 'wayland-1' } as NodeJS.ProcessEnv)).resolves.toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('isUsableClipboardText', () => {
|
||||
it('accepts normal text', () => {
|
||||
expect(isUsableClipboardText('hello world\n')).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects empty or whitespace-only content', () => {
|
||||
expect(isUsableClipboardText('')).toBe(false)
|
||||
expect(isUsableClipboardText(' \n\t')).toBe(false)
|
||||
})
|
||||
|
||||
it('rejects binary-looking clipboard payloads', () => {
|
||||
expect(isUsableClipboardText('PNG\u0000\u0001\u0002\u0003IHDR')).toBe(false)
|
||||
expect(isUsableClipboardText('TIFF\ufffd\ufffd\ufffdmetadata')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue