wispy-cli 2.7.22 → 2.7.24

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/bin/wispy.mjs CHANGED
@@ -1474,13 +1474,15 @@ if (!command || command === "server" || command === "overview") {
1474
1474
  // No args → TUI
1475
1475
  if (!command) {
1476
1476
  try {
1477
- await import(join(rootDir, "bin/wispy-tui.mjs"));
1477
+ await import(join(rootDir, "lib/wispy-tui.mjs"));
1478
+ // TUI handles its own lifecycle via waitUntilExit() — do NOT process.exit() here
1478
1479
  } catch (err) {
1479
1480
  console.error("TUI failed to start:", err.message);
1480
1481
  console.log("Falling back to REPL...");
1481
1482
  await import(join(rootDir, "lib/wispy-repl.mjs"));
1482
1483
  }
1483
- process.exit(0);
1484
+ // Keep process alive — TUI/REPL manages exit
1485
+ await new Promise(() => {}); // block forever, TUI calls process.exit() when done
1484
1486
  }
1485
1487
  }
1486
1488
 
@@ -0,0 +1,163 @@
1
+ /**
2
+ * cjk-text-input.mjs — CJK-aware text input for Ink TUI
3
+ *
4
+ * Drop-in replacement for `ink-text-input` that handles Korean/CJK IME
5
+ * composition properly by using Node's UTF-8 encoded stdin data events
6
+ * instead of raw byte-by-byte useInput hooks.
7
+ *
8
+ * Key insight: When stdin is in "raw" mode and encoding is set to "utf8",
9
+ * Node.js emits fully-composed UTF-8 strings (including Korean/CJK characters)
10
+ * rather than raw bytes. The terminal emulator handles IME composition before
11
+ * sending the final composed character to stdin.
12
+ */
13
+
14
+ import React, { useState, useEffect, useRef, useCallback } from "react";
15
+ import { Box, Text, useStdin } from "ink";
16
+
17
+ /**
18
+ * CJKTextInput — A text input component that properly handles CJK/Korean input.
19
+ *
20
+ * @param {object} props
21
+ * @param {string} props.value - Controlled value
22
+ * @param {function} props.onChange - Called with new value on change
23
+ * @param {function} props.onSubmit - Called with value on Enter
24
+ * @param {string} [props.placeholder] - Placeholder text when empty
25
+ * @param {boolean} [props.focus=true] - Whether this input is focused
26
+ * @param {boolean} [props.showCursor=true] - Whether to show cursor block
27
+ */
28
+ function CJKTextInput({
29
+ value = "",
30
+ onChange,
31
+ onSubmit,
32
+ placeholder = "",
33
+ focus = true,
34
+ showCursor = true,
35
+ }) {
36
+ const { stdin, setRawMode } = useStdin();
37
+ const valueRef = useRef(value);
38
+
39
+ // Keep ref in sync with controlled value
40
+ useEffect(() => {
41
+ valueRef.current = value;
42
+ }, [value]);
43
+
44
+ useEffect(() => {
45
+ if (!focus || !stdin) return;
46
+
47
+ // Set UTF-8 encoding so Node decodes multi-byte sequences (Korean, CJK)
48
+ // before emitting the data event — this is the key fix for CJK input.
49
+ const prevEncoding = stdin.readableEncoding;
50
+ stdin.setEncoding("utf8");
51
+
52
+ const onData = (chunk) => {
53
+ // chunk is now a UTF-8 decoded string (not a Buffer)
54
+ const str = typeof chunk === "string" ? chunk : chunk.toString("utf8");
55
+
56
+ for (let i = 0; i < str.length; ) {
57
+ // Check for escape sequences (arrow keys, etc.)
58
+ if (str[i] === "\x1b") {
59
+ const seq = str.slice(i);
60
+ // Arrow keys
61
+ if (seq.startsWith("\x1b[D") || seq.startsWith("\x1bOD")) {
62
+ // Left arrow — not handled here, skip
63
+ i += seq.startsWith("\x1b[D") ? 3 : 3;
64
+ continue;
65
+ }
66
+ if (seq.startsWith("\x1b[C") || seq.startsWith("\x1bOC")) {
67
+ // Right arrow — not handled here, skip
68
+ i += 3;
69
+ continue;
70
+ }
71
+ // Other escape sequences — skip
72
+ i += 1;
73
+ continue;
74
+ }
75
+
76
+ const ch = str[i];
77
+ const code = ch.charCodeAt(0);
78
+
79
+ if (ch === "\r" || ch === "\n") {
80
+ onSubmit?.(valueRef.current);
81
+ i++;
82
+ continue;
83
+ }
84
+
85
+ if (ch === "\x7f" || ch === "\b") {
86
+ // Backspace
87
+ const newVal = valueRef.current.slice(0, -1);
88
+ valueRef.current = newVal;
89
+ onChange?.(newVal);
90
+ i++;
91
+ continue;
92
+ }
93
+
94
+ if (ch === "\x03") {
95
+ // Ctrl+C — let Ink handle it
96
+ i++;
97
+ continue;
98
+ }
99
+
100
+ if (ch === "\t") {
101
+ // Tab — do NOT consume, let Ink's useInput handle tab for view switching
102
+ i++;
103
+ continue;
104
+ }
105
+
106
+ if (ch === "\x15") {
107
+ // Ctrl+U — clear line
108
+ valueRef.current = "";
109
+ onChange?.("");
110
+ i++;
111
+ continue;
112
+ }
113
+
114
+ if (ch === "\x01") {
115
+ // Ctrl+A — move to beginning (just clear for simplicity)
116
+ i++;
117
+ continue;
118
+ }
119
+
120
+ // Normal printable character (ASCII or multi-byte CJK/Korean)
121
+ // code >= 32 for ASCII printable; any non-control Unicode char is fine
122
+ if (code >= 32 || code > 127) {
123
+ const newVal = valueRef.current + ch;
124
+ valueRef.current = newVal;
125
+ onChange?.(newVal);
126
+ }
127
+
128
+ i++;
129
+ }
130
+ };
131
+
132
+ stdin.on("data", onData);
133
+
134
+ return () => {
135
+ stdin.removeListener("data", onData);
136
+ // Restore previous encoding
137
+ if (prevEncoding !== null) {
138
+ stdin.setEncoding(prevEncoding);
139
+ }
140
+ };
141
+ }, [focus, stdin, onChange, onSubmit]);
142
+
143
+ // Render
144
+ const displayValue = value;
145
+ const showPlaceholder = !displayValue && placeholder;
146
+
147
+ if (showPlaceholder) {
148
+ return React.createElement(Text, { dimColor: true }, placeholder);
149
+ }
150
+
151
+ if (showCursor && focus) {
152
+ return React.createElement(
153
+ Box,
154
+ {},
155
+ React.createElement(Text, {}, displayValue),
156
+ React.createElement(Text, { inverse: true }, " "),
157
+ );
158
+ }
159
+
160
+ return React.createElement(Text, {}, displayValue);
161
+ }
162
+
163
+ export default CJKTextInput;
package/lib/wispy-tui.mjs CHANGED
@@ -12,7 +12,9 @@
12
12
  import React, { useState, useEffect, useRef, useCallback } from "react";
13
13
  import { render, Box, Text, useApp, useInput, useStdout } from "ink";
14
14
  import Spinner from "ink-spinner";
15
- import TextInput from "ink-text-input";
15
+ import CJKTextInput from "./cjk-text-input.mjs";
16
+ // CJKTextInput is a drop-in replacement for ink-text-input with Korean/CJK support
17
+ const TextInput = CJKTextInput;
16
18
 
17
19
  import { COMMANDS, filterCommands } from "./command-registry.mjs";
18
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wispy-cli",
3
- "version": "2.7.22",
3
+ "version": "2.7.24",
4
4
  "description": "🌿 Wispy — AI workspace assistant with trustworthy execution (harness, receipts, approvals, diffs)",
5
5
  "license": "MIT",
6
6
  "author": "Minseo & Poropo",