pi-better-btw-plus 1.0.0 → 1.0.2
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.
- package/banner.png +0 -0
- package/package.json +1 -1
- package/srcs/clipboard-read.ts +94 -7
package/banner.png
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-better-btw-plus",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "pi extension: /btw side-chat overlay — maintained fork of @yceachan/pi-better-btw (+ right-click copy/paste, fork model switch, turn-level retry)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
package/srcs/clipboard-read.ts
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* is self-built because `readClipboardText` is not re-exported from the
|
|
7
7
|
* package entry. Each platform maps to a primary channel:
|
|
8
8
|
*
|
|
9
|
-
* - win32 →
|
|
10
|
-
*
|
|
11
|
-
* - darwin → `pbpaste`, then
|
|
9
|
+
* - win32 → native addon `getText` (pi's own clipboard read), then
|
|
10
|
+
* PowerShell `Get-Clipboard -Raw`, then an OSC 52 query;
|
|
11
|
+
* - darwin → native addon `getText`, then `pbpaste`, then OSC 52 query;
|
|
12
12
|
* - linux → OSC 52 query (`\x1b]52;c;?\x07`, read the reply).
|
|
13
13
|
*
|
|
14
14
|
* Channels are injected functions, so tests mock success / fallback /
|
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { spawnSync } from "node:child_process";
|
|
21
21
|
|
|
22
|
+
import { createRequire } from "node:module";
|
|
23
|
+
import { dirname, join } from "node:path";
|
|
24
|
+
import { pathToFileURL } from "node:url";
|
|
22
25
|
/**
|
|
23
26
|
* Outcome of reading the system clipboard: text, an explicitly empty
|
|
24
27
|
* clipboard (a channel ran and saw nothing), or no usable channel at all.
|
|
@@ -159,6 +162,87 @@ function makeCommandChannel(
|
|
|
159
162
|
};
|
|
160
163
|
}
|
|
161
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Native clipboard addon (win32 / darwin primary read channel): the
|
|
167
|
+
* `@mariozechner/clipboard` napi addon pi itself uses for reads and writes.
|
|
168
|
+
* Its `getText` is a sub-millisecond, non-blocking call — vs. the PowerShell
|
|
169
|
+
* `Get-Clipboard` subprocess, which cold-starts ~1s and blocks the event loop
|
|
170
|
+
* with `spawnSync` (the source of the right-click-paste lag). Resolution
|
|
171
|
+
* mirrors pi's `clipboard-native` loader: the package's own `require` first,
|
|
172
|
+
* then a `require` rooted at the pi executable's directory.
|
|
173
|
+
*
|
|
174
|
+
* The addon is an optional dependency provided at runtime by the pi host
|
|
175
|
+
* environment — deliberately not listed in package.json to avoid
|
|
176
|
+
* cross-platform prebuild dependency issues. When it is missing or fails to
|
|
177
|
+
* load, the channel reports `unavailable` and the fallback chain
|
|
178
|
+
* (PowerShell / pbpaste → OSC 52) takes over.
|
|
179
|
+
*/
|
|
180
|
+
export interface NativeClipboardAddon {
|
|
181
|
+
/** Resolve the clipboard's plain text; null when it holds no text. */
|
|
182
|
+
getText(): Promise<string | null>;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let cachedNativeAddon: NativeClipboardAddon | null | undefined;
|
|
186
|
+
|
|
187
|
+
function loadNativeAddon(): NativeClipboardAddon | null {
|
|
188
|
+
if (cachedNativeAddon !== undefined) return cachedNativeAddon;
|
|
189
|
+
const moduleRequire = createRequire(import.meta.url);
|
|
190
|
+
const executableDirRequire = createRequire(
|
|
191
|
+
pathToFileURL(join(dirname(process.execPath), "package.json")).href,
|
|
192
|
+
);
|
|
193
|
+
cachedNativeAddon = null;
|
|
194
|
+
for (const requireClipboard of [moduleRequire, executableDirRequire]) {
|
|
195
|
+
try {
|
|
196
|
+
const addon = requireClipboard("@mariozechner/clipboard") as
|
|
197
|
+
| { getText?: () => Promise<string | null> }
|
|
198
|
+
| undefined;
|
|
199
|
+
if (addon && typeof addon.getText === "function") {
|
|
200
|
+
cachedNativeAddon = addon as NativeClipboardAddon;
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
} catch {
|
|
204
|
+
// Try the next resolution root.
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return cachedNativeAddon;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export interface NativeChannelOptions {
|
|
211
|
+
/** Native addon loader override (tests). Defaults to {@link loadNativeAddon}. */
|
|
212
|
+
loadNative?: () => NativeClipboardAddon | null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Native channel: read plain text via the `@mariozechner/clipboard` addon.
|
|
217
|
+
* A missing/unloadable addon reports `unavailable`; a loaded addon that sees
|
|
218
|
+
* no text reports `empty` (definitive, stops the cascade).
|
|
219
|
+
*/
|
|
220
|
+
export function makeNativeChannel(
|
|
221
|
+
options: NativeChannelOptions = {},
|
|
222
|
+
): ClipboardReadChannel {
|
|
223
|
+
const load = options.loadNative ?? loadNativeAddon;
|
|
224
|
+
return {
|
|
225
|
+
name: "native",
|
|
226
|
+
async read() {
|
|
227
|
+
let addon: NativeClipboardAddon | null;
|
|
228
|
+
try {
|
|
229
|
+
addon = load();
|
|
230
|
+
} catch {
|
|
231
|
+
addon = null;
|
|
232
|
+
}
|
|
233
|
+
if (!addon) return { ok: false, reason: "unavailable" };
|
|
234
|
+
try {
|
|
235
|
+
const text = await addon.getText();
|
|
236
|
+
return text && text.length > 0
|
|
237
|
+
? { ok: true, text }
|
|
238
|
+
: { ok: false, reason: "empty" };
|
|
239
|
+
} catch {
|
|
240
|
+
return { ok: false, reason: "unavailable" };
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
162
246
|
/**
|
|
163
247
|
* PowerShell channel: `powershell.exe -NoProfile -Command "Get-Clipboard
|
|
164
248
|
* -Raw"`, mirroring pi's clipboard-image PowerShell channel family. The
|
|
@@ -298,13 +382,14 @@ export interface PlatformChannelsOptions {
|
|
|
298
382
|
exec?: ExecFn;
|
|
299
383
|
write?: (sequence: string) => void;
|
|
300
384
|
readReply?: (timeoutMs: number) => Promise<string | null>;
|
|
385
|
+
/** Native addon loader override (tests). Defaults to the real loader. */
|
|
386
|
+
loadNative?: () => NativeClipboardAddon | null;
|
|
301
387
|
}
|
|
302
|
-
|
|
303
388
|
/**
|
|
304
389
|
* The per-platform channel matrix (primary first, then fallbacks). win32 /
|
|
305
|
-
* darwin lead with
|
|
306
|
-
* linux (and unknown platforms) query OSC 52 directly —
|
|
307
|
-
* write-side cascade where OSC 52 is the universal last resort.
|
|
390
|
+
* darwin lead with the native addon, fall back to their platform tool, then
|
|
391
|
+
* to an OSC 52 query; linux (and unknown platforms) query OSC 52 directly —
|
|
392
|
+
* mirroring the write-side cascade where OSC 52 is the universal last resort.
|
|
308
393
|
*/
|
|
309
394
|
export function buildPlatformChannels(
|
|
310
395
|
options: PlatformChannelsOptions = {},
|
|
@@ -316,11 +401,13 @@ export function buildPlatformChannels(
|
|
|
316
401
|
switch (options.platform ?? process.platform) {
|
|
317
402
|
case "win32":
|
|
318
403
|
return [
|
|
404
|
+
makeNativeChannel({ loadNative: options.loadNative }),
|
|
319
405
|
makePowerShellChannel({ exec: options.exec, env: options.env }),
|
|
320
406
|
osc52,
|
|
321
407
|
];
|
|
322
408
|
case "darwin":
|
|
323
409
|
return [
|
|
410
|
+
makeNativeChannel({ loadNative: options.loadNative }),
|
|
324
411
|
makePbpasteChannel({ exec: options.exec, env: options.env }),
|
|
325
412
|
osc52,
|
|
326
413
|
];
|