xgen-dex-cli 1.7.0 → 1.7.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.
@@ -288,7 +288,7 @@ import { Box as Box2, Text as Text2 } from "ink";
288
288
 
289
289
  // src/tui/ime-text-input.tsx
290
290
  import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
291
- import { Box, Text, useCursor, useInput, useStdin } from "ink";
291
+ import { Box, Text, useCursor, useInput } from "ink";
292
292
  import stringWidth2 from "string-width";
293
293
 
294
294
  // src/tui/terminal-input.ts
@@ -504,58 +504,6 @@ function backspace(session) {
504
504
  };
505
505
  }
506
506
 
507
- // src/tui/kitty.ts
508
- var ESC = "\x1B";
509
- var QUERY = `${ESC}[?u`;
510
- var KEY_CODES = {
511
- 57358: "capslock",
512
- 57449: "rightalt"
513
- };
514
- var CAPS_LOCK_BIT = 64;
515
- function parseKeyEvents(chunk) {
516
- const events = [];
517
- const pattern = /\u001B\[(\d+)(?::\d+)*(?:;(\d+)(?::(\d+))?)?(?:;[\d:]*)?u/g;
518
- for (const match of chunk.matchAll(pattern)) {
519
- const code = Number(match[1]);
520
- const modifiers = match[2] ? Number(match[2]) - 1 : 0;
521
- events.push({
522
- name: KEY_CODES[code],
523
- // eslint-disable-next-line no-bitwise
524
- capsLock: (modifiers & CAPS_LOCK_BIT) !== 0,
525
- eventType: match[3] ? Number(match[3]) : 1
526
- });
527
- }
528
- return events;
529
- }
530
- async function supportsKittyKeyboard(streams, timeoutMs = 200) {
531
- if (process.env.DEX_NO_KITTY === "1") return false;
532
- if (!streams.stdout.isTTY || !streams.isTTY) return false;
533
- const wasRaw = streams.stdin.isRaw === true;
534
- const wasPaused = !wasRaw;
535
- streams.stdin.setRawMode?.(true);
536
- streams.stdin.resume?.();
537
- return new Promise((resolve) => {
538
- let settled = false;
539
- let seen = "";
540
- const finish = (supported) => {
541
- if (settled) return;
542
- settled = true;
543
- clearTimeout(timer);
544
- streams.stdin.removeListener("data", onData);
545
- if (!wasRaw) streams.stdin.setRawMode?.(false);
546
- if (wasPaused) streams.stdin.pause?.();
547
- resolve(supported);
548
- };
549
- const onData = (data) => {
550
- seen += typeof data === "string" ? data : data.toString("utf8");
551
- if (/\u001B\[\?\d*u/.test(seen)) finish(true);
552
- };
553
- const timer = setTimeout(() => finish(false), timeoutMs);
554
- streams.stdin.on("data", onData);
555
- streams.stdout.write(QUERY);
556
- });
557
- }
558
-
559
507
  // src/tui/ime-text-input.tsx
560
508
  import { jsx, jsxs } from "react/jsx-runtime";
561
509
  var segmenter = new Intl.Segmenter("ko", { granularity: "grapheme" });
@@ -589,6 +537,12 @@ function visibleInput(segments, cursor, maximumWidth) {
589
537
  cursorWidth: widthBeforeCursor
590
538
  };
591
539
  }
540
+ function isHangulToggle(input, key) {
541
+ if (key.ctrl && (input === "`" || input === " " || input === "l")) return true;
542
+ if (key.meta && input === " ") return true;
543
+ if (key.shift && input === " ") return true;
544
+ return false;
545
+ }
592
546
  function TerminalCursor({ x, y }) {
593
547
  const { setCursorPosition } = useCursor();
594
548
  setCursorPosition({ x, y });
@@ -613,22 +567,6 @@ function ImeTextInput(props) {
613
567
  moveCursor(nextCursor, segments.length);
614
568
  props.onChange(nextValue);
615
569
  };
616
- const { stdin: stdin2, isRawModeSupported } = useStdin();
617
- const onModeKeyRef = useRef2(void 0);
618
- onModeKeyRef.current = () => props.onHangulModeChange?.(!props.hangulMode);
619
- useEffect2(() => {
620
- if (!props.focus || !isRawModeSupported) return void 0;
621
- const onData = (data) => {
622
- const chunk = typeof data === "string" ? data : data.toString("utf8");
623
- for (const event of parseKeyEvents(chunk)) {
624
- typingRef.current = { ...typingRef.current, capsLock: event.capsLock };
625
- if (event.eventType !== 1) continue;
626
- if (event.name === "rightalt" || event.name === "capslock") onModeKeyRef.current?.();
627
- }
628
- };
629
- stdin2?.on("data", onData);
630
- return () => void stdin2?.off("data", onData);
631
- }, [props.focus, isRawModeSupported, stdin2]);
632
570
  useEffect2(() => {
633
571
  if (!props.focus || !props.hangulMode) {
634
572
  typingRef.current = { ...IDLE, capsLock: typingRef.current.capsLock };
@@ -674,7 +612,7 @@ function ImeTextInput(props) {
674
612
  }
675
613
  return;
676
614
  }
677
- if (key.ctrl && (input === "`" || input === "l") || key.meta && input === " ") {
615
+ if (isHangulToggle(input, key)) {
678
616
  settle();
679
617
  props.onHangulModeChange?.(!props.hangulMode);
680
618
  return;
@@ -1100,6 +1038,15 @@ function Dashboard(props) {
1100
1038
  setHangulMode(enabled);
1101
1039
  props.preferences?.onHangulModeChange?.(enabled);
1102
1040
  };
1041
+ useEffect6(
1042
+ () => props.preferences?.onModeKey?.(
1043
+ () => setHangulMode((current) => {
1044
+ props.preferences?.onHangulModeChange?.(!current);
1045
+ return !current;
1046
+ })
1047
+ ),
1048
+ [props.preferences]
1049
+ );
1103
1050
  const viewport = useRef3({ lineCount: 0, height: 0 });
1104
1051
  const [starting, setStarting] = useState7(false);
1105
1052
  const controller = useRef3(null);
@@ -1755,25 +1702,25 @@ function RetryInput({ onRetry }) {
1755
1702
  }
1756
1703
 
1757
1704
  // src/tui/screen.ts
1758
- var ESC2 = "\x1B";
1759
- var ENTER_ALT_SCREEN = `${ESC2}[?1049h`;
1760
- var LEAVE_ALT_SCREEN = `${ESC2}[?1049l`;
1761
- var SHOW_CURSOR = `${ESC2}[?25h`;
1705
+ var ESC = "\x1B";
1706
+ var ENTER_ALT_SCREEN = `${ESC}[?1049h`;
1707
+ var LEAVE_ALT_SCREEN = `${ESC}[?1049l`;
1708
+ var SHOW_CURSOR = `${ESC}[?25h`;
1762
1709
  var DISABLE_REPORTS = [
1763
- `${ESC2}[?1004l`,
1710
+ `${ESC}[?1004l`,
1764
1711
  // 포커스 들어옴/나감
1765
- `${ESC2}[?1000l`,
1712
+ `${ESC}[?1000l`,
1766
1713
  // 마우스 클릭
1767
- `${ESC2}[?1002l`,
1714
+ `${ESC}[?1002l`,
1768
1715
  // 마우스 드래그
1769
- `${ESC2}[?1003l`,
1716
+ `${ESC}[?1003l`,
1770
1717
  // 마우스 이동 전부
1771
- `${ESC2}[?1006l`
1718
+ `${ESC}[?1006l`
1772
1719
  // SGR 확장 좌표
1773
1720
  ].join("");
1774
- var ENABLE_BRACKETED_PASTE = `${ESC2}[?2004h`;
1775
- var DISABLE_BRACKETED_PASTE = `${ESC2}[?2004l`;
1776
- var POP_KITTY_KEYBOARD = `${ESC2}[<u`;
1721
+ var ENABLE_BRACKETED_PASTE = `${ESC}[?2004h`;
1722
+ var DISABLE_BRACKETED_PASTE = `${ESC}[?2004l`;
1723
+ var POP_KITTY_KEYBOARD = `${ESC}[<u`;
1777
1724
  function createScreenGuard(stream, options = {}) {
1778
1725
  const alt = Boolean(stream.isTTY);
1779
1726
  let entered = false;
@@ -1826,6 +1773,131 @@ async function writePreferences(preferences, env = process.env) {
1826
1773
  }
1827
1774
  }
1828
1775
 
1776
+ // src/tui/kitty.ts
1777
+ var ESC2 = "\x1B";
1778
+ var QUERY = `${ESC2}[?u`;
1779
+ var KEY_CODES = {
1780
+ 57358: "capslock",
1781
+ 57449: "rightalt"
1782
+ };
1783
+ var CAPS_LOCK_BIT = 64;
1784
+ function parseKeyEvents(chunk) {
1785
+ const events = [];
1786
+ const pattern = /\u001B\[(\d+)(?::\d+)*(?:;(\d+)(?::(\d+))?)?(?:;[\d:]*)?u/g;
1787
+ for (const match of chunk.matchAll(pattern)) {
1788
+ const code = Number(match[1]);
1789
+ const modifiers = match[2] ? Number(match[2]) - 1 : 0;
1790
+ events.push({
1791
+ name: KEY_CODES[code],
1792
+ // eslint-disable-next-line no-bitwise
1793
+ capsLock: (modifiers & CAPS_LOCK_BIT) !== 0,
1794
+ eventType: match[3] ? Number(match[3]) : 1
1795
+ });
1796
+ }
1797
+ return events;
1798
+ }
1799
+ function knownKittyTerminal(env = process.env) {
1800
+ if (env.KITTY_WINDOW_ID) return true;
1801
+ if (env.GHOSTTY_RESOURCES_DIR) return true;
1802
+ const term = (env.TERM ?? "").toLowerCase();
1803
+ if (term === "xterm-kitty" || term === "xterm-ghostty" || term.includes("foot")) return true;
1804
+ const program = (env.TERM_PROGRAM ?? "").toLowerCase();
1805
+ return program === "wezterm" || program === "ghostty" || program === "kitty";
1806
+ }
1807
+ async function supportsKittyKeyboard(streams, timeoutMs = 200, env = process.env) {
1808
+ if (env.DEX_NO_KITTY === "1") return false;
1809
+ if (!streams.stdout.isTTY || !streams.isTTY) return false;
1810
+ if (knownKittyTerminal(env)) return true;
1811
+ const wasRaw = streams.stdin.isRaw === true;
1812
+ const wasPaused = !wasRaw;
1813
+ streams.stdin.setRawMode?.(true);
1814
+ streams.stdin.resume?.();
1815
+ return new Promise((resolve) => {
1816
+ let settled = false;
1817
+ let seen = "";
1818
+ const finish = (supported) => {
1819
+ if (settled) return;
1820
+ settled = true;
1821
+ clearTimeout(timer);
1822
+ streams.stdin.removeListener("data", onData);
1823
+ if (!wasRaw) streams.stdin.setRawMode?.(false);
1824
+ if (wasPaused) streams.stdin.pause?.();
1825
+ resolve(supported);
1826
+ };
1827
+ const onData = (data) => {
1828
+ seen += typeof data === "string" ? data : data.toString("utf8");
1829
+ if (/\u001B\[\?\d*u/.test(seen)) finish(true);
1830
+ };
1831
+ const timer = setTimeout(() => finish(false), timeoutMs);
1832
+ streams.stdin.on("data", onData);
1833
+ streams.stdout.write(QUERY);
1834
+ });
1835
+ }
1836
+ var KEY_EVENT = /\u001B\[([0-9]+)(?::[0-9:]*)?(?:;([0-9]*(?::[0-9]+)?))?(?:;([0-9:]+))?u/g;
1837
+ function normalizeKeyEvents(chunk) {
1838
+ return chunk.replace(
1839
+ KEY_EVENT,
1840
+ (all, code, modifiers, text) => {
1841
+ const hasAlternate = all.slice(0, all.indexOf(";") === -1 ? all.length : all.indexOf(";")).includes(":");
1842
+ const needsModifier = modifiers !== void 0 && modifiers.length === 0;
1843
+ if (!hasAlternate && !needsModifier) return all;
1844
+ const mods = modifiers === void 0 ? void 0 : modifiers.length > 0 ? modifiers : "1";
1845
+ const fields = [code, mods, text].filter((field) => field !== void 0).join(";");
1846
+ return `\x1B[${fields}u`;
1847
+ }
1848
+ );
1849
+ }
1850
+ var PARTIAL_TAIL = /\u001B\[[0-9:;]*$/;
1851
+ var MAX_PARTIAL = 64;
1852
+ function createNormalizer() {
1853
+ let pending = "";
1854
+ return (chunk) => {
1855
+ const combined = pending + chunk;
1856
+ const match = PARTIAL_TAIL.exec(combined);
1857
+ if (match && combined.length - match.index <= MAX_PARTIAL) {
1858
+ pending = combined.slice(match.index);
1859
+ return normalizeKeyEvents(combined.slice(0, match.index));
1860
+ }
1861
+ pending = "";
1862
+ return normalizeKeyEvents(combined);
1863
+ };
1864
+ }
1865
+ function normalizedStdin(stdin2, onSpecialKey) {
1866
+ const normalize = createNormalizer();
1867
+ return new Proxy(stdin2, {
1868
+ get(target, property, receiver) {
1869
+ if (property === "read") {
1870
+ return (...args) => {
1871
+ const chunk = target.read(...args);
1872
+ if (chunk === null || chunk === void 0) return null;
1873
+ const text = typeof chunk === "string" ? chunk : String(chunk);
1874
+ if (onSpecialKey) {
1875
+ for (const event of parseKeyEvents(text)) {
1876
+ if (event.name && event.eventType === 1) onSpecialKey(event.name);
1877
+ }
1878
+ }
1879
+ return normalize(text);
1880
+ };
1881
+ }
1882
+ void receiver;
1883
+ const value = Reflect.get(target, property, target);
1884
+ return typeof value === "function" ? value.bind(target) : value;
1885
+ }
1886
+ });
1887
+ }
1888
+ function createKeySignal() {
1889
+ const listeners = /* @__PURE__ */ new Set();
1890
+ return {
1891
+ notify(name) {
1892
+ for (const listener of listeners) listener(name);
1893
+ },
1894
+ subscribe(listener) {
1895
+ listeners.add(listener);
1896
+ return () => void listeners.delete(listener);
1897
+ }
1898
+ };
1899
+ }
1900
+
1829
1901
  // src/tui/index.tsx
1830
1902
  import { jsx as jsx11 } from "react/jsx-runtime";
1831
1903
  async function runTui(engine) {
@@ -1841,6 +1913,7 @@ async function runTui(engine) {
1841
1913
  const preferences = await readPreferences();
1842
1914
  const kitty = await supportsKittyKeyboard({ stdin, stdout, isTTY: stdin.isTTY });
1843
1915
  screen = createScreenGuard(stdout, { kittyKeyboard: kitty });
1916
+ const modeKeys = createKeySignal();
1844
1917
  screen.enter();
1845
1918
  try {
1846
1919
  const instance = render(
@@ -1850,18 +1923,33 @@ async function runTui(engine) {
1850
1923
  engine,
1851
1924
  preferences: {
1852
1925
  hangulMode: preferences.hangulMode,
1853
- onHangulModeChange: (enabled) => void writePreferences({ hangulMode: enabled })
1926
+ onHangulModeChange: (enabled) => void writePreferences({ hangulMode: enabled }),
1927
+ onModeKey: modeKeys.subscribe
1854
1928
  }
1855
1929
  }
1856
1930
  ),
1857
1931
  {
1858
1932
  exitOnCtrlC: true,
1859
1933
  patchConsole: true,
1860
- // 모든 키를 이스케이프로 받아야 수식 자체가 사건으로 온다.
1934
+ // 프로토콜을 켜면 kitty ink 읽는 모양으로 키를 보낸다 — 사이에서
1935
+ // 고쳐 넘기지 않으면 글자가 하나도 입력되지 않는다.
1936
+ ...kitty ? {
1937
+ stdin: normalizedStdin(stdin, (name) => {
1938
+ if (name === "rightalt" || name === "capslock") modeKeys.notify(name);
1939
+ })
1940
+ } : {},
1941
+ // 모든 키를 이스케이프로 받아야 한/영 키(오른쪽 Alt)와 Caps Lock 이 사건으로
1942
+ // 온다. 그런데 그렇게만 켜면 터미널이 **글쇠 코드만** 보내서 Shift+r 이
1943
+ // 소문자 `r` 로 도착한다 — 된소리도 영문 대문자도 사라진다. 그래서 글자까지
1944
+ // 함께 보내 달라고(reportAssociatedText) 요청한다.
1861
1945
  ...kitty ? {
1862
1946
  kittyKeyboard: {
1863
1947
  mode: "enabled",
1864
- flags: ["disambiguateEscapeCodes", "reportAllKeysAsEscapeCodes"]
1948
+ flags: [
1949
+ "disambiguateEscapeCodes",
1950
+ "reportAllKeysAsEscapeCodes",
1951
+ "reportAssociatedText"
1952
+ ]
1865
1953
  }
1866
1954
  } : {}
1867
1955
  }
@@ -1877,4 +1965,4 @@ async function runTui(engine) {
1877
1965
  export {
1878
1966
  runTui
1879
1967
  };
1880
- //# sourceMappingURL=tui-QF5OJEFW.js.map
1968
+ //# sourceMappingURL=tui-VLPJMWUK.js.map