@oh-my-pi/pi-coding-agent 9.7.0 → 9.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,248 +0,0 @@
1
- import * as fs from "node:fs/promises";
2
- import * as os from "node:os";
3
- import { $ } from "bun";
4
- import { nanoid } from "nanoid";
5
-
6
- const PREFERRED_IMAGE_MIME_TYPES = ["image/png", "image/jpeg", "image/webp", "image/gif"] as const;
7
-
8
- function isWaylandSession(env: NodeJS.ProcessEnv = process.env): boolean {
9
- return Boolean(env.WAYLAND_DISPLAY) || env.XDG_SESSION_TYPE === "wayland";
10
- }
11
-
12
- function baseMimeType(mimeType: string): string {
13
- const base = mimeType.split(";")[0]?.trim().toLowerCase() ?? mimeType.toLowerCase();
14
- return base === "image/jpg" ? "image/jpeg" : base;
15
- }
16
-
17
- function selectPreferredImageMimeType(mimeTypes: string[]): string | null {
18
- const normalized = mimeTypes
19
- .map(t => t.trim())
20
- .filter(Boolean)
21
- .map(t => ({ raw: t, base: baseMimeType(t) }));
22
-
23
- for (const preferred of PREFERRED_IMAGE_MIME_TYPES) {
24
- const match = normalized.find(t => t.base === preferred);
25
- if (match) {
26
- return match.raw;
27
- }
28
- }
29
-
30
- const anyImage = normalized.find(t => t.base.startsWith("image/"));
31
- return anyImage?.raw ?? null;
32
- }
33
-
34
- export async function copyToClipboard(text: string): Promise<void> {
35
- const p = os.platform();
36
- const timeout = 5000;
37
-
38
- try {
39
- if (p === "darwin") {
40
- await Bun.spawn(["pbcopy"], { stdin: Buffer.from(text), timeout, windowsHide: true }).exited;
41
- } else if (p === "win32") {
42
- await Bun.spawn(["clip"], { stdin: Buffer.from(text), timeout, windowsHide: true }).exited;
43
- } else {
44
- const wayland = isWaylandSession();
45
- if (wayland) {
46
- const wlCopyPath = Bun.which("wl-copy");
47
- if (wlCopyPath) {
48
- // Fire-and-forget: wl-copy may not exit promptly, so we unref to avoid blocking
49
- void Bun.spawn([wlCopyPath], { stdin: Buffer.from(text), timeout, windowsHide: true }).unref();
50
- return;
51
- }
52
- }
53
-
54
- // Linux - try xclip first, fall back to xsel
55
- try {
56
- await Bun.spawn(["xclip", "-selection", "clipboard"], {
57
- stdin: Buffer.from(text),
58
- timeout,
59
- windowsHide: true,
60
- }).exited;
61
- } catch {
62
- await Bun.spawn(["xsel", "--clipboard", "--input"], {
63
- stdin: Buffer.from(text),
64
- timeout,
65
- windowsHide: true,
66
- }).exited;
67
- }
68
- }
69
- } catch (error) {
70
- const msg = error instanceof Error ? error.message : String(error);
71
- if (p === "linux") {
72
- const tools = isWaylandSession() ? "wl-copy, xclip, or xsel" : "xclip or xsel";
73
- throw new Error(`Failed to copy to clipboard. Install ${tools}: ${msg}`);
74
- }
75
- throw new Error(`Failed to copy to clipboard: ${msg}`);
76
- }
77
- }
78
-
79
- export interface ClipboardImage {
80
- data: string; // base64 encoded
81
- mimeType: string;
82
- }
83
-
84
- /**
85
- * Read image from system clipboard if available.
86
- * Returns null if no image is in clipboard or clipboard access fails.
87
- *
88
- * Supported platforms:
89
- * - Linux: requires wl-paste (Wayland) or xclip (X11)
90
- * - macOS: uses osascript + pbpaste
91
- * - Windows: uses PowerShell
92
- */
93
- export async function readImageFromClipboard(): Promise<ClipboardImage | null> {
94
- const p = os.platform();
95
- const timeout = 3000;
96
- let promise: Promise<ClipboardImage | null>;
97
- switch (p) {
98
- case "linux":
99
- promise = readImageLinux();
100
- break;
101
- case "darwin":
102
- promise = readImageMacOS();
103
- break;
104
- case "win32":
105
- promise = readImageWindows();
106
- break;
107
- default:
108
- return null;
109
- }
110
- return Promise.race([promise, Bun.sleep(timeout).then(() => null)]);
111
- }
112
-
113
- type ClipboardReadResult =
114
- | { status: "found"; image: ClipboardImage }
115
- | { status: "empty" } // Tools ran successfully, no image in clipboard
116
- | { status: "unavailable" }; // Tools not found or failed to run
117
-
118
- async function readImageLinux(): Promise<ClipboardImage | null> {
119
- const wayland = isWaylandSession();
120
- if (wayland) {
121
- const result = await readImageWayland();
122
- if (result.status === "found") return result.image;
123
- if (result.status === "empty") return null; // Don't fall back to X11 if Wayland worked
124
- }
125
-
126
- const result = await readImageX11();
127
- return result.status === "found" ? result.image : null;
128
- }
129
-
130
- async function readImageWayland(): Promise<ClipboardReadResult> {
131
- const types = await $`wl-paste --list-types`.quiet().text();
132
- if (!types) return { status: "unavailable" }; // Command failed
133
-
134
- const typeList = types
135
- .split(/\r?\n/)
136
- .map(t => t.trim())
137
- .filter(Boolean);
138
-
139
- const selectedType = selectPreferredImageMimeType(typeList);
140
- if (!selectedType) return { status: "empty" }; // No image types available
141
-
142
- const imageData = await $`wl-paste --type ${selectedType} --no-newline`.quiet().arrayBuffer();
143
- if (!imageData || imageData.byteLength === 0) return { status: "empty" };
144
-
145
- return {
146
- status: "found",
147
- image: {
148
- data: Buffer.from(imageData).toString("base64"),
149
- mimeType: baseMimeType(selectedType),
150
- },
151
- };
152
- }
153
-
154
- async function readImageX11(): Promise<ClipboardReadResult> {
155
- const targets = await $`xclip -selection clipboard -t TARGETS -o`.quiet().text();
156
- if (!targets) return { status: "unavailable" }; // xclip failed (no X server?)
157
-
158
- const candidateTypes = targets
159
- .split(/\r?\n/)
160
- .map(t => t.trim())
161
- .filter(Boolean);
162
-
163
- const selectedType = selectPreferredImageMimeType(candidateTypes);
164
- if (!selectedType) return { status: "empty" }; // Clipboard has no image types
165
-
166
- const imageData = await $`xclip -selection clipboard -t ${selectedType} -o`.quiet().arrayBuffer();
167
- if (!imageData || imageData.byteLength === 0) return { status: "empty" };
168
-
169
- return {
170
- status: "found",
171
- image: {
172
- data: Buffer.from(imageData).toString("base64"),
173
- mimeType: baseMimeType(selectedType),
174
- },
175
- };
176
- }
177
-
178
- async function readImageMacOS(): Promise<ClipboardImage | null> {
179
- // Use osascript to check clipboard class and read PNG data
180
- // First check if clipboard has image data
181
- const checkScript = `
182
- try
183
- clipboard info for «class PNGf»
184
- return "png"
185
- on error
186
- try
187
- clipboard info for «class JPEG»
188
- return "jpeg"
189
- on error
190
- return "none"
191
- end try
192
- end try
193
- `;
194
-
195
- const checkResult = await $`osascript -e ${checkScript}`.quiet().text();
196
- const imageType = checkResult.trim();
197
- if (imageType === "none") return null;
198
-
199
- // Read the actual image data using a temp file approach
200
- // osascript can't output binary directly, so we write to a temp file
201
- const tempFile = `/tmp/omp-clipboard-${nanoid()}.${imageType === "png" ? "png" : "jpg"}`;
202
- const clipboardClass = imageType === "png" ? "«class PNGf»" : "«class JPEG»";
203
-
204
- const readScript = `
205
- set imageData to the clipboard as ${clipboardClass}
206
- set filePath to POSIX file "${tempFile}"
207
- set fileRef to open for access filePath with write permission
208
- write imageData to fileRef
209
- close access fileRef
210
- `;
211
-
212
- await $`osascript -e ${readScript}`.quiet().text();
213
-
214
- try {
215
- const file = Bun.file(tempFile);
216
- if (await file.exists()) {
217
- const buffer = await file.bytes();
218
- await fs.unlink(tempFile).catch(() => {});
219
-
220
- if (buffer.length > 0) {
221
- return {
222
- data: Buffer.from(buffer).toString("base64"),
223
- mimeType: imageType === "png" ? "image/png" : "image/jpeg",
224
- };
225
- }
226
- }
227
- } catch {
228
- // File read failed
229
- }
230
-
231
- return null;
232
- }
233
-
234
- async function readImageWindows(): Promise<ClipboardImage | null> {
235
- // PowerShell script to read image from clipboard as base64
236
- const script = `
237
- Add-Type -AssemblyName System.Windows.Forms
238
- $clipboard = [System.Windows.Forms.Clipboard]::GetImage()
239
- if ($clipboard -ne $null) {
240
- $ms = New-Object System.IO.MemoryStream
241
- $clipboard.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png)
242
- [Convert]::ToBase64String($ms.ToArray())
243
- }
244
- `;
245
-
246
- const result = await $`powershell -NoProfile -Command ${script}`.quiet().text();
247
- return result ? { data: result, mimeType: "image/png" } : null;
248
- }