Files
oh-my-pi/packages/coding-agent/src/utils/clipboard.ts
T
can1357 9b78a6fa85 refactor(natives): migrated native bindings to NAPI-RS with auto-generated types
- Migrated native bindings from TypeScript wrappers to NAPI-RS generated modules with auto-generated type definitions and runtime enums.
- Replaced chunk tree API with stateful ChunkState class supporting render, edit, and resolve operations with improved error handling.
- Converted callback signatures to error-first pattern (error, result) for shell, PTY, glob, and grep operations.
- Introduced type-safe enums for MacOSAppearance, GrepOutputMode, KeyEventType, ImageFormat, and AstMatchStrictness replacing string literals.
- Refactored chunk tree implementation with dedicated modules for edit, indent, resolve, and state management with comprehensive validation.
- Moved clipboard utilities from native bindings to coding-agent package with improved OSC 52 and Termux compatibility.
2026-04-07 02:00:59 +02:00

81 lines
2.4 KiB
TypeScript

import { execSync } from "node:child_process";
import type { ClipboardImage } from "@oh-my-pi/pi-natives";
import * as native from "@oh-my-pi/pi-natives";
const hasDisplay = process.platform !== "linux" || Boolean(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
/**
* Copy text to the system clipboard.
*
* Emits OSC 52 first when running in a real terminal (works over SSH/mosh),
* then attempts native clipboard copy as best-effort for local sessions.
* On Termux, tries `termux-clipboard-set` before native.
*
* @param text - UTF-8 text to place on the clipboard.
*/
export async function copyToClipboard(text: string): Promise<void> {
if (process.stdout.isTTY) {
const onError = (err: unknown) => {
process.stdout.off("error", onError);
// Prevent unhandled 'error' from crashing the process when stdout is a closed pipe.
if ((err as NodeJS.ErrnoException | null | undefined)?.code === "EPIPE") {
return;
}
};
try {
const encoded = Buffer.from(text).toString("base64");
const osc52 = `\x1b]52;c;${encoded}\x07`;
process.stdout.on("error", onError);
process.stdout.write(osc52, err => {
process.stdout.off("error", onError);
// If stdout is closed (e.g. piped to a process that exits early),
// ignore EPIPE and proceed with native clipboard best-effort.
if ((err as NodeJS.ErrnoException | null | undefined)?.code === "EPIPE") {
return;
}
});
} catch (err) {
process.stdout.off("error", onError);
if ((err as NodeJS.ErrnoException | null | undefined)?.code !== "EPIPE") {
// Ignore all write failures (OSC 52 is best-effort).
}
}
}
// Also try native tools (best effort for local sessions)
try {
if (process.env.TERMUX_VERSION) {
try {
execSync("termux-clipboard-set", { input: text, timeout: 5000 });
return;
} catch {
// Fall through to native
}
}
await native.copyToClipboard(text);
} catch {
// Ignore — clipboard copy is best-effort
}
}
/**
* Read an image from the system clipboard.
*
* Returns null on Termux (no image clipboard support) or when no display
* server is available (headless/SSH without forwarding).
*
* @returns PNG payload or null when no image is available.
*/
export async function readImageFromClipboard(): Promise<ClipboardImage | null> {
if (process.env.TERMUX_VERSION) {
return null;
}
if (!hasDisplay) {
return null;
}
return (await native.readImageFromClipboard()) ?? null;
}