cligentic
← back to catalog
cligentic block

copy-clipboard

Copy text to the system clipboard across macOS, Linux (X11 + Wayland), Windows, and WSL. Detects pbcopy, xclip, xsel, wl-copy, clip.exe automatically. Typed verdict, never throws.

Install
copy-clipboard demo
NPM dependencies

zero deps — pure TS

Block dependencies
Size

85 LOC

The problem

Every CLI that copies output to the clipboard ends up with a process.platform switch and a spawn call that fails silently on half the platforms it claims to support.

The copy-clipboard block detects the right clipboard backend automatically, handles WSL (routes to clip.exe), handles Wayland vs X11 on Linux, and returns a typed verdict instead of throwing.

Quickstart

import { copyToClipboard } from "@/cli/platform/copy-clipboard";
 
const result = await copyToClipboard("https://cligentic.railly.dev");
 
if (result.copied) {
  console.log("Copied to clipboard.");
} else {
  console.log("Copy this manually:", text);
}

Backend detection order

  • macOS: pbcopy (always present)
  • Windows: powershell Set-Clipboard
  • WSL: clip.exe (routes to Windows host clipboard)
  • Linux Wayland: wl-copy (if WAYLAND_DISPLAY is set)
  • Linux X11: xclip -selection clipboard, then xsel --clipboard --input (if DISPLAY is set)
  • Fallback: returns { copied: false, via: "manual" } with reason

Signature

export type CopyResult = {
  copied: boolean;
  via: "pbcopy" | "xclip" | "xsel" | "wl-copy" | "clip.exe" | "powershell" | "manual";
  reason?: string;
};
 
export type CopyOptions = {
  dryRun?: boolean;
};
 
export function copyToClipboard(
  text: string,
  options?: CopyOptions,
): Promise<CopyResult>;

Pair with other blocks

  • Use after json-mode's emit() to copy the result to clipboard
  • Use with notify-os to fire a "Copied!" notification after clipboard write
Source

The full file, 85 lines.

This is the exact TypeScript that lands in your project when you run the install command. Read it, copy it, edit it, own it. cligentic never touches it again.

src/cli/platform/copy-clipboard.ts
// cligentic block: copy-clipboard
//
// Copies text to the system clipboard across macOS, Linux (X11 + Wayland),
// Windows, and WSL. Returns a typed verdict instead of throwing.
//
// Usage:
//   import { copyToClipboard } from "./platform/copy-clipboard";
//
//   const result = await copyToClipboard("https://cligentic.railly.dev");
//   if (!result.copied) {
//     console.log("Copy this manually:", text);
//   }

import { execSync } from "node:child_process";
import { platform } from "node:os";
import { hasCommand, isWsl } from "./detect";

export type CopyResult = {
  copied: boolean;
  via: "pbcopy" | "xclip" | "xsel" | "wl-copy" | "clip.exe" | "powershell" | "manual";
  reason?: string;
};

export type CopyOptions = {
  dryRun?: boolean;
};

function tryExec(cmd: string, text: string): boolean {
  try {
    execSync(cmd, { input: text, stdio: ["pipe", "ignore", "ignore"], timeout: 5000 });
    return true;
  } catch {
    return false;
  }
}

export async function copyToClipboard(
  text: string,
  options: CopyOptions = {},
): Promise<CopyResult> {
  const { dryRun = false } = options;
  const os = platform();

  if (os === "darwin") {
    if (dryRun) return { copied: true, via: "pbcopy", reason: "would run: pbcopy" };
    if (tryExec("pbcopy", text)) return { copied: true, via: "pbcopy" };
    return { copied: false, via: "manual", reason: "pbcopy failed" };
  }

  if (os === "win32") {
    if (dryRun) return { copied: true, via: "powershell", reason: "would run: Set-Clipboard" };
    if (tryExec('powershell.exe -NoProfile -Command "Set-Clipboard -Value $input"', text)) {
      return { copied: true, via: "powershell" };
    }
    return { copied: false, via: "manual", reason: "powershell Set-Clipboard failed" };
  }

  if (os === "linux" && isWsl()) {
    if (hasCommand("clip.exe")) {
      if (dryRun) return { copied: true, via: "clip.exe", reason: "would run: clip.exe" };
      if (tryExec("clip.exe", text)) return { copied: true, via: "clip.exe" };
    }
    return { copied: false, via: "manual", reason: "WSL without clip.exe" };
  }

  // Linux: Wayland first, then X11
  if (process.env.WAYLAND_DISPLAY && hasCommand("wl-copy")) {
    if (dryRun) return { copied: true, via: "wl-copy", reason: "would run: wl-copy" };
    if (tryExec("wl-copy", text)) return { copied: true, via: "wl-copy" };
  }

  if (process.env.DISPLAY) {
    if (hasCommand("xclip")) {
      if (dryRun) return { copied: true, via: "xclip", reason: "would run: xclip" };
      if (tryExec("xclip -selection clipboard", text)) return { copied: true, via: "xclip" };
    }
    if (hasCommand("xsel")) {
      if (dryRun) return { copied: true, via: "xsel", reason: "would run: xsel" };
      if (tryExec("xsel --clipboard --input", text)) return { copied: true, via: "xsel" };
    }
  }

  return { copied: false, via: "manual", reason: "no clipboard backend found" };
}