@narumitw/pi-btw 0.55.4 → 0.56.1
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/README.md +125 -88
- package/dist/index.ts +159 -50
- package/dist/index.ts.map +2 -2
- package/package.json +5 -8
- package/src/btw.ts +31 -26
- package/src/fullscreen-ui.ts +130 -22
- package/src/menu.ts +34 -2
- package/src/settings.ts +16 -0
package/dist/index.ts
CHANGED
|
@@ -503,10 +503,13 @@ var FullscreenUiDisposedError = class extends Error {
|
|
|
503
503
|
this.name = "FullscreenUiDisposedError";
|
|
504
504
|
}
|
|
505
505
|
};
|
|
506
|
-
async function runBtwFullscreen(ctx, run, dependencies = {}) {
|
|
507
|
-
const createTui = dependencies.createTui ?? ((parent, theme) => createBtwFullscreenTui(
|
|
506
|
+
async function runBtwFullscreen(ctx, run, options = {}, dependencies = {}) {
|
|
507
|
+
const createTui = dependencies.createTui ?? ((parent, theme, keybindings, fullscreenOptions) => createBtwFullscreenTui(
|
|
508
508
|
parent,
|
|
509
509
|
theme,
|
|
510
|
+
keybindings,
|
|
511
|
+
fullscreenOptions.copyOnSelect ?? true,
|
|
512
|
+
dependencies.manualSelectionCopySupported ?? hasManualSelectionCopyApi(),
|
|
510
513
|
dependencies.openUrl ?? openUrlInBrowser,
|
|
511
514
|
dependencies.copyToClipboard ?? copyToHostClipboard
|
|
512
515
|
));
|
|
@@ -529,7 +532,8 @@ async function runBtwFullscreen(ctx, run, dependencies = {}) {
|
|
|
529
532
|
}
|
|
530
533
|
done(value);
|
|
531
534
|
},
|
|
532
|
-
createTui
|
|
535
|
+
createTui,
|
|
536
|
+
options
|
|
533
537
|
);
|
|
534
538
|
return host;
|
|
535
539
|
},
|
|
@@ -550,7 +554,7 @@ async function runBtwFullscreen(ctx, run, dependencies = {}) {
|
|
|
550
554
|
var btwInputListeners = /* @__PURE__ */ new WeakMap();
|
|
551
555
|
function dispatchBtwInput(listeners, data) {
|
|
552
556
|
let current = data;
|
|
553
|
-
for (const group of [listeners.beforeViewport, listeners.regular]) {
|
|
557
|
+
for (const group of [listeners.beforeAll, listeners.beforeViewport, listeners.regular]) {
|
|
554
558
|
for (const listener of group) {
|
|
555
559
|
const result = listener(current);
|
|
556
560
|
if (result?.consume) return result;
|
|
@@ -560,10 +564,14 @@ function dispatchBtwInput(listeners, data) {
|
|
|
560
564
|
return current === data ? void 0 : { data: current };
|
|
561
565
|
}
|
|
562
566
|
var BtwTuiAltScreen = class extends TuiAltScreen {
|
|
567
|
+
hasFocusedOverlay() {
|
|
568
|
+
return this.isOverlayFocused();
|
|
569
|
+
}
|
|
563
570
|
addInputListener(listener) {
|
|
564
571
|
let listeners = btwInputListeners.get(this);
|
|
565
572
|
if (!listeners) {
|
|
566
573
|
const registeredListeners = {
|
|
574
|
+
beforeAll: /* @__PURE__ */ new Set(),
|
|
567
575
|
beforeViewport: /* @__PURE__ */ new Set(),
|
|
568
576
|
regular: /* @__PURE__ */ new Set()
|
|
569
577
|
};
|
|
@@ -574,6 +582,12 @@ var BtwTuiAltScreen = class extends TuiAltScreen {
|
|
|
574
582
|
listeners.regular.add(listener);
|
|
575
583
|
return () => listeners.regular.delete(listener);
|
|
576
584
|
}
|
|
585
|
+
addInputListenerBeforeAll(listener) {
|
|
586
|
+
const listeners = btwInputListeners.get(this);
|
|
587
|
+
if (!listeners) return super.addInputListener(listener);
|
|
588
|
+
listeners.beforeAll.add(listener);
|
|
589
|
+
return () => listeners.beforeAll.delete(listener);
|
|
590
|
+
}
|
|
577
591
|
addInputListenerBeforeViewport(listener) {
|
|
578
592
|
const listeners = btwInputListeners.get(this);
|
|
579
593
|
if (!listeners) return super.addInputListener(listener);
|
|
@@ -586,26 +600,64 @@ var BtwTuiAltScreen = class extends TuiAltScreen {
|
|
|
586
600
|
super.removeInputListener(listener);
|
|
587
601
|
return;
|
|
588
602
|
}
|
|
603
|
+
listeners.beforeAll.delete(listener);
|
|
589
604
|
listeners.beforeViewport.delete(listener);
|
|
590
605
|
listeners.regular.delete(listener);
|
|
591
606
|
}
|
|
592
607
|
};
|
|
593
|
-
|
|
608
|
+
var BRACKETED_PASTE_START = "\x1B[200~";
|
|
609
|
+
var BRACKETED_PASTE_END = "\x1B[201~";
|
|
610
|
+
function hasManualSelectionCopyApi() {
|
|
611
|
+
return typeof TuiAltScreen.prototype.hasActiveSelection === "function" && typeof TuiAltScreen.prototype.copyActiveSelectionToClipboard === "function";
|
|
612
|
+
}
|
|
613
|
+
function createBtwFullscreenTui(parent, theme, keybindings, copyOnSelect, manualSelectionCopySupported, openUrl, copyToClipboard2) {
|
|
614
|
+
if (!copyOnSelect && !manualSelectionCopySupported) {
|
|
615
|
+
throw new Error(
|
|
616
|
+
"Manual fullscreen selection copying is unavailable in this Pi version; update Pi or enable automatic selection copying."
|
|
617
|
+
);
|
|
618
|
+
}
|
|
594
619
|
const styleSearchMatch = (text) => theme.bg("searchMatchBg", theme.fg("searchMatchText", text));
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
620
|
+
const fullscreen = new BtwTuiAltScreen(
|
|
621
|
+
parent.terminal,
|
|
622
|
+
parent.getShowHardwareCursor(),
|
|
623
|
+
void 0,
|
|
624
|
+
{
|
|
625
|
+
mouse: true,
|
|
626
|
+
copyOnSelect,
|
|
627
|
+
searchMatchStyle: (text) => theme.underline(styleSearchMatch(text)),
|
|
628
|
+
searchCurrentMatchStyle: (text) => theme.bold(theme.inverse(styleSearchMatch(text))),
|
|
629
|
+
openUrl,
|
|
630
|
+
copySelection: async (text) => {
|
|
631
|
+
try {
|
|
632
|
+
await copyToClipboard2(text);
|
|
633
|
+
return true;
|
|
634
|
+
} catch {
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
606
637
|
}
|
|
607
638
|
}
|
|
608
|
-
|
|
639
|
+
);
|
|
640
|
+
if (!copyOnSelect) {
|
|
641
|
+
let isInBracketedPaste = false;
|
|
642
|
+
fullscreen.addInputListenerBeforeViewport((data) => {
|
|
643
|
+
const wasInBracketedPaste = isInBracketedPaste;
|
|
644
|
+
const startsBracketedPaste = data.includes(BRACKETED_PASTE_START);
|
|
645
|
+
if (startsBracketedPaste) isInBracketedPaste = true;
|
|
646
|
+
if (isInBracketedPaste && data.includes(BRACKETED_PASTE_END)) {
|
|
647
|
+
isInBracketedPaste = false;
|
|
648
|
+
}
|
|
649
|
+
if (wasInBracketedPaste || startsBracketedPaste || fullscreen.hasFocusedOverlay() || isKeyRelease(data) || !keybindings.matches(data, "app.message.copy")) {
|
|
650
|
+
return void 0;
|
|
651
|
+
}
|
|
652
|
+
if (!fullscreen.hasActiveSelection()) {
|
|
653
|
+
fullscreen.flash("No selection to copy");
|
|
654
|
+
return { consume: true };
|
|
655
|
+
}
|
|
656
|
+
void fullscreen.copyActiveSelectionToClipboard().catch(() => fullscreen.flash("Copy failed"));
|
|
657
|
+
return { consume: true };
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
return fullscreen;
|
|
609
661
|
}
|
|
610
662
|
function openUrlInBrowser(target) {
|
|
611
663
|
const [command, args] = process.platform === "darwin" ? ["open", [target]] : process.platform === "win32" ? ["rundll32", ["url.dll,FileProtocolHandler", target]] : ["xdg-open", [target]];
|
|
@@ -613,7 +665,7 @@ function openUrlInBrowser(target) {
|
|
|
613
665
|
}).unref();
|
|
614
666
|
}
|
|
615
667
|
var BtwFullscreenHost = class {
|
|
616
|
-
constructor(parent, theme, keybindings, ctx, run, done, createTui) {
|
|
668
|
+
constructor(parent, theme, keybindings, ctx, run, done, createTui, options) {
|
|
617
669
|
this.parent = parent;
|
|
618
670
|
this.theme = theme;
|
|
619
671
|
this.keybindings = keybindings;
|
|
@@ -621,6 +673,7 @@ var BtwFullscreenHost = class {
|
|
|
621
673
|
this.run = run;
|
|
622
674
|
this.done = done;
|
|
623
675
|
this.createTui = createTui;
|
|
676
|
+
this.options = options;
|
|
624
677
|
queueMicrotask(() => void this.start());
|
|
625
678
|
}
|
|
626
679
|
parent;
|
|
@@ -630,6 +683,7 @@ var BtwFullscreenHost = class {
|
|
|
630
683
|
run;
|
|
631
684
|
done;
|
|
632
685
|
createTui;
|
|
686
|
+
options;
|
|
633
687
|
fullscreen;
|
|
634
688
|
parentOverlay;
|
|
635
689
|
cancelActiveCustom;
|
|
@@ -639,9 +693,10 @@ var BtwFullscreenHost = class {
|
|
|
639
693
|
disposed = false;
|
|
640
694
|
finished = false;
|
|
641
695
|
parentStopped = false;
|
|
642
|
-
|
|
696
|
+
parentRestoreAttempted = false;
|
|
643
697
|
fullscreenCreated = false;
|
|
644
698
|
fullscreenStopped = false;
|
|
699
|
+
parentRestoreQueued = false;
|
|
645
700
|
cleanupError;
|
|
646
701
|
setParentOverlay(overlay) {
|
|
647
702
|
this.parentOverlay = overlay;
|
|
@@ -665,16 +720,16 @@ var BtwFullscreenHost = class {
|
|
|
665
720
|
this.parent.stop({ preserveScreen: true });
|
|
666
721
|
this.parentStopped = true;
|
|
667
722
|
if (this.disposed) throw new FullscreenUiDisposedError();
|
|
668
|
-
this.fullscreen = this.createTui(this.parent, this.theme);
|
|
723
|
+
this.fullscreen = this.createTui(this.parent, this.theme, this.keybindings, this.options);
|
|
669
724
|
this.fullscreenCreated = true;
|
|
670
725
|
this.fullscreen.start();
|
|
671
|
-
const addHardCancelListener = this.fullscreen.addInputListenerBeforeViewport?.bind(this.fullscreen) ?? this.fullscreen.addInputListener.bind(this.fullscreen);
|
|
726
|
+
const addHardCancelListener = this.fullscreen.addInputListenerBeforeAll?.bind(this.fullscreen) ?? this.fullscreen.addInputListenerBeforeViewport?.bind(this.fullscreen) ?? this.fullscreen.addInputListener.bind(this.fullscreen);
|
|
672
727
|
this.removeHardCancelListener = addHardCancelListener((data) => {
|
|
673
728
|
if (isKeyRelease(data) || !matchesKey2(data, Key2.ctrl("c"))) return void 0;
|
|
674
729
|
try {
|
|
675
730
|
this.hardCancelActiveCustom?.();
|
|
676
731
|
} finally {
|
|
677
|
-
this.
|
|
732
|
+
this.queueParentRestore();
|
|
678
733
|
}
|
|
679
734
|
return { consume: true };
|
|
680
735
|
});
|
|
@@ -692,9 +747,22 @@ var BtwFullscreenHost = class {
|
|
|
692
747
|
this.finished = true;
|
|
693
748
|
this.done(outcome);
|
|
694
749
|
}
|
|
750
|
+
queueParentRestore() {
|
|
751
|
+
if (this.parentRestoreQueued || this.parentRestoreAttempted) return;
|
|
752
|
+
this.parentRestoreQueued = true;
|
|
753
|
+
queueMicrotask(() => {
|
|
754
|
+
this.parentRestoreQueued = false;
|
|
755
|
+
this.restoreParent();
|
|
756
|
+
});
|
|
757
|
+
}
|
|
695
758
|
restoreParent() {
|
|
696
|
-
this.removeHardCancelListener
|
|
759
|
+
const removeHardCancelListener = this.removeHardCancelListener;
|
|
697
760
|
this.removeHardCancelListener = void 0;
|
|
761
|
+
try {
|
|
762
|
+
removeHardCancelListener?.();
|
|
763
|
+
} catch (error) {
|
|
764
|
+
this.cleanupError ??= error;
|
|
765
|
+
}
|
|
698
766
|
if (this.fullscreenCreated && !this.fullscreenStopped) {
|
|
699
767
|
this.fullscreenStopped = true;
|
|
700
768
|
try {
|
|
@@ -703,7 +771,7 @@ var BtwFullscreenHost = class {
|
|
|
703
771
|
this.cleanupError ??= error;
|
|
704
772
|
}
|
|
705
773
|
}
|
|
706
|
-
if (!this.parentStopped || this.
|
|
774
|
+
if (!this.parentStopped || this.parentRestoreAttempted) return;
|
|
707
775
|
const parentOverlay = this.parentOverlay;
|
|
708
776
|
this.parentOverlay = void 0;
|
|
709
777
|
try {
|
|
@@ -712,8 +780,8 @@ var BtwFullscreenHost = class {
|
|
|
712
780
|
this.cleanupError ??= error;
|
|
713
781
|
}
|
|
714
782
|
try {
|
|
783
|
+
this.parentRestoreAttempted = true;
|
|
715
784
|
this.parent.start();
|
|
716
|
-
this.parentRestarted = true;
|
|
717
785
|
this.parent.renderNow(false);
|
|
718
786
|
} catch (error) {
|
|
719
787
|
this.cleanupError ??= error;
|
|
@@ -1020,6 +1088,7 @@ Use the provided conversation context only as background. Answer the user's side
|
|
|
1020
1088
|
|
|
1021
1089
|
// src/settings.ts
|
|
1022
1090
|
var BTW_SETTINGS_FILE = "pi-btw.json";
|
|
1091
|
+
var DEFAULT_FULLSCREEN_COPY_ON_SELECT = true;
|
|
1023
1092
|
var DEFAULT_REMEMBER_THINKING_LEVEL_CHANGES = true;
|
|
1024
1093
|
var MAX_SETTINGS_BYTES = 64 * 1024;
|
|
1025
1094
|
var mutationQueues = /* @__PURE__ */ new Map();
|
|
@@ -1044,6 +1113,11 @@ function normalizeBtwSettings(value) {
|
|
|
1044
1113
|
if (typeof remember !== "boolean") return void 0;
|
|
1045
1114
|
settings.rememberThinkingLevelChanges = remember;
|
|
1046
1115
|
}
|
|
1116
|
+
if (Object.hasOwn(value, "fullscreenCopyOnSelect")) {
|
|
1117
|
+
const copyOnSelect = Reflect.get(value, "fullscreenCopyOnSelect");
|
|
1118
|
+
if (typeof copyOnSelect !== "boolean") return void 0;
|
|
1119
|
+
settings.fullscreenCopyOnSelect = copyOnSelect;
|
|
1120
|
+
}
|
|
1047
1121
|
return settings;
|
|
1048
1122
|
}
|
|
1049
1123
|
function parseBtwModelReference(reference) {
|
|
@@ -1052,6 +1126,9 @@ function parseBtwModelReference(reference) {
|
|
|
1052
1126
|
if (separator <= 0 || separator === reference.length - 1) return void 0;
|
|
1053
1127
|
return { provider: reference.slice(0, separator), modelId: reference.slice(separator + 1) };
|
|
1054
1128
|
}
|
|
1129
|
+
function effectiveFullscreenCopyOnSelect(settings) {
|
|
1130
|
+
return settings.fullscreenCopyOnSelect ?? DEFAULT_FULLSCREEN_COPY_ON_SELECT;
|
|
1131
|
+
}
|
|
1055
1132
|
function effectiveRememberThinkingLevelChanges(settings) {
|
|
1056
1133
|
return settings.rememberThinkingLevelChanges ?? DEFAULT_REMEMBER_THINKING_LEVEL_CHANGES;
|
|
1057
1134
|
}
|
|
@@ -1189,6 +1266,10 @@ function applyBtwSettingsPatch(current, patch) {
|
|
|
1189
1266
|
if (Object.hasOwn(patch, "rememberThinkingLevelChanges")) {
|
|
1190
1267
|
updated.rememberThinkingLevelChanges = patch.rememberThinkingLevelChanges;
|
|
1191
1268
|
}
|
|
1269
|
+
if (Object.hasOwn(patch, "fullscreenCopyOnSelect")) {
|
|
1270
|
+
if (patch.fullscreenCopyOnSelect === void 0) delete updated.fullscreenCopyOnSelect;
|
|
1271
|
+
else updated.fullscreenCopyOnSelect = patch.fullscreenCopyOnSelect;
|
|
1272
|
+
}
|
|
1192
1273
|
return updated;
|
|
1193
1274
|
}
|
|
1194
1275
|
function isSettingsDocument(value) {
|
|
@@ -1246,7 +1327,8 @@ async function showBtwCommandMenu(ctx, options) {
|
|
|
1246
1327
|
kind: "actions",
|
|
1247
1328
|
title: "Pi BTW",
|
|
1248
1329
|
lines: [
|
|
1249
|
-
`Thinking: ${displayThinkingSummary(state.settings)} \xB7 Remember changes: ${displayRememberSummary(state.settings)}
|
|
1330
|
+
`Thinking: ${displayThinkingSummary(state.settings)} \xB7 Remember changes: ${displayRememberSummary(state.settings)}`,
|
|
1331
|
+
`Copy on select: ${effectiveFullscreenCopyOnSelect(state.settings) ? "On" : "Off"}`
|
|
1250
1332
|
],
|
|
1251
1333
|
items: [
|
|
1252
1334
|
{
|
|
@@ -1272,7 +1354,7 @@ async function showBtwCommandMenu(ctx, options) {
|
|
|
1272
1354
|
{
|
|
1273
1355
|
id: "settings",
|
|
1274
1356
|
label: "Settings",
|
|
1275
|
-
description: "Choose
|
|
1357
|
+
description: "Choose thinking, shortcut memory, and selection copying",
|
|
1276
1358
|
to: state.kind === "invalid" ? "invalid" : "settings"
|
|
1277
1359
|
}
|
|
1278
1360
|
],
|
|
@@ -1311,6 +1393,14 @@ async function showBtwCommandMenu(ctx, options) {
|
|
|
1311
1393
|
currentValue: effectiveRememberThinkingLevelChanges(state.settings) ? "On" : "Off",
|
|
1312
1394
|
values: ["On", "Off"],
|
|
1313
1395
|
action: "set-remember"
|
|
1396
|
+
},
|
|
1397
|
+
{
|
|
1398
|
+
id: "fullscreenCopyOnSelect",
|
|
1399
|
+
label: "Copy selection automatically",
|
|
1400
|
+
description: "Copy mouse selections immediately instead of with the configured copy key.",
|
|
1401
|
+
currentValue: effectiveFullscreenCopyOnSelect(state.settings) ? "On" : "Off",
|
|
1402
|
+
values: ["On", "Off"],
|
|
1403
|
+
action: "set-fullscreen-copy"
|
|
1314
1404
|
}
|
|
1315
1405
|
]
|
|
1316
1406
|
}),
|
|
@@ -1368,6 +1458,21 @@ async function showBtwCommandMenu(ctx, options) {
|
|
|
1368
1458
|
if (!signal.aborted) notifySaveFailure(ctx, error);
|
|
1369
1459
|
return { kind: "rejected" };
|
|
1370
1460
|
}
|
|
1461
|
+
},
|
|
1462
|
+
"set-fullscreen-copy": async ({ value, signal }) => {
|
|
1463
|
+
if (value !== "On" && value !== "Off") return { kind: "rejected" };
|
|
1464
|
+
try {
|
|
1465
|
+
await updateSettings(
|
|
1466
|
+
{ fullscreenCopyOnSelect: value === "On" },
|
|
1467
|
+
{ settingsPath, signal }
|
|
1468
|
+
);
|
|
1469
|
+
if (signal.aborted) return { kind: "rejected" };
|
|
1470
|
+
notifySafely(ctx, `Copy selection automatically: ${value}.`, "info");
|
|
1471
|
+
return { kind: "stay" };
|
|
1472
|
+
} catch (error) {
|
|
1473
|
+
if (!signal.aborted) notifySaveFailure(ctx, error);
|
|
1474
|
+
return { kind: "rejected" };
|
|
1475
|
+
}
|
|
1371
1476
|
}
|
|
1372
1477
|
}
|
|
1373
1478
|
});
|
|
@@ -2475,29 +2580,33 @@ function btw(pi, dependencies = {}) {
|
|
|
2475
2580
|
}
|
|
2476
2581
|
const startingTurnCount = state?.thread.turns.length ?? 0;
|
|
2477
2582
|
try {
|
|
2478
|
-
await runFullscreen(
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
state
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2583
|
+
await runFullscreen(
|
|
2584
|
+
ctx,
|
|
2585
|
+
(fullscreenCtx) => {
|
|
2586
|
+
if (!state) {
|
|
2587
|
+
const createdAt = Date.now();
|
|
2588
|
+
state = {
|
|
2589
|
+
id: `btw-${nextThreadNumber}`,
|
|
2590
|
+
thread: createSideThread(
|
|
2591
|
+
selectedConversationContext ?? buildConversationContext(fullscreenCtx.sessionManager.getBranch())
|
|
2592
|
+
),
|
|
2593
|
+
thinkingLevel: settings.thinkingLevel ?? pi.getThinkingLevel(),
|
|
2594
|
+
createdAt,
|
|
2595
|
+
updatedAt: createdAt
|
|
2596
|
+
};
|
|
2597
|
+
nextThreadNumber += 1;
|
|
2598
|
+
}
|
|
2599
|
+
return runThread({
|
|
2600
|
+
initialQuestion: question || void 0,
|
|
2601
|
+
selected: resolution.selected,
|
|
2602
|
+
thinkingLevel: state.thinkingLevel,
|
|
2603
|
+
rememberThinkingLevelChanges: !sameAsMainThinkingLevel && effectiveRememberThinkingLevelChanges(settings),
|
|
2604
|
+
state,
|
|
2605
|
+
ctx: fullscreenCtx
|
|
2606
|
+
});
|
|
2607
|
+
},
|
|
2608
|
+
{ copyOnSelect: effectiveFullscreenCopyOnSelect(settings) }
|
|
2609
|
+
);
|
|
2501
2610
|
} finally {
|
|
2502
2611
|
if (state?.title && state.thread.turns.length > 0) {
|
|
2503
2612
|
if (state.thread.turns.length > startingTurnCount) {
|