@tritard/waterbrother 0.14.1 → 0.14.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +62 -17
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tritard/waterbrother",
3
- "version": "0.14.1",
3
+ "version": "0.14.2",
4
4
  "description": "Waterbrother: Grok-powered coding CLI with local tools, sessions, operator modes, and approval controls",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -4704,29 +4704,74 @@ async function readInteractiveLine(options = {}) {
4704
4704
  }
4705
4705
  }
4706
4706
 
4707
+ // Track whether keypress handler processed the last data chunk
4708
+ let keypressHandled = false;
4709
+ const origOnKeypress = onKeypress;
4710
+ onKeypress = function (str, key) {
4711
+ keypressHandled = true;
4712
+ origOnKeypress(str, key);
4713
+ };
4714
+
4707
4715
  function onData(chunk) {
4708
4716
  if (settled) return;
4709
4717
  const text = String(chunk || "");
4710
4718
  if (!text) return;
4711
- const normalized = normalizePastedChunk(text);
4712
- const looksLikeBracketedPaste = text.includes("\x1b[200~") || text.includes("\x1b[201~");
4713
- const looksLikePastedBlock =
4714
- looksLikeBracketedPaste ||
4715
- (normalized.length > 1 && normalized.includes("\n") && /[^\n]/.test(normalized));
4716
-
4717
- if (!looksLikePastedBlock) {
4718
- return;
4719
- }
4720
4719
 
4721
- if (normalized) {
4722
- buffer += normalized;
4723
- selectedIndex = 0;
4724
- render();
4725
- }
4720
+ // Give keypress handler a chance to fire first (it's synchronous)
4721
+ keypressHandled = false;
4722
+ // readline.emitKeypressEvents will fire onKeypress synchronously before onData returns
4723
+ // on some platforms. Set a microtask to check if keypress handled it.
4724
+ Promise.resolve().then(() => {
4725
+ if (keypressHandled || settled) return;
4726
+
4727
+ // Keypress didn't fire — handle raw data as fallback
4728
+ const normalized = normalizePastedChunk(text);
4729
+ const looksLikeBracketedPaste = text.includes("\x1b[200~") || text.includes("\x1b[201~");
4730
+ const looksLikePastedBlock =
4731
+ looksLikeBracketedPaste ||
4732
+ (normalized.length > 1 && normalized.includes("\n") && /[^\n]/.test(normalized));
4733
+
4734
+ if (looksLikePastedBlock) {
4735
+ if (normalized) {
4736
+ buffer += normalized;
4737
+ selectedIndex = 0;
4738
+ render();
4739
+ }
4740
+ ignoredPastePrintable += [...normalized].filter((char) => char !== "\n").length;
4741
+ ignoredPasteEnters += (normalized.match(/\n/g) || []).length;
4742
+ pasteSuppressUntil = Date.now() + 300;
4743
+ return;
4744
+ }
4726
4745
 
4727
- ignoredPastePrintable += [...normalized].filter((char) => char !== "\n").length;
4728
- ignoredPasteEnters += (normalized.match(/\n/g) || []).length;
4729
- pasteSuppressUntil = Date.now() + 300;
4746
+ // Single character fallback keypress emitter failed to fire
4747
+ for (const ch of text) {
4748
+ if (ch === "\r" || ch === "\n") {
4749
+ handleSubmit();
4750
+ return;
4751
+ }
4752
+ if (ch === "\u0003") {
4753
+ if (settled) return;
4754
+ settled = true;
4755
+ cleanup();
4756
+ output.write("\n");
4757
+ reject(new Error("Interrupted"));
4758
+ return;
4759
+ }
4760
+ if (ch === "\u007f" || ch === "\b") {
4761
+ if (buffer.length > 0) {
4762
+ buffer = buffer.slice(0, -1);
4763
+ selectedIndex = 0;
4764
+ render();
4765
+ }
4766
+ continue;
4767
+ }
4768
+ if (ch.charCodeAt(0) < 32 || ch.charCodeAt(0) === 127) continue;
4769
+ if (ch.includes("\x1b")) continue;
4770
+ buffer += ch;
4771
+ selectedIndex = 0;
4772
+ render();
4773
+ }
4774
+ });
4730
4775
  }
4731
4776
 
4732
4777
  // Ensure stdin is in a clean state before attaching listeners.