Agent skill
arboard
Cross-platform clipboard access for text and images
Install this agent skill to your Project
npx add-skill https://github.com/johnlindquist/script-kit-next/tree/main/.opencode/skill/arboard
SKILL.md
arboard
Cross-platform clipboard library by 1Password for reading and writing text and images. Provides a unified API across macOS, Windows, and Linux (X11/Wayland).
Crate: https://crates.io/crates/arboard Docs: https://docs.rs/arboard/latest/arboard/
Key Types
Clipboard
The main entry point. Create one instance to interact with the system clipboard.
use arboard::Clipboard;
let mut clipboard = Clipboard::new()?;
Important: Clipboard::new() returns Result<Clipboard, Error> - it can fail if:
- Clipboard is not supported on the platform/environment
- Another process is holding the clipboard (rare)
ImageData
Stores raw RGBA pixel data for clipboard images.
use arboard::ImageData;
use std::borrow::Cow;
let image = ImageData {
width: 100,
height: 100,
bytes: Cow::Owned(vec![0u8; 100 * 100 * 4]), // RGBA: 4 bytes per pixel
};
Pixel Format:
- Row-major order (left-to-right, top-to-bottom)
- 4 channels per pixel: Red, Green, Blue, Alpha
- Each channel is 1 byte (0-255)
- Total bytes =
width * height * 4
Error
Non-exhaustive enum with these variants:
ContentNotAvailable- Clipboard empty or wrong formatClipboardNotSupported- Platform/environment doesn't support clipboardClipboardOccupied- Another process is using the clipboardConversionFailure- Image/text couldn't be convertedUnknown { description }- Catch-all for other errors
Usage in script-kit-gpui
The codebase uses arboard for:
-
Clipboard History Monitoring (
src/clipboard_history/monitor.rs)- Polls clipboard every 50-500ms
- Captures both text and images
- Uses content hashing for deduplication
-
Copy to Clipboard (
src/clipboard_history/clipboard.rs)- Restores entries from history
- Handles both text and image content types
-
Selected Text Operations (
src/selected_text.rs)- Save/restore clipboard during text replacement
- Clipboard-based paste simulation
-
Text Injection (
src/text_injector.rs)- Snippet expansion via clipboard + Cmd+V
- Preserves original clipboard contents
Pattern: Save and Restore
script-kit-gpui consistently uses this pattern when temporarily using the clipboard:
use arboard::Clipboard;
use anyhow::{Context, Result};
fn paste_via_clipboard(text: &str) -> Result<()> {
let mut clipboard = Clipboard::new().context("Failed to access clipboard")?;
// 1. Save original contents
let original = clipboard.get_text().ok();
// 2. Set new content
clipboard.set_text(text).context("Failed to set clipboard")?;
// 3. Perform operation (e.g., simulate Cmd+V)
simulate_paste()?;
// 4. Restore original (best effort)
if let Some(original_text) = original {
let _ = clipboard.set_text(&original_text);
}
Ok(())
}
Text Operations
Get Text
let mut clipboard = Clipboard::new()?;
match clipboard.get_text() {
Ok(text) => println!("Got: {}", text),
Err(arboard::Error::ContentNotAvailable) => println!("Empty or not text"),
Err(e) => eprintln!("Error: {}", e),
}
Set Text
let mut clipboard = Clipboard::new()?;
clipboard.set_text("Hello, world!")?;
// Also accepts String, &String, Cow<str>
clipboard.set_text(String::from("owned"))?;
Set HTML
// HTML with plain text fallback
clipboard.set_html(
"<b>Bold</b> text",
Some("Bold text"), // Alt text for apps that don't support HTML
)?;
Image Operations
Get Image
let mut clipboard = Clipboard::new()?;
match clipboard.get_image() {
Ok(image) => {
println!("{}x{} image, {} bytes",
image.width, image.height, image.bytes.len());
}
Err(arboard::Error::ContentNotAvailable) => {
println!("No image on clipboard");
}
Err(e) => eprintln!("Error: {}", e),
}
Set Image
use arboard::{Clipboard, ImageData};
use std::borrow::Cow;
let mut clipboard = Clipboard::new()?;
// Create a 2x2 red/green/blue/white test image
let pixels = vec![
255, 0, 0, 255, // Red pixel
0, 255, 0, 255, // Green pixel
0, 0, 255, 255, // Blue pixel
255, 255, 255, 255, // White pixel
];
let image = ImageData {
width: 2,
height: 2,
bytes: Cow::Owned(pixels),
};
clipboard.set_image(image)?;
Convert to Owned
Use to_owned_img() when you need to store the image beyond the clipboard's lifetime:
let image = clipboard.get_image()?;
let owned: ImageData<'static> = image.to_owned_img();
// Now safe to use after clipboard is dropped
Platform Specifics
macOS
- Uses
NSPasteboardviaobjc2 - Images stored as
NSImageobjects - Most reliable platform for clipboard operations
- Efficient change detection via
changeCount
Windows
- Uses
clipboard-wincrate - Important: Clipboard is a global object, only one thread can open it at a time
- Parallel operations may fail with
ClipboardOccupied - Image formats:
CF_DIB,CF_BITMAP - Recommended: Avoid creating Clipboard objects on multiple threads
Linux (X11/Wayland)
- Critical: Clipboard content is "hosted" by the application
- When last
Clipboardinstance is dropped, content may become unavailable to other apps! - Use
SetExtLinuxtrait for persistence options:
use arboard::{Clipboard, SetExtLinux};
let mut clipboard = Clipboard::new()?;
clipboard.set()
.wait() // Keep clipboard available after app exits (forks background process)
.text("Persistent text")?;
- Primary vs Clipboard selections (X11):
LinuxClipboardKind::Clipboard- Ctrl+C/Ctrl+V (default)LinuxClipboardKind::Primary- Middle-click paste
Builder API
For advanced operations, use the builder pattern:
let mut clipboard = Clipboard::new()?;
// Get with options
let text = clipboard.get()
.text()?;
// Set with options
clipboard.set()
.text("content")?;
// Clear
clipboard.clear()?;
Integration with image Crate
script-kit-gpui converts between arboard::ImageData and the image crate:
use arboard::ImageData;
use image::RgbaImage;
use std::borrow::Cow;
// ImageData -> RgbaImage
fn to_rgba_image(img: &ImageData) -> Option<RgbaImage> {
RgbaImage::from_raw(
img.width as u32,
img.height as u32,
img.bytes.to_vec(),
)
}
// RgbaImage -> ImageData
fn from_rgba_image(rgba: &RgbaImage) -> ImageData<'static> {
ImageData {
width: rgba.width() as usize,
height: rgba.height() as usize,
bytes: Cow::Owned(rgba.as_raw().clone()),
}
}
Anti-patterns
Don't hold Clipboard across await points
// BAD: Clipboard held during async operation
let mut clipboard = Clipboard::new()?;
let text = clipboard.get_text()?;
some_async_operation().await; // Other processes blocked!
clipboard.set_text(&modified)?;
// GOOD: Drop clipboard before await
let text = {
let mut clipboard = Clipboard::new()?;
clipboard.get_text()?
};
some_async_operation().await;
{
let mut clipboard = Clipboard::new()?;
clipboard.set_text(&modified)?;
}
Don't assume content type
// BAD: Panics if clipboard has image
let text = clipboard.get_text().unwrap();
// GOOD: Handle both content types
if let Ok(text) = clipboard.get_text() {
handle_text(&text);
} else if let Ok(image) = clipboard.get_image() {
handle_image(&image);
}
Don't poll clipboard content directly on Linux
// BAD: Expensive on Linux (reads full payload)
loop {
let content = clipboard.get_text();
thread::sleep(Duration::from_millis(100));
}
// GOOD: Use OS-level change detection when available
// (macOS: NSPasteboard.changeCount, Windows: clipboard sequence number)
Don't forget to handle errors
// BAD: Silent failure
let _ = clipboard.set_text("text");
// GOOD: Log or propagate errors
clipboard.set_text("text").context("Failed to set clipboard")?;
Don't create ImageData with wrong byte count
// BAD: Bytes don't match dimensions
let image = ImageData {
width: 100,
height: 100,
bytes: Cow::Owned(vec![0u8; 1000]), // Should be 40000!
};
// GOOD: Validate or compute correctly
let width = 100;
let height = 100;
let bytes = vec![0u8; width * height * 4];
let image = ImageData { width, height, bytes: Cow::Owned(bytes) };
Error Handling Best Practices
use arboard::{Clipboard, Error};
use anyhow::{Context, Result};
fn clipboard_operation() -> Result<String> {
let mut clipboard = Clipboard::new()
.context("Failed to access clipboard")?;
match clipboard.get_text() {
Ok(text) => Ok(text),
Err(Error::ContentNotAvailable) => {
// Empty clipboard is often expected
Ok(String::new())
}
Err(Error::ClipboardOccupied) => {
// Retry logic might help
anyhow::bail!("Clipboard busy, try again")
}
Err(e) => {
anyhow::bail!("Clipboard error: {}", e)
}
}
}
Feature Flags
image-data(default) - Enable image supportwayland-data-control- Use wl-clipboard protocol on Wayland
[dependencies]
arboard = { version = "3.6", default-features = false } # Text only
arboard = { version = "3.6", features = ["image-data"] } # With images
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
Generate Component Documentation
Based on existing docs styles and specific API implementations, and referencing same name stories, generate comprehensive documentation for the new component.
Generate Component Story
Generate a comprehensive story for a new component for as example.
new-component
How to write a new component of GPUI Component.
troubleshooting
Diagnose and fix common Script Kit issues. Use when the user reports bugs, crashes, missing features, or unexpected behavior in Script Kit GPUI.
script-authoring
Create and manage TypeScript scripts for Script Kit. Use when the user wants to write a new script, edit an existing script, or understand Script Kit's SDK and metadata system.
agents
Create mdflow-backed agent files for Script Kit. Use when the user wants to create AI agents, configure agent backends (Claude, Gemini, Codex), or manage agent metadata.
Didn't find tool you were looking for?