From 765b972a00bf04a96a5d1238dd991dc6f984b03e Mon Sep 17 00:00:00 2001 From: HugoAir Dev Date: Wed, 10 Jun 2026 17:38:28 +0900 Subject: [PATCH 1/3] feat(coding-agent): fall back to text paste when clipboard holds no image app.clipboard.pasteImage dead-ended with a status message when the clipboard held text, which made the chord useless on hosts that only deliver that one keypress (VS Code's integrated terminal forwarding Ctrl+V, Windows clipboard history via Win+V simulating Ctrl+V). - handleImagePaste now pastes clipboard text through the editor's paste semantics when no image is available; an empty clipboard reports 'Clipboard is empty'. - readTextFromClipboard reaches the Windows clipboard through host PowerShell under WSL (mirroring readImageFromClipboard), with CRLF normalization and UTF-8 output encoding. - handleClipboardTextRawPaste's empty-clipboard status message moved to the actual empty branch (was shown after successful pastes). - Clipboard reads are constructor-injectable for tests. Implements proposal 1 of #1628. --- docs/keybindings.md | 4 +- packages/coding-agent/CHANGELOG.md | 4 + .../coding-agent/src/config/keybindings.ts | 2 +- .../src/modes/controllers/input-controller.ts | 29 ++++- .../src/modes/utils/hotkeys-markdown.ts | 2 +- packages/coding-agent/src/utils/clipboard.ts | 16 +++ .../test/input-controller-smart-paste.test.ts | 118 ++++++++++++++++++ 7 files changed, 166 insertions(+), 9 deletions(-) create mode 100644 packages/coding-agent/test/input-controller-smart-paste.test.ts diff --git a/docs/keybindings.md b/docs/keybindings.md index dfc881bbe..dfe79cfd5 100644 --- a/docs/keybindings.md +++ b/docs/keybindings.md @@ -39,10 +39,10 @@ app.stt.toggle: [] | `app.display.reset` | `Ctrl+L` | Reset terminal display | | `app.clipboard.copyLine` | `Alt+Shift+L` | Copy the current line | | `app.clipboard.copyPrompt` | `Alt+Shift+C` | Copy the whole prompt | -| `app.clipboard.pasteImage` | `Ctrl+V` (`Alt+V` fallback on Windows) | Paste an image from the clipboard | +| `app.clipboard.pasteImage` | `Ctrl+V` (`Alt+V` fallback on Windows) | Paste from the clipboard (image preferred, text fallback) | | `app.stt.toggle` | `Alt+H` | Toggle speech-to-text recording | -On Windows Terminal, `Ctrl+V` may be handled by the terminal paste command before `omp` sees it; use the `Alt+V` fallback when clipboard image paste appears to do nothing. Windows Terminal also swallows `Ctrl+Enter`, so the follow-up shortcut also binds `Ctrl+Q` — the same chord GitHub Copilot CLI uses. If your existing `keybindings.yml` already assigns `Ctrl+Q` to another action, that user remap wins and follow-up keeps `Ctrl+Enter` unless you explicitly bind `app.message.followUp`. +On Windows Terminal, `Ctrl+V` may be handled by the terminal paste command before `omp` sees it; use the `Alt+V` fallback when clipboard image paste appears to do nothing. When the clipboard holds no image, `app.clipboard.pasteImage` pastes the clipboard text instead, so hosts that deliver only this chord (VS Code's integrated terminal when configured to forward `Ctrl+V`, Windows clipboard history via `Win+V`) work for both payload kinds. Windows Terminal also swallows `Ctrl+Enter`, so the follow-up shortcut also binds `Ctrl+Q` — the same chord GitHub Copilot CLI uses. If your existing `keybindings.yml` already assigns `Ctrl+Q` to another action, that user remap wins and follow-up keeps `Ctrl+Enter` unless you explicitly bind `app.message.followUp`. Terminals that implement OSC 5522 enhanced paste can send clipboard MIME data directly to `omp`; image pastes are attached as `[Image #N]`, while text/plain paste events keep normal paste behavior. When OSC 5522 is unavailable, bracketed paste still handles text, and a pasted single image-file path is loaded as an image when the file is readable from the `omp` host. diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index f488065de..13b2fbc4d 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Changed + +- `app.clipboard.pasteImage` (`Ctrl+V`) now falls back to pasting clipboard text when no image is present, so hosts that deliver only that chord (VS Code's integrated terminal forwarding `Ctrl+V`, Windows clipboard history via `Win+V`) cover both payload kinds; WSL text reads now reach the Windows clipboard through host PowerShell like image reads already did ([#1628](https://github.com/can1357/oh-my-pi/issues/1628)). + ## [15.10.12] - 2026-06-10 ### Added diff --git a/packages/coding-agent/src/config/keybindings.ts b/packages/coding-agent/src/config/keybindings.ts index b652586b3..61d7c2cd9 100644 --- a/packages/coding-agent/src/config/keybindings.ts +++ b/packages/coding-agent/src/config/keybindings.ts @@ -136,7 +136,7 @@ export const KEYBINDINGS = { }, "app.clipboard.pasteImage": { defaultKeys: getDefaultPasteImageKeys(), - description: "Paste image from clipboard", + description: "Paste image or text from clipboard", }, "app.clipboard.pasteTextRaw": { defaultKeys: ["ctrl+shift+v", "alt+shift+v"], diff --git a/packages/coding-agent/src/modes/controllers/input-controller.ts b/packages/coding-agent/src/modes/controllers/input-controller.ts index de55b97ad..b4e134d6f 100644 --- a/packages/coding-agent/src/modes/controllers/input-controller.ts +++ b/packages/coding-agent/src/modes/controllers/input-controller.ts @@ -49,7 +49,14 @@ const TINY_TITLE_PROGRESS_DONE_TTL_MS = 3_000; const TINY_TITLE_PROGRESS_REVEAL_DELAY_MS = 1_000; export class InputController { - constructor(private ctx: InteractiveModeContext) {} + constructor( + private ctx: InteractiveModeContext, + /** Injectable clipboard reads so tests can drive paste flows without a real clipboard. */ + private clipboard: { + readImage: typeof readImageFromClipboard; + readText: typeof readTextFromClipboard; + } = { readImage: readImageFromClipboard, readText: readTextFromClipboard }, + ) {} #enhancedPaste?: EnhancedPasteController; @@ -821,10 +828,21 @@ export class InputController { async handleImagePaste(): Promise { try { - const image = await readImageFromClipboard(); + const image = await this.clipboard.readImage(); if (!image) { - this.ctx.showStatus("No image in clipboard (use terminal paste for text)"); - return false; + // Smart paste (#1628): no image on the clipboard — fall back to + // pasting its text so the same chord covers both payload kinds. + // Hosts that pre-empt the terminal's own paste (VS Code's + // integrated terminal, Win+V clipboard history) deliver only + // this keypress, so a miss here must not dead-end. + const text = await this.clipboard.readText(); + if (!text) { + this.ctx.showStatus("Clipboard is empty"); + return false; + } + this.ctx.editor.pasteText(text); + this.ctx.ui.requestRender(); + return true; } return await this.#normalizeAndInsertPastedImage( { @@ -842,10 +860,11 @@ export class InputController { async handleClipboardTextRawPaste(): Promise { try { - const text = await readTextFromClipboard(); + const text = await this.clipboard.readText(); if (text) { this.ctx.editor.insertText(text); this.ctx.ui.requestRender(); + } else { this.ctx.showStatus("No text in clipboard to paste raw"); } } catch { diff --git a/packages/coding-agent/src/modes/utils/hotkeys-markdown.ts b/packages/coding-agent/src/modes/utils/hotkeys-markdown.ts index 97a179e66..cd09a6b37 100644 --- a/packages/coding-agent/src/modes/utils/hotkeys-markdown.ts +++ b/packages/coding-agent/src/modes/utils/hotkeys-markdown.ts @@ -48,7 +48,7 @@ export function buildHotkeysMarkdown(bindings: HotkeysMarkdownBindings): string `| \`${appKey(bindings, "app.tools.expand")}\` | Toggle tool output expansion |`, `| \`${appKey(bindings, "app.thinking.toggle")}\` | Toggle thinking block visibility |`, `| \`${appKey(bindings, "app.editor.external")}\` | Edit message in external editor |`, - `| \`${appKey(bindings, "app.clipboard.pasteImage")}\` | Paste image from clipboard |`, + `| \`${appKey(bindings, "app.clipboard.pasteImage")}\` | Paste image or text from clipboard |`, `| \`${appKey(bindings, "app.stt.toggle")}\` | Toggle speech-to-text recording |`, "| `#` | Open prompt actions |", "| `/` | Slash commands |", diff --git a/packages/coding-agent/src/utils/clipboard.ts b/packages/coding-agent/src/utils/clipboard.ts index 5ea555ad1..d882af5f3 100644 --- a/packages/coding-agent/src/utils/clipboard.ts +++ b/packages/coding-agent/src/utils/clipboard.ts @@ -173,6 +173,22 @@ export async function readTextFromClipboard(): Promise { if (process.env.TERMUX_VERSION) { return execSync("termux-clipboard-get", { encoding: "utf8", timeout: 2000 }).toString(); } + if (isWsl()) { + try { + // Reach the Windows clipboard through host PowerShell, mirroring + // readImageFromClipboard: WSLg's wl-paste only works when + // wl-clipboard happens to be installed in the distro, while + // powershell.exe is always reachable over WSL interop. + return execSync( + 'powershell.exe -NoProfile -NonInteractive -Command "[Console]::OutputEncoding=[Text.Encoding]::UTF8; [Console]::Out.Write([string](Get-Clipboard -Raw))"', + { encoding: "utf8", timeout: 5000 }, + ) + .toString() + .replaceAll("\r\n", "\n"); + } catch { + // Fall through to the wl-paste/xclip paths below. + } + } const hasWaylandDisplay = Boolean(process.env.WAYLAND_DISPLAY); const hasX11Display = Boolean(process.env.DISPLAY); if (hasWaylandDisplay) { diff --git a/packages/coding-agent/test/input-controller-smart-paste.test.ts b/packages/coding-agent/test/input-controller-smart-paste.test.ts new file mode 100644 index 000000000..0d8723a78 --- /dev/null +++ b/packages/coding-agent/test/input-controller-smart-paste.test.ts @@ -0,0 +1,118 @@ +/** + * Smart paste (#1628): `app.clipboard.pasteImage` must fall back to pasting + * clipboard text when no image is available, instead of dead-ending with + * "No image in clipboard". Hosts that deliver only this one chord (VS Code's + * integrated terminal forwarding Ctrl+V, Windows clipboard history via Win+V) + * rely on the fallback to cover both payload kinds. + */ + +import { describe, expect, it, vi } from "bun:test"; +import { InputController } from "@oh-my-pi/pi-coding-agent/modes/controllers/input-controller"; +import type { InteractiveModeContext } from "@oh-my-pi/pi-coding-agent/modes/types"; + +function createContext() { + const pasteText = vi.fn(); + const insertText = vi.fn(); + const requestRender = vi.fn(); + const showStatus = vi.fn(); + const ctx = { + editor: { pasteText, insertText } as unknown as InteractiveModeContext["editor"], + ui: { requestRender } as unknown as InteractiveModeContext["ui"], + showStatus, + } as unknown as InteractiveModeContext; + return { ctx, spies: { pasteText, insertText, requestRender, showStatus } }; +} + +describe("InputController.handleImagePaste smart-paste fallback", () => { + it("prefers the clipboard image and never consults text when an image is present", async () => { + const { ctx, spies } = createContext(); + const readText = vi.fn(async () => "text that must not be pasted"); + const controller = new InputController(ctx, { + // Unsupported/undecodable payload keeps the test off the full image + // pipeline; the contract under test is the read order, and that an + // image failure must NOT silently degrade into a text paste. + readImage: async () => ({ data: Buffer.from("not an image"), mimeType: "image/tiff" }), + readText, + }); + + const result = await controller.handleImagePaste(); + + expect(result).toBe(false); + expect(readText).not.toHaveBeenCalled(); + expect(spies.pasteText).not.toHaveBeenCalled(); + expect(spies.showStatus).toHaveBeenCalledWith("Unsupported clipboard image format: image/tiff"); + }); + + it("attaches nothing and pastes clipboard text when no image is present", async () => { + const { ctx, spies } = createContext(); + const controller = new InputController(ctx, { + readImage: async () => null, + readText: async () => "copied text\nsecond line", + }); + + const result = await controller.handleImagePaste(); + + expect(result).toBe(true); + expect(spies.pasteText).toHaveBeenCalledWith("copied text\nsecond line"); + expect(spies.requestRender).toHaveBeenCalled(); + expect(spies.showStatus).not.toHaveBeenCalled(); + }); + + it("reports an empty clipboard when neither image nor text is available", async () => { + const { ctx, spies } = createContext(); + const controller = new InputController(ctx, { + readImage: async () => null, + readText: async () => "", + }); + + const result = await controller.handleImagePaste(); + + expect(result).toBe(false); + expect(spies.pasteText).not.toHaveBeenCalled(); + expect(spies.showStatus).toHaveBeenCalledWith("Clipboard is empty"); + }); + + it("surfaces a read failure without pasting", async () => { + const { ctx, spies } = createContext(); + const controller = new InputController(ctx, { + readImage: async () => { + throw new Error("clipboard unavailable"); + }, + readText: async () => "should never be used", + }); + + const result = await controller.handleImagePaste(); + + expect(result).toBe(false); + expect(spies.pasteText).not.toHaveBeenCalled(); + expect(spies.showStatus).toHaveBeenCalledWith("Failed to read clipboard"); + }); +}); + +describe("InputController.handleClipboardTextRawPaste", () => { + it("inserts clipboard text verbatim", async () => { + const { ctx, spies } = createContext(); + const controller = new InputController(ctx, { + readImage: async () => null, + readText: async () => "raw $TEXT", + }); + + await controller.handleClipboardTextRawPaste(); + + expect(spies.insertText).toHaveBeenCalledWith("raw $TEXT"); + expect(spies.showStatus).not.toHaveBeenCalled(); + }); + + it("shows the empty-clipboard status only when there is no text", async () => { + const { ctx, spies } = createContext(); + const controller = new InputController(ctx, { + readImage: async () => null, + readText: async () => "", + }); + + await controller.handleClipboardTextRawPaste(); + + expect(spies.insertText).not.toHaveBeenCalled(); + expect(spies.showStatus).toHaveBeenCalledWith("No text in clipboard to paste raw"); + }); +}); From 5cd9c016d1c838eeaca5c0534e368c25d58be6e8 Mon Sep 17 00:00:00 2001 From: HugoAir Dev Date: Wed, 10 Jun 2026 17:59:57 +0900 Subject: [PATCH 2/3] fix(coding-agent): spawn the WSL text clipboard read async and route the text fallback by focus Address review notes on #2248: - readTextFromClipboard's WSL branch now mirrors readImageViaPowerShell (Bun.spawn + kill timer sharing POWERSHELL_TIMEOUT_MS) instead of execSync, so a cold powershell.exe start cannot block the TUI event loop on the first smart-paste miss. - The smart-paste text fallback routes through ui.getFocused() + hasPasteText like the enhanced-paste text path, so the payload lands in focused modal Input prompts instead of the hidden main editor (#2127 contract). Covered by a new routing test. --- .../src/modes/controllers/input-controller.ts | 6 +- packages/coding-agent/src/utils/clipboard.ts | 64 +++++++++++++++---- .../test/input-controller-smart-paste.test.ts | 19 +++++- 3 files changed, 72 insertions(+), 17 deletions(-) diff --git a/packages/coding-agent/src/modes/controllers/input-controller.ts b/packages/coding-agent/src/modes/controllers/input-controller.ts index b4e134d6f..348644375 100644 --- a/packages/coding-agent/src/modes/controllers/input-controller.ts +++ b/packages/coding-agent/src/modes/controllers/input-controller.ts @@ -840,7 +840,11 @@ export class InputController { this.ctx.showStatus("Clipboard is empty"); return false; } - this.ctx.editor.pasteText(text); + // Route to the focused component when it accepts pastes (modal + // Input prompts), matching the enhanced-paste text path (#2127). + const focused = this.ctx.ui.getFocused(); + const target = focused && focused !== this.ctx.editor && hasPasteText(focused) ? focused : this.ctx.editor; + target.pasteText(text); this.ctx.ui.requestRender(); return true; } diff --git a/packages/coding-agent/src/utils/clipboard.ts b/packages/coding-agent/src/utils/clipboard.ts index d882af5f3..2d07153bf 100644 --- a/packages/coding-agent/src/utils/clipboard.ts +++ b/packages/coding-agent/src/utils/clipboard.ts @@ -125,6 +125,53 @@ async function readImageViaPowerShell(): Promise { } } +// PowerShell one-liner that emits the clipboard text verbatim on stdout, or +// nothing when the clipboard holds no text. `[Console]::Out.Write` avoids the +// trailing newline Write-Output would add; output encoding is forced to UTF-8 +// so non-ASCII text survives the interop boundary regardless of console +// codepage. +const POWERSHELL_TEXT_SCRIPT = ` +$ErrorActionPreference = 'Stop' +[Console]::OutputEncoding = [Text.Encoding]::UTF8 +[Console]::Out.Write([string](Get-Clipboard -Raw)) +`; + +/** + * Read clipboard text through the Windows host's PowerShell. + * + * Same rationale as `readImageViaPowerShell`: WSLg's Wayland clipboard only + * works when `wl-clipboard` happens to be installed in the distro, while + * `powershell.exe` is always reachable over WSL interop. Spawned async so a + * cold PowerShell start cannot block the TUI event loop. + * + * Returns null when the bridge fails (caller falls through to wl-paste/xclip); + * an empty string is a successful "no text on the clipboard" read. + */ +async function readTextViaPowerShell(): Promise { + try { + const proc = Bun.spawn(["powershell.exe", "-NoProfile", "-NonInteractive", "-Command", POWERSHELL_TEXT_SCRIPT], { + stdout: "pipe", + stderr: "ignore", + stdin: "ignore", + }); + const timer = setTimeout(() => proc.kill(), POWERSHELL_TIMEOUT_MS); + let stdout = ""; + try { + stdout = await new Response(proc.stdout).text(); + await proc.exited; + } catch (err) { + logger.warn("clipboard: powershell text read failed", { error: String(err) }); + return null; + } finally { + clearTimeout(timer); + } + if (proc.exitCode !== 0) return null; + return stdout.replaceAll("\r\n", "\n"); + } catch { + return null; + } +} + /** * Read an image from the system clipboard. * @@ -174,20 +221,9 @@ export async function readTextFromClipboard(): Promise { return execSync("termux-clipboard-get", { encoding: "utf8", timeout: 2000 }).toString(); } if (isWsl()) { - try { - // Reach the Windows clipboard through host PowerShell, mirroring - // readImageFromClipboard: WSLg's wl-paste only works when - // wl-clipboard happens to be installed in the distro, while - // powershell.exe is always reachable over WSL interop. - return execSync( - 'powershell.exe -NoProfile -NonInteractive -Command "[Console]::OutputEncoding=[Text.Encoding]::UTF8; [Console]::Out.Write([string](Get-Clipboard -Raw))"', - { encoding: "utf8", timeout: 5000 }, - ) - .toString() - .replaceAll("\r\n", "\n"); - } catch { - // Fall through to the wl-paste/xclip paths below. - } + const text = await readTextViaPowerShell(); + if (text !== null) return text; + // Bridge failed — fall through to the wl-paste/xclip paths below. } const hasWaylandDisplay = Boolean(process.env.WAYLAND_DISPLAY); const hasX11Display = Boolean(process.env.DISPLAY); diff --git a/packages/coding-agent/test/input-controller-smart-paste.test.ts b/packages/coding-agent/test/input-controller-smart-paste.test.ts index 0d8723a78..51abebcdf 100644 --- a/packages/coding-agent/test/input-controller-smart-paste.test.ts +++ b/packages/coding-agent/test/input-controller-smart-paste.test.ts @@ -10,14 +10,14 @@ import { describe, expect, it, vi } from "bun:test"; import { InputController } from "@oh-my-pi/pi-coding-agent/modes/controllers/input-controller"; import type { InteractiveModeContext } from "@oh-my-pi/pi-coding-agent/modes/types"; -function createContext() { +function createContext(options?: { focused?: { pasteText(text: string): void } }) { const pasteText = vi.fn(); const insertText = vi.fn(); const requestRender = vi.fn(); const showStatus = vi.fn(); const ctx = { editor: { pasteText, insertText } as unknown as InteractiveModeContext["editor"], - ui: { requestRender } as unknown as InteractiveModeContext["ui"], + ui: { requestRender, getFocused: () => options?.focused ?? null } as unknown as InteractiveModeContext["ui"], showStatus, } as unknown as InteractiveModeContext; return { ctx, spies: { pasteText, insertText, requestRender, showStatus } }; @@ -58,6 +58,21 @@ describe("InputController.handleImagePaste smart-paste fallback", () => { expect(spies.showStatus).not.toHaveBeenCalled(); }); + it("routes the text fallback to a focused paste-capable component (#2127 contract)", async () => { + const focusedPasteText = vi.fn(); + const { ctx, spies } = createContext({ focused: { pasteText: focusedPasteText } }); + const controller = new InputController(ctx, { + readImage: async () => null, + readText: async () => "api-key-123", + }); + + const result = await controller.handleImagePaste(); + + expect(result).toBe(true); + expect(focusedPasteText).toHaveBeenCalledWith("api-key-123"); + expect(spies.pasteText).not.toHaveBeenCalled(); + }); + it("reports an empty clipboard when neither image nor text is available", async () => { const { ctx, spies } = createContext(); const controller = new InputController(ctx, { From f1dc1e9d767f7bb5d963d45be21bdc11561e5734 Mon Sep 17 00:00:00 2001 From: HugoAir Dev Date: Wed, 10 Jun 2026 18:08:16 +0900 Subject: [PATCH 3/3] fix(coding-agent): route native win32 clipboard text reads through the shared PowerShell bridge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Self-review follow-up to 5cd9c01: the smart-paste fallback promotes readTextFromClipboard's win32 branch to a primary path, and the legacy execSync Get-Clipboard shell-out there had the same two defects the review flagged on the WSL branch — it blocks the event loop and, worse, reads through the console codepage so non-ASCII clipboard text arrives mangled on non-UTF-8 Windows locales. Native win32 now shares readTextViaPowerShell (UTF-8-forced, -Raw, async, CRLF-normalized). --- packages/coding-agent/src/utils/clipboard.ts | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/coding-agent/src/utils/clipboard.ts b/packages/coding-agent/src/utils/clipboard.ts index 2d07153bf..3d9f6080d 100644 --- a/packages/coding-agent/src/utils/clipboard.ts +++ b/packages/coding-agent/src/utils/clipboard.ts @@ -137,15 +137,18 @@ $ErrorActionPreference = 'Stop' `; /** - * Read clipboard text through the Windows host's PowerShell. + * Read clipboard text through Windows PowerShell — native win32 or the WSL + * host over interop. * - * Same rationale as `readImageViaPowerShell`: WSLg's Wayland clipboard only - * works when `wl-clipboard` happens to be installed in the distro, while - * `powershell.exe` is always reachable over WSL interop. Spawned async so a - * cold PowerShell start cannot block the TUI event loop. + * Same rationale as `readImageViaPowerShell`: under WSL, the WSLg Wayland + * clipboard only works when `wl-clipboard` happens to be installed in the + * distro, while `powershell.exe` is always reachable. Forcing UTF-8 output + * encoding keeps non-ASCII text intact regardless of the console codepage + * (the legacy win32 `Get-Clipboard` shell-out mangled it), and `Bun.spawn` + * keeps a cold PowerShell start off the TUI event loop. * - * Returns null when the bridge fails (caller falls through to wl-paste/xclip); - * an empty string is a successful "no text on the clipboard" read. + * Returns null when the bridge fails (WSL callers fall through to + * wl-paste/xclip); an empty string is a successful "no text" read. */ async function readTextViaPowerShell(): Promise { try { @@ -212,10 +215,7 @@ export async function readTextFromClipboard(): Promise { return execSync("pbpaste", { encoding: "utf8", timeout: 2000 }).toString(); } if (p === "win32") { - return execSync('powershell.exe -NoProfile -Command "Get-Clipboard"', { - encoding: "utf8", - timeout: 2000, - }).toString(); + return (await readTextViaPowerShell()) ?? ""; } if (process.env.TERMUX_VERSION) { return execSync("termux-clipboard-get", { encoding: "utf8", timeout: 2000 }).toString();