* Pipe text into the system clipboard via the platform-native command. * Returns null on success, or a short message describing why it failed. * Never throws — clipboard is best-effort.
(text: string)
| 102 | * Never throws — clipboard is best-effort. |
| 103 | */ |
| 104 | async function copyToClipboard(text: string): Promise<string | null> { |
| 105 | const { spawn } = await import("node:child_process"); |
| 106 | const platform = process.platform; |
| 107 | const candidates: Array<[string, string[]]> = |
| 108 | platform === "darwin" |
| 109 | ? [["pbcopy", []]] |
| 110 | : platform === "win32" |
| 111 | ? [["clip", []]] |
| 112 | : [ |
| 113 | ["wl-copy", []], |
| 114 | ["xclip", ["-selection", "clipboard"]], |
| 115 | ["xsel", ["--clipboard", "--input"]], |
| 116 | ]; |
| 117 | for (const [cmd, args] of candidates) { |
| 118 | try { |
| 119 | const child = spawn(cmd, args, { stdio: ["pipe", "ignore", "ignore"] }); |
| 120 | const ok = await new Promise<boolean>((resolve) => { |
| 121 | let settled = false; |
| 122 | child.on("error", () => { |
| 123 | if (!settled) { |
| 124 | settled = true; |
| 125 | resolve(false); |
| 126 | } |
| 127 | }); |
| 128 | child.on("close", (code) => { |
| 129 | if (!settled) { |
| 130 | settled = true; |
| 131 | resolve(code === 0); |
| 132 | } |
| 133 | }); |
| 134 | child.stdin.end(text, "utf8"); |
| 135 | }); |
| 136 | if (ok) return null; |
| 137 | } catch { |
| 138 | // try next candidate |
| 139 | } |
| 140 | } |
| 141 | return platform === "darwin" |
| 142 | ? "pbcopy not available" |
| 143 | : platform === "win32" |
| 144 | ? "clip.exe not available" |
| 145 | : "no clipboard tool found (install xclip, xsel, or wl-clipboard)"; |
| 146 | } |
| 147 | |
| 148 | interface ShareEntry { |
| 149 | id: string; |