@code-yeongyu/senpi-tui 2026.9.30 → 2026.10.1-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/dist/utils.js CHANGED
@@ -67,6 +67,10 @@ export function __widthCacheStats() {
67
67
  };
68
68
  }
69
69
  export const cjkBreakRegex = /[\p{Script_Extensions=Han}\p{Script_Extensions=Hiragana}\p{Script_Extensions=Katakana}\p{Script_Extensions=Hangul}\p{Script_Extensions=Bopomofo}]/u;
70
+ // CJK letters remain part of words and paths; only punctuation can separate prose from completions.
71
+ export const cjkPunctuationRegex = new RegExp(`(?:(?=\\p{Punctuation})${cjkBreakRegex.source}|[,.:;!?()[]{}“”‘’…—])`, "u");
72
+ export const autocompleteSeparatorRegex = new RegExp(`(?:\\s|${cjkPunctuationRegex.source})`, "u");
73
+ export const autocompleteBoundaryRegex = new RegExp(`(?:^|${autocompleteSeparatorRegex.source})`, "u");
70
74
  function isPrintableAscii(str) {
71
75
  for (let i = 0; i < str.length; i++) {
72
76
  const code = str.charCodeAt(i);
@@ -166,8 +170,12 @@ function finalizeTruncatedResult(prefix, prefixWidth, ellipsis, ellipsisWidth, m
166
170
  * check to avoid running the RGI_Emoji regex unnecessarily.
167
171
  */
168
172
  function graphemeWidth(segment) {
169
- if (segment === "\t") {
170
- return 3;
173
+ if (segment.length === 1) {
174
+ const code = segment.charCodeAt(0);
175
+ if (code >= 0x20 && code <= 0x7e)
176
+ return 1;
177
+ if (code === 0x09)
178
+ return 3;
171
179
  }
172
180
  // Some marks occupy cells even without a base character.
173
181
  if (terminalSpacingMarkRegex.test(segment)) {
@@ -229,9 +237,11 @@ export function visibleWidth(str) {
229
237
  if (str.length === 0) {
230
238
  return 0;
231
239
  }
232
- // Fast path: pure ASCII printable
233
- if (isPrintableAscii(str)) {
234
- return str.length;
240
+ // Fast path: printable ASCII, tabs, and ANSI escape sequences. Styled lines take this path, so
241
+ // re-rendering after a theme change does not run grapheme segmentation on every line.
242
+ const asciiWidth = asciiVisibleWidth(str);
243
+ if (asciiWidth !== -1) {
244
+ return asciiWidth;
235
245
  }
236
246
  // Check cache
237
247
  const cachedCurrent = widthCacheCurrent.get(str);
@@ -251,22 +261,26 @@ export function visibleWidth(str) {
251
261
  if (str.includes("\t")) {
252
262
  clean = clean.replace(/\t/g, " ");
253
263
  }
254
- if (clean.includes("\x1b")) {
264
+ let escapeIndex = clean.indexOf("\x1b");
265
+ if (escapeIndex !== -1) {
255
266
  // Strip supported ANSI/OSC/APC escape sequences in one pass.
256
267
  // This covers CSI styling/cursor codes, OSC hyperlinks and prompt markers,
257
268
  // and APC sequences like CURSOR_MARKER.
258
269
  let stripped = "";
259
- let i = 0;
260
- while (i < clean.length) {
261
- const ansi = extractAnsiCode(clean, i);
262
- if (ansi) {
263
- i += ansi.length;
264
- continue;
270
+ let copyFrom = 0;
271
+ while (escapeIndex !== -1) {
272
+ const length = ansiCodeLength(clean, escapeIndex);
273
+ if (length > 0) {
274
+ stripped += clean.slice(copyFrom, escapeIndex);
275
+ escapeIndex += length;
276
+ copyFrom = escapeIndex;
265
277
  }
266
- stripped += clean[i];
267
- i++;
278
+ else {
279
+ escapeIndex++;
280
+ }
281
+ escapeIndex = clean.indexOf("\x1b", escapeIndex);
268
282
  }
269
- clean = stripped;
283
+ clean = stripped + clean.slice(copyFrom);
270
284
  }
271
285
  // Calculate width
272
286
  let width = 0;
@@ -394,30 +408,52 @@ export function coalesceAdjacentSgr(line) {
394
408
  * Extract ANSI escape sequences from a string at the given position.
395
409
  */
396
410
  export function extractAnsiCode(str, pos) {
397
- if (pos >= str.length || str[pos] !== "\x1b")
398
- return null;
411
+ const length = ansiCodeLength(str, pos);
412
+ return length > 0 ? { code: str.substring(pos, pos + length), length } : null;
413
+ }
414
+ /**
415
+ * Width of a string made of printable ASCII, tabs, and ANSI escape sequences, or -1 if it contains
416
+ * anything else. Matches `visibleWidth` for those strings without allocating.
417
+ */
418
+ function asciiVisibleWidth(str) {
419
+ let width = 0;
420
+ let i = 0;
421
+ while (i < str.length) {
422
+ const code = str.charCodeAt(i);
423
+ if (code >= 0x20 && code <= 0x7e) {
424
+ width++;
425
+ i++;
426
+ }
427
+ else if (code === 0x09) {
428
+ width += 3;
429
+ i++;
430
+ }
431
+ else if (code === 0x1b) {
432
+ const length = ansiCodeLength(str, i);
433
+ if (length === 0)
434
+ return -1;
435
+ i += length;
436
+ }
437
+ else {
438
+ return -1;
439
+ }
440
+ }
441
+ return width;
442
+ }
443
+ /** Length of the ANSI/OSC/APC escape sequence starting at `pos`, or 0 if there is none. */
444
+ function ansiCodeLength(str, pos) {
445
+ if (pos >= str.length || str.charCodeAt(pos) !== 0x1b)
446
+ return 0;
399
447
  const next = str[pos + 1];
400
448
  // CSI sequence: ESC [ ... m/G/K/H/J
401
449
  if (next === "[") {
402
- let j = pos + 2;
403
- while (j < str.length && !/[mGKHJ]/.test(str[j]))
404
- j++;
405
- if (j < str.length)
406
- return { code: str.substring(pos, j + 1), length: j + 1 - pos };
407
- return null;
408
- }
409
- // OSC sequence: ESC ] ... BEL or ESC ] ... ST (ESC \)
410
- // Used for hyperlinks (OSC 8), window titles, etc.
411
- if (next === "]") {
412
- let j = pos + 2;
413
- while (j < str.length) {
414
- if (str[j] === "\x07")
415
- return { code: str.substring(pos, j + 1), length: j + 1 - pos };
416
- if (str[j] === "\x1b" && str[j + 1] === "\\")
417
- return { code: str.substring(pos, j + 2), length: j + 2 - pos };
418
- j++;
450
+ for (let j = pos + 2; j < str.length; j++) {
451
+ const c = str.charCodeAt(j);
452
+ // m, G, K, H, J
453
+ if (c === 0x6d || c === 0x47 || c === 0x4b || c === 0x48 || c === 0x4a)
454
+ return j + 1 - pos;
419
455
  }
420
- return null;
456
+ return 0;
421
457
  }
422
458
  // DCS sequence: ESC P ... ST (ESC \)
423
459
  // Used for tmux passthrough (ESC P tmux; ... ESC \) whose payload doubles
@@ -426,32 +462,33 @@ export function extractAnsiCode(str, pos) {
426
462
  if (next === "P") {
427
463
  let j = pos + 2;
428
464
  while (j < str.length) {
429
- if (str[j] === "\x1b") {
465
+ if (str.charCodeAt(j) === 0x1b) {
430
466
  if (str[j + 1] === "\\")
431
- return { code: str.substring(pos, j + 2), length: j + 2 - pos };
432
- if (str[j + 1] === "\x1b") {
467
+ return j + 2 - pos;
468
+ if (str.charCodeAt(j + 1) === 0x1b) {
433
469
  j += 2;
434
470
  continue;
435
471
  }
436
472
  }
437
473
  j++;
438
474
  }
439
- return null;
475
+ return 0;
440
476
  }
477
+ // OSC sequence: ESC ] ... BEL or ESC ] ... ST (ESC \)
478
+ // Used for hyperlinks (OSC 8), window titles, etc.
441
479
  // APC sequence: ESC _ ... BEL or ESC _ ... ST (ESC \)
442
480
  // Used for cursor marker and application-specific commands
443
- if (next === "_") {
444
- let j = pos + 2;
445
- while (j < str.length) {
446
- if (str[j] === "\x07")
447
- return { code: str.substring(pos, j + 1), length: j + 1 - pos };
448
- if (str[j] === "\x1b" && str[j + 1] === "\\")
449
- return { code: str.substring(pos, j + 2), length: j + 2 - pos };
450
- j++;
481
+ if (next === "]" || next === "_") {
482
+ for (let j = pos + 2; j < str.length; j++) {
483
+ const c = str.charCodeAt(j);
484
+ if (c === 0x07)
485
+ return j + 1 - pos;
486
+ if (c === 0x1b && str[j + 1] === "\\")
487
+ return j + 2 - pos;
451
488
  }
452
- return null;
489
+ return 0;
453
490
  }
454
- return null;
491
+ return 0;
455
492
  }
456
493
  function parseOsc8Hyperlink(ansiCode) {
457
494
  if (!ansiCode.startsWith("\x1b]8;")) {
@@ -726,16 +763,17 @@ class AnsiCodeTracker {
726
763
  }
727
764
  }
728
765
  function updateTrackerFromText(text, tracker) {
729
- let i = 0;
730
- while (i < text.length) {
731
- const ansiResult = extractAnsiCode(text, i);
732
- if (ansiResult) {
733
- tracker.process(ansiResult.code);
734
- i += ansiResult.length;
766
+ let i = text.indexOf("\x1b");
767
+ while (i !== -1) {
768
+ const length = ansiCodeLength(text, i);
769
+ if (length > 0) {
770
+ tracker.process(text.substring(i, i + length));
771
+ i += length;
735
772
  }
736
773
  else {
737
774
  i++;
738
775
  }
776
+ i = text.indexOf("\x1b", i);
739
777
  }
740
778
  }
741
779
  /** Return only the background color active at the end of an ANSI-styled string. */
@@ -744,6 +782,10 @@ export function getActiveBackgroundAnsi(text) {
744
782
  updateTrackerFromText(text, tracker);
745
783
  return tracker.getActiveBackgroundCode();
746
784
  }
785
+ function* graphemeSegments(text) {
786
+ for (const { segment } of graphemeSegmenter.segment(text))
787
+ yield segment;
788
+ }
747
789
  /**
748
790
  * Split text into words while keeping ANSI codes attached.
749
791
  */
@@ -769,13 +811,19 @@ function splitIntoTokensWithAnsi(text) {
769
811
  i += ansiResult.length;
770
812
  continue;
771
813
  }
772
- let end = i;
773
- while (end < text.length && !extractAnsiCode(text, end)) {
774
- end++;
814
+ // Visible text runs up to the next escape sequence.
815
+ let end = text.indexOf("\x1b", i + 1);
816
+ while (end !== -1 && ansiCodeLength(text, end) === 0) {
817
+ end = text.indexOf("\x1b", end + 1);
775
818
  }
776
- for (const { segment } of graphemeSegmenter.segment(text.slice(i, end))) {
819
+ if (end === -1)
820
+ end = text.length;
821
+ const chunk = text.slice(i, end);
822
+ // Printable ASCII characters are single graphemes, so skip the segmenter for them.
823
+ const ascii = isPrintableAscii(chunk);
824
+ for (const segment of ascii ? chunk : graphemeSegments(chunk)) {
777
825
  const segmentIsSpace = segment === " ";
778
- if (!segmentIsSpace && cjkBreakRegex.test(segment)) {
826
+ if (!ascii && !segmentIsSpace && cjkBreakRegex.test(segment)) {
779
827
  flushCurrent();
780
828
  const token = pendingAnsi + segment;
781
829
  pendingAnsi = "";
@@ -0,0 +1,23 @@
1
+ /** Lines moved per mouse-wheel event, or `"auto"` to accelerate fast wheel spins. */
2
+ export type WheelScrollLines = number | "auto";
3
+ /**
4
+ * Converts wheel events into line counts.
5
+ *
6
+ * In `"auto"` mode on terminals that do not accelerate wheel input, the count follows event
7
+ * velocity: an isolated notch moves one line, while a fast spin moves up to six lines per event.
8
+ * For example, notches 100 ms apart move 1 line each, 50 ms apart move 2, and 20 ms apart move 5.
9
+ */
10
+ export declare class WheelScrollAccelerator {
11
+ private lines;
12
+ private readonly accelerate;
13
+ private lastTime;
14
+ private lastDirection;
15
+ private averageGap;
16
+ private carry;
17
+ constructor(lines?: WheelScrollLines, accelerate?: boolean);
18
+ setLines(lines: WheelScrollLines): void;
19
+ /** Return the positive line count for a wheel event in `direction` at time `now` (milliseconds). */
20
+ next(direction: -1 | 1, now: number): number;
21
+ private reset;
22
+ }
23
+ //# sourceMappingURL=wheel-scroll.d.ts.map
@@ -0,0 +1,70 @@
1
+ // Several events closer than this belong to one physical notch (Ghostty emits them ~4 ms apart)
2
+ // or come from a high-resolution source. They move one line each and do not accelerate.
3
+ const BURST_GAP_MS = 5;
4
+ // A pause longer than this ends a scroll gesture.
5
+ const GESTURE_GAP_MS = 200;
6
+ // Average event gap that maps to one line per event. Faster events scale up proportionally.
7
+ const REFERENCE_GAP_MS = 100;
8
+ const MAX_AUTO_LINES = 6;
9
+ /**
10
+ * Local macOS terminals receive wheel and trackpad deltas that the OS has already accelerated,
11
+ * and they emit one event per line. Other platforms, and SSH sessions where the client platform
12
+ * is unknown, usually send one event per wheel notch.
13
+ */
14
+ function terminalAcceleratesWheel() {
15
+ const env = process.env;
16
+ return (process.platform === "darwin" &&
17
+ env.SSH_CONNECTION === undefined &&
18
+ env.SSH_CLIENT === undefined &&
19
+ env.SSH_TTY === undefined);
20
+ }
21
+ /**
22
+ * Converts wheel events into line counts.
23
+ *
24
+ * In `"auto"` mode on terminals that do not accelerate wheel input, the count follows event
25
+ * velocity: an isolated notch moves one line, while a fast spin moves up to six lines per event.
26
+ * For example, notches 100 ms apart move 1 line each, 50 ms apart move 2, and 20 ms apart move 5.
27
+ */
28
+ export class WheelScrollAccelerator {
29
+ constructor(lines = "auto", accelerate = !terminalAcceleratesWheel()) {
30
+ this.lastTime = Number.NEGATIVE_INFINITY;
31
+ this.lastDirection = 0;
32
+ this.carry = 0;
33
+ this.lines = lines;
34
+ this.accelerate = accelerate;
35
+ }
36
+ setLines(lines) {
37
+ this.lines = lines;
38
+ this.reset();
39
+ }
40
+ /** Return the positive line count for a wheel event in `direction` at time `now` (milliseconds). */
41
+ next(direction, now) {
42
+ if (this.lines !== "auto")
43
+ return Number.isFinite(this.lines) ? Math.max(1, Math.floor(this.lines)) : 1;
44
+ if (!this.accelerate)
45
+ return 1;
46
+ const gap = now - this.lastTime;
47
+ const sameGesture = direction === this.lastDirection && gap <= GESTURE_GAP_MS;
48
+ this.lastTime = now;
49
+ this.lastDirection = direction;
50
+ if (!sameGesture) {
51
+ this.averageGap = undefined;
52
+ this.carry = 0;
53
+ return 1;
54
+ }
55
+ if (gap < BURST_GAP_MS)
56
+ return 1;
57
+ this.averageGap = this.averageGap === undefined ? gap : (this.averageGap + gap) / 2;
58
+ const lines = Math.min(MAX_AUTO_LINES, Math.max(1, REFERENCE_GAP_MS / this.averageGap)) + this.carry;
59
+ const whole = Math.floor(lines);
60
+ this.carry = lines - whole;
61
+ return whole;
62
+ }
63
+ reset() {
64
+ this.lastTime = Number.NEGATIVE_INFINITY;
65
+ this.lastDirection = 0;
66
+ this.averageGap = undefined;
67
+ this.carry = 0;
68
+ }
69
+ }
70
+ //# sourceMappingURL=wheel-scroll.js.map
@@ -7,8 +7,17 @@
7
7
  #include <string.h>
8
8
  #endif
9
9
 
10
- typedef enum { CLIPBOARD_TEXT, CLIPBOARD_IMAGE, CLIPBOARD_WRITE } clipboard_operation;
11
- typedef enum { CLIPBOARD_UNAVAILABLE, CLIPBOARD_EMPTY, CLIPBOARD_UTF8, CLIPBOARD_LATIN1, CLIPBOARD_UTF16, CLIPBOARD_BUFFER } clipboard_format;
10
+ typedef enum { CLIPBOARD_TEXT, CLIPBOARD_IMAGE, CLIPBOARD_WRITE, CLIPBOARD_FILES } clipboard_operation;
11
+ // CLIPBOARD_PATHS stores concatenated, NUL-terminated UTF-8 paths in clipboard_job.data.
12
+ typedef enum {
13
+ CLIPBOARD_UNAVAILABLE,
14
+ CLIPBOARD_EMPTY,
15
+ CLIPBOARD_UTF8,
16
+ CLIPBOARD_LATIN1,
17
+ CLIPBOARD_UTF16,
18
+ CLIPBOARD_BUFFER,
19
+ CLIPBOARD_PATHS
20
+ } clipboard_format;
12
21
 
13
22
  typedef struct {
14
23
  napi_async_work work;
@@ -63,6 +72,33 @@ static void PI_NAPI_CALL execute_clipboard_work(napi_env env, void* data) {
63
72
  clipboard_execute(data);
64
73
  }
65
74
 
75
+ static int create_clipboard_paths_value(
76
+ napi_env env,
77
+ const clipboard_job* job,
78
+ napi_create_string_utf8_fn create_string,
79
+ napi_value* result
80
+ ) {
81
+ napi_create_array_fn create_array = (napi_create_array_fn)node_symbol("napi_create_array");
82
+ napi_set_element_fn set_element = (napi_set_element_fn)node_symbol("napi_set_element");
83
+ if (!create_array || !set_element || create_array(env, result) != 0) return 1;
84
+
85
+ const char* cursor = job->data;
86
+ const char* end = cursor + job->length;
87
+ for (uint32_t index = 0; cursor < end; index++) {
88
+ const char* terminator = cursor;
89
+ while (terminator < end && *terminator != '\0') terminator++;
90
+ if (terminator == end) return 1;
91
+
92
+ napi_value item = 0;
93
+ if (create_string(env, cursor, (size_t)(terminator - cursor), &item) != 0 ||
94
+ set_element(env, *result, index, item) != 0) {
95
+ return 1;
96
+ }
97
+ cursor = terminator + 1;
98
+ }
99
+ return 0;
100
+ }
101
+
66
102
  static void PI_NAPI_CALL complete_clipboard_work(napi_env env, int status, void* data) {
67
103
  clipboard_job* job = data;
68
104
  napi_create_string_utf8_fn create_string = (napi_create_string_utf8_fn)node_symbol("napi_create_string_utf8");
@@ -71,6 +107,8 @@ static void PI_NAPI_CALL complete_clipboard_work(napi_env env, int status, void*
71
107
  if (!job->error && job->operation != CLIPBOARD_WRITE) {
72
108
  if (job->format == CLIPBOARD_EMPTY) {
73
109
  result = null_value(env);
110
+ } else if (job->format == CLIPBOARD_PATHS) {
111
+ status = create_clipboard_paths_value(env, job, create_string, &result);
74
112
  } else if (job->format == CLIPBOARD_BUFFER) {
75
113
  napi_create_buffer_copy_fn create_buffer = (napi_create_buffer_copy_fn)node_symbol("napi_create_buffer_copy");
76
114
  status = create_buffer(env, job->length, job->data, 0, &result);
@@ -1,6 +1,6 @@
1
1
  # Darwin native prebuilds
2
2
 
3
- Provides modifier-key state and asynchronous text/image clipboard access using AppKit.
3
+ Provides modifier-key state and asynchronous text, image, and file-path clipboard access using AppKit.
4
4
 
5
5
  ## Building
6
6
 
@@ -66,6 +66,40 @@ static void clipboard_execute(clipboard_job* job) {
66
66
  } else {
67
67
  clipboard_copy(job, text.UTF8String, [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding], CLIPBOARD_UTF8);
68
68
  }
69
+ } else if (job->operation == CLIPBOARD_FILES) {
70
+ NSArray* urls = [pasteboard readObjectsForClasses:@[[NSURL class]]
71
+ options:@{NSPasteboardURLReadingFileURLsOnlyKey: @YES}];
72
+ size_t total = 0;
73
+ for (NSURL* url in urls) {
74
+ const char* path = url.fileSystemRepresentation;
75
+ if (!path) continue;
76
+ size_t length = strlen(path);
77
+ if (length == SIZE_MAX || total > SIZE_MAX - length - 1) {
78
+ job->error = "Clipboard file paths are too large";
79
+ return;
80
+ }
81
+ total += length + 1;
82
+ }
83
+ if (total == 0) {
84
+ job->format = CLIPBOARD_EMPTY;
85
+ return;
86
+ }
87
+ char* paths = clipboard_alloc(total);
88
+ if (!paths) {
89
+ job->error = "Out of memory";
90
+ return;
91
+ }
92
+ char* cursor = paths;
93
+ for (NSURL* url in urls) {
94
+ const char* path = url.fileSystemRepresentation;
95
+ if (!path) continue;
96
+ size_t length = strlen(path);
97
+ memcpy(cursor, path, length + 1);
98
+ cursor += length + 1;
99
+ }
100
+ job->data = paths;
101
+ job->length = total;
102
+ job->format = CLIPBOARD_PATHS;
69
103
  } else {
70
104
  if (![pasteboard availableTypeFromArray:@[ NSPasteboardTypePNG, NSPasteboardTypeTIFF ]]) {
71
105
  job->format = CLIPBOARD_EMPTY;
@@ -84,10 +118,15 @@ static void clipboard_execute(clipboard_job* job) {
84
118
  }
85
119
  }
86
120
 
121
+ static napi_value PI_NAPI_CALL get_clipboard_file_paths(napi_env env, napi_callback_info info) {
122
+ return queue_clipboard(env, info, CLIPBOARD_FILES);
123
+ }
124
+
87
125
  PI_NAPI_EXPORT napi_value napi_register_module_v1(napi_env env, napi_value exports) {
88
126
  set_function_export(env, exports, "isModifierPressed", is_modifier_pressed);
89
127
  set_function_export(env, exports, "getText", get_clipboard_text);
90
128
  set_function_export(env, exports, "setText", set_clipboard_text);
91
129
  set_function_export(env, exports, "getImage", get_clipboard_image);
130
+ set_function_export(env, exports, "getFilePaths", get_clipboard_file_paths);
92
131
  return exports;
93
132
  }
package/native/napi.h CHANGED
@@ -26,6 +26,7 @@ typedef void* napi_deferred;
26
26
  typedef void (PI_NAPI_CALL *napi_async_execute_callback)(napi_env, void*);
27
27
  typedef void (PI_NAPI_CALL *napi_async_complete_callback)(napi_env, int, void*);
28
28
  typedef int (PI_NAPI_CALL *napi_create_async_work_fn)(napi_env, napi_value, napi_value, napi_async_execute_callback, napi_async_complete_callback, void*, napi_async_work*);
29
+ typedef int (PI_NAPI_CALL *napi_create_array_fn)(napi_env, napi_value*);
29
30
  typedef int (PI_NAPI_CALL *napi_async_work_fn)(napi_env, napi_async_work);
30
31
  typedef int (PI_NAPI_CALL *napi_create_promise_fn)(napi_env, napi_deferred*, napi_value*);
31
32
  typedef int (PI_NAPI_CALL *napi_settle_deferred_fn)(napi_env, napi_deferred, napi_value);
@@ -41,6 +42,7 @@ typedef int (PI_NAPI_CALL *napi_get_value_fn)(napi_env, napi_value*);
41
42
  typedef int (PI_NAPI_CALL *napi_get_value_string_utf8_fn)(napi_env, napi_value, char*, size_t, size_t*);
42
43
  typedef int (PI_NAPI_CALL *napi_get_value_string_utf16_fn)(napi_env, napi_value, uint16_t*, size_t, size_t*);
43
44
  typedef int (PI_NAPI_CALL *napi_set_named_property_fn)(napi_env, napi_value, const char*, napi_value);
45
+ typedef int (PI_NAPI_CALL *napi_set_element_fn)(napi_env, napi_value, uint32_t, napi_value);
44
46
  typedef int (PI_NAPI_CALL *napi_throw_error_fn)(napi_env, const char*, const char*);
45
47
 
46
48
  static void* node_symbol(const char* name) {
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@code-yeongyu/senpi-tui",
3
- "version": "2026.9.30",
3
+ "version": "2026.10.1-2",
4
4
  "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "scripts": {
8
8
  "clean": "shx rm -rf dist",
9
- "build": "tsgo -p tsconfig.build.json",
9
+ "build": "tsc -p tsconfig.build.json",
10
10
  "build:native:darwin": "native/darwin/build.sh",
11
11
  "build:native:linux": "native/linux/build.sh",
12
12
  "build:native:win32": "node native/win32/build.mjs",