xgen-dex-cli 1.3.0 → 1.4.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.
@@ -5,14 +5,15 @@ import {
5
5
 
6
6
  // src/tui/index.tsx
7
7
  import { render } from "ink";
8
+ import { stdout } from "node:process";
8
9
 
9
10
  // src/tui/app.tsx
10
11
  import { useCallback, useEffect as useEffect7, useState as useState10 } from "react";
11
- import { Box as Box9, Text as Text10, useApp as useApp2, useInput as useInput9 } from "ink";
12
+ import { Box as Box10, Text as Text10, useApp as useApp2, useInput as useInput9 } from "ink";
12
13
 
13
14
  // src/tui/dashboard.tsx
14
15
  import { useEffect as useEffect5, useReducer, useRef as useRef2, useState as useState6 } from "react";
15
- import { Box as Box5, Text as Text6, useApp, useInput as useInput5 } from "ink";
16
+ import { Box as Box6, Text as Text6, useApp, useInput as useInput5 } from "ink";
16
17
 
17
18
  // src/tui/chat-state.ts
18
19
  var initialChatState = { messages: [], running: false };
@@ -135,16 +136,32 @@ function chatReducer(state, action) {
135
136
 
136
137
  // src/tui/command-palette.tsx
137
138
  import { useState as useState2 } from "react";
138
- import { Box as Box2, Text as Text3, useInput as useInput2 } from "ink";
139
+ import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
139
140
 
140
141
  // src/tui/components.tsx
141
- import { Box, Text as Text2 } from "ink";
142
+ import { Box as Box2, Text as Text2 } from "ink";
142
143
 
143
144
  // src/tui/ime-text-input.tsx
144
145
  import { useEffect, useRef, useState } from "react";
145
- import { Text, useCursor, useInput, useStdout } from "ink";
146
+ import { Box, Text, useCursor, useInput } from "ink";
146
147
  import stringWidth from "string-width";
147
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
148
+
149
+ // src/tui/terminal-input.ts
150
+ var PASTE_START = "[200~";
151
+ var PASTE_END = "[201~";
152
+ var CONTROL_TAIL = /^(?:\[[\d;:<>?!"'$ ]*[@-~]|O[@-~])$/;
153
+ var CONTROL_CHARS = /[\u0000-\u001F\u007F]/g;
154
+ function classifyInput(input, pasting = false) {
155
+ if (input === PASTE_START) return { kind: "paste-start" };
156
+ if (input === PASTE_END) return { kind: "paste-end" };
157
+ if (!pasting && input.length > 1 && CONTROL_TAIL.test(input)) return { kind: "ignore" };
158
+ const flattened = input.replace(/\r\n|[\r\n]/g, " ");
159
+ const text = flattened.replace(CONTROL_CHARS, "");
160
+ return text ? { kind: "text", text } : { kind: "ignore" };
161
+ }
162
+
163
+ // src/tui/ime-text-input.tsx
164
+ import { jsx, jsxs } from "react/jsx-runtime";
148
165
  var segmenter = new Intl.Segmenter("ko", { granularity: "grapheme" });
149
166
  function graphemes(value) {
150
167
  return [...segmenter.segment(value)].map(({ segment }) => segment);
@@ -152,6 +169,19 @@ function graphemes(value) {
152
169
  function clamp(value, minimum, maximum) {
153
170
  return Math.min(Math.max(value, minimum), maximum);
154
171
  }
172
+ function placementOf(node) {
173
+ const width = node?.yogaNode?.getComputedWidth();
174
+ if (!node || width === void 0) return void 0;
175
+ let x = 0;
176
+ let y = 0;
177
+ for (let current = node; current; current = current.parentNode) {
178
+ const yoga = current.yogaNode;
179
+ if (!yoga) continue;
180
+ x += yoga.getComputedLeft();
181
+ y += yoga.getComputedTop();
182
+ }
183
+ return { x, y, width };
184
+ }
155
185
  function visibleInput(segments, cursor, maximumWidth) {
156
186
  let start = cursor;
157
187
  let widthBeforeCursor = 0;
@@ -176,17 +206,26 @@ function visibleInput(segments, cursor, maximumWidth) {
176
206
  cursorWidth: widthBeforeCursor
177
207
  };
178
208
  }
179
- function TerminalCursor({ origin, offset }) {
209
+ function TerminalCursor({ x, y }) {
180
210
  const { setCursorPosition } = useCursor();
181
- setCursorPosition({ x: origin.x + offset, y: origin.y });
211
+ setCursorPosition({ x, y });
182
212
  return null;
183
213
  }
184
214
  function ImeTextInput(props) {
185
- const { stdout } = useStdout();
215
+ const ref = useRef(null);
216
+ const [placement, setPlacement] = useState(void 0);
186
217
  const initialSegments = graphemes(props.value);
187
218
  const [cursor, setCursor] = useState(initialSegments.length);
188
219
  const valueRef = useRef(props.value);
189
220
  const cursorRef = useRef(initialSegments.length);
221
+ const pastingRef = useRef(false);
222
+ useEffect(() => {
223
+ const measured = placementOf(ref.current);
224
+ if (!measured) return;
225
+ if (placement?.x !== measured.x || placement?.y !== measured.y || placement?.width !== measured.width) {
226
+ setPlacement(measured);
227
+ }
228
+ });
190
229
  const moveCursor = (next, length) => {
191
230
  const resolved = clamp(next, 0, length);
192
231
  cursorRef.current = resolved;
@@ -210,6 +249,23 @@ function ImeTextInput(props) {
210
249
  (input, key) => {
211
250
  const current = graphemes(valueRef.current);
212
251
  const currentCursor = clamp(cursorRef.current, 0, current.length);
252
+ const event = classifyInput(input, pastingRef.current);
253
+ if (event.kind === "paste-start") {
254
+ pastingRef.current = true;
255
+ return;
256
+ }
257
+ if (event.kind === "paste-end") {
258
+ pastingRef.current = false;
259
+ return;
260
+ }
261
+ if (pastingRef.current) {
262
+ if (event.kind === "text") {
263
+ const inserted2 = graphemes(event.text);
264
+ current.splice(currentCursor, 0, ...inserted2);
265
+ updateValue(current, currentCursor + inserted2.length);
266
+ }
267
+ return;
268
+ }
213
269
  if (key.return) {
214
270
  props.onSubmit?.(valueRef.current);
215
271
  return;
@@ -236,10 +292,11 @@ function ImeTextInput(props) {
236
292
  updateValue(current, currentCursor - 1);
237
293
  return;
238
294
  }
239
- if (!input || key.ctrl || key.meta || key.tab || key.escape || key.upArrow || key.downArrow || key.pageUp || key.pageDown) {
295
+ if (key.ctrl || key.meta || key.tab || key.escape || key.upArrow || key.downArrow || key.pageUp || key.pageDown) {
240
296
  return;
241
297
  }
242
- const inserted = graphemes(input);
298
+ if (event.kind !== "text") return;
299
+ const inserted = graphemes(event.text);
243
300
  current.splice(currentCursor, 0, ...inserted);
244
301
  updateValue(current, currentCursor + inserted.length);
245
302
  },
@@ -248,18 +305,18 @@ function ImeTextInput(props) {
248
305
  const rawSegments = graphemes(props.value);
249
306
  const safeCursor = clamp(cursor, 0, rawSegments.length);
250
307
  const displayedSegments = props.mask ? rawSegments.map(() => props.mask ?? "") : rawSegments;
251
- const maximumWidth = Math.max(1, (stdout.columns || 100) - props.cursorOrigin.x - 3);
308
+ const maximumWidth = Math.max(1, (placement?.width ?? 40) - 1);
252
309
  const visible = visibleInput(displayedSegments, safeCursor, maximumWidth);
253
- return /* @__PURE__ */ jsxs(Fragment, { children: [
310
+ return /* @__PURE__ */ jsxs(Box, { ref, flexGrow: 1, children: [
254
311
  rawSegments.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: props.placeholder ?? "" }) : /* @__PURE__ */ jsx(Text, { children: visible.text }),
255
- props.focus ? /* @__PURE__ */ jsx(TerminalCursor, { origin: props.cursorOrigin, offset: visible.cursorWidth }) : null
312
+ props.focus && placement ? /* @__PURE__ */ jsx(TerminalCursor, { x: placement.x + visible.cursorWidth, y: placement.y }) : null
256
313
  ] });
257
314
  }
258
315
 
259
316
  // src/tui/components.tsx
260
317
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
261
318
  function Header(props) {
262
- return /* @__PURE__ */ jsxs2(Box, { paddingX: 1, justifyContent: "space-between", children: [
319
+ return /* @__PURE__ */ jsxs2(Box2, { paddingX: 1, justifyContent: "space-between", children: [
263
320
  /* @__PURE__ */ jsx2(Text2, { bold: true, color: "blueBright", children: "XGEN Dex" }),
264
321
  props.profile ? /* @__PURE__ */ jsxs2(Text2, { children: [
265
322
  props.profile,
@@ -272,37 +329,40 @@ function Header(props) {
272
329
  ] });
273
330
  }
274
331
  function Footer({ text }) {
275
- return /* @__PURE__ */ jsx2(Box, { paddingX: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: text }) });
332
+ return /* @__PURE__ */ jsx2(Box2, { paddingX: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: text }) });
276
333
  }
277
334
  function Loading({ label = "\uBD88\uB7EC\uC624\uB294 \uC911..." }) {
278
- return /* @__PURE__ */ jsx2(Box, { padding: 1, children: /* @__PURE__ */ jsxs2(Text2, { color: "cyan", children: [
335
+ return /* @__PURE__ */ jsx2(Box2, { padding: 1, children: /* @__PURE__ */ jsxs2(Text2, { color: "cyan", children: [
279
336
  "\u25C6 ",
280
337
  label
281
338
  ] }) });
282
339
  }
283
340
  function Notice({ children, error = false }) {
284
- return /* @__PURE__ */ jsx2(Box, { borderStyle: "round", borderColor: error ? "red" : "cyan", paddingX: 1, children: /* @__PURE__ */ jsx2(Text2, { color: error ? "red" : void 0, children }) });
341
+ return /* @__PURE__ */ jsx2(Box2, { borderStyle: "round", borderColor: error ? "red" : "cyan", paddingX: 1, children: /* @__PURE__ */ jsx2(Text2, { color: error ? "red" : void 0, children }) });
285
342
  }
286
343
  function FormField(props) {
287
- return /* @__PURE__ */ jsxs2(Box, { children: [
288
- /* @__PURE__ */ jsx2(Box, { width: 14, children: /* @__PURE__ */ jsxs2(Text2, { color: props.focus ? "cyan" : void 0, children: [
289
- props.focus ? "\u203A" : " ",
290
- " ",
291
- props.label
292
- ] }) }),
293
- /* @__PURE__ */ jsx2(
294
- ImeTextInput,
295
- {
296
- value: props.value,
297
- onChange: props.onChange,
298
- onSubmit: props.onSubmit,
299
- focus: props.focus,
300
- cursorOrigin: props.cursorOrigin,
301
- placeholder: props.placeholder,
302
- mask: props.secret ? "\u2022" : void 0
303
- }
304
- )
305
- ] });
344
+ return (
345
+ // 가로로 늘려 둬야 입력 칸이 남는 폭을 있다. 내용만큼만 넓으면 칸의 폭이
346
+ // 글자 폭이 되어, 긴 주소가 스스로를 잘라 내는 꼴이 된다.
347
+ /* @__PURE__ */ jsxs2(Box2, { flexGrow: 1, children: [
348
+ /* @__PURE__ */ jsx2(Box2, { width: 14, flexShrink: 0, children: /* @__PURE__ */ jsxs2(Text2, { color: props.focus ? "cyan" : void 0, children: [
349
+ props.focus ? "\u203A" : " ",
350
+ " ",
351
+ props.label
352
+ ] }) }),
353
+ /* @__PURE__ */ jsx2(
354
+ ImeTextInput,
355
+ {
356
+ value: props.value,
357
+ onChange: props.onChange,
358
+ onSubmit: props.onSubmit,
359
+ focus: props.focus,
360
+ placeholder: props.placeholder,
361
+ mask: props.secret ? "\u2022" : void 0
362
+ }
363
+ )
364
+ ] })
365
+ );
306
366
  }
307
367
 
308
368
  // src/tui/command-palette.tsx
@@ -315,9 +375,9 @@ function CommandPalette(props) {
315
375
  if (key.downArrow) setCursor((current) => Math.min(props.actions.length - 1, current + 1));
316
376
  if (key.return && props.actions[cursor]) props.actions[cursor].run();
317
377
  });
318
- return /* @__PURE__ */ jsxs3(Box2, { flexDirection: "column", flexGrow: 1, borderStyle: "double", borderColor: "magenta", padding: 1, children: [
378
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", flexGrow: 1, borderStyle: "double", borderColor: "magenta", padding: 1, children: [
319
379
  /* @__PURE__ */ jsx3(Text3, { bold: true, children: "\uBA85\uB839" }),
320
- /* @__PURE__ */ jsx3(Box2, { flexDirection: "column", marginTop: 1, children: props.actions.map((action, index) => /* @__PURE__ */ jsxs3(Text3, { color: index === cursor ? "magentaBright" : void 0, children: [
380
+ /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", marginTop: 1, children: props.actions.map((action, index) => /* @__PURE__ */ jsxs3(Text3, { color: index === cursor ? "magentaBright" : void 0, children: [
321
381
  index === cursor ? "\u203A" : " ",
322
382
  " ",
323
383
  action.label
@@ -328,7 +388,7 @@ function CommandPalette(props) {
328
388
 
329
389
  // src/tui/history-screen.tsx
330
390
  import { useEffect as useEffect2, useState as useState3 } from "react";
331
- import { Box as Box3, Text as Text4, useInput as useInput3 } from "ink";
391
+ import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
332
392
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
333
393
  function HistoryScreen(props) {
334
394
  const [items, setItems] = useState3([]);
@@ -363,7 +423,7 @@ function HistoryScreen(props) {
363
423
  },
364
424
  { isActive: !loading }
365
425
  );
366
- return /* @__PURE__ */ jsxs4(Box3, { flexDirection: "column", flexGrow: 1, borderStyle: "round", borderColor: "cyan", padding: 1, children: [
426
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", flexGrow: 1, borderStyle: "round", borderColor: "cyan", padding: 1, children: [
367
427
  /* @__PURE__ */ jsx4(Text4, { bold: true, children: "\uB300\uD654 \uAE30\uB85D" }),
368
428
  loading ? /* @__PURE__ */ jsx4(Loading, {}) : null,
369
429
  !loading && items.length === 0 ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\uB300\uD654 \uAE30\uB85D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." }) : null,
@@ -385,7 +445,7 @@ function HistoryScreen(props) {
385
445
 
386
446
  // src/tui/start-panel.tsx
387
447
  import { useEffect as useEffect3, useState as useState4 } from "react";
388
- import { Box as Box4, Text as Text5, useInput as useInput4 } from "ink";
448
+ import { Box as Box5, Text as Text5, useInput as useInput4 } from "ink";
389
449
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
390
450
  function StartPanel(props) {
391
451
  const rows = [
@@ -428,10 +488,10 @@ function StartPanel(props) {
428
488
  },
429
489
  { isActive: !opening }
430
490
  );
431
- return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", flexGrow: 1, borderStyle: "round", borderColor: "cyan", padding: 1, children: [
491
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", flexGrow: 1, borderStyle: "round", borderColor: "cyan", padding: 1, children: [
432
492
  /* @__PURE__ */ jsx5(Text5, { bold: true, children: props.agentName }),
433
493
  /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\uC5B4\uB5BB\uAC8C \uC2DC\uC791\uD560\uAE4C\uC694?" }),
434
- /* @__PURE__ */ jsx5(Box4, { flexDirection: "column", marginTop: 1, children: rows.map((row, index) => {
494
+ /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginTop: 1, children: rows.map((row, index) => {
435
495
  const active = index === cursor;
436
496
  const mark = active ? "\u203A" : " ";
437
497
  if (row.kind === "new") {
@@ -456,7 +516,7 @@ function StartPanel(props) {
456
516
  }) }),
457
517
  opening ? /* @__PURE__ */ jsx5(Loading, { label: "\uB300\uD654\uB97C \uBD88\uB7EC\uC624\uB294 \uC911..." }) : null,
458
518
  error ? /* @__PURE__ */ jsx5(Notice, { error: true, children: error }) : null,
459
- /* @__PURE__ */ jsx5(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2191\u2193 \uC774\uB3D9 \xB7 Enter \uC120\uD0DD \xB7 Esc \uBAA9\uB85D\uC73C\uB85C" }) })
519
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2191\u2193 \uC774\uB3D9 \xB7 Enter \uC120\uD0DD \xB7 Esc \uBAA9\uB85D\uC73C\uB85C" }) })
460
520
  ] });
461
521
  }
462
522
  function when(conversation) {
@@ -471,22 +531,22 @@ function when(conversation) {
471
531
 
472
532
  // src/tui/use-terminal-size.ts
473
533
  import { useEffect as useEffect4, useState as useState5 } from "react";
474
- import { useStdout as useStdout2 } from "ink";
534
+ import { useStdout } from "ink";
475
535
  function useTerminalSize() {
476
- const { stdout } = useStdout2();
536
+ const { stdout: stdout2 } = useStdout();
477
537
  const read = () => {
478
- const columns = stdout.columns || 100;
479
- const rows = stdout.rows || 30;
538
+ const columns = stdout2.columns || 100;
539
+ const rows = stdout2.rows || 30;
480
540
  return { columns, rows, wide: columns >= 88 };
481
541
  };
482
542
  const [size, setSize] = useState5(read);
483
543
  useEffect4(() => {
484
544
  const resize = () => setSize(read());
485
- stdout.on("resize", resize);
545
+ stdout2.on("resize", resize);
486
546
  return () => {
487
- stdout.off("resize", resize);
547
+ stdout2.off("resize", resize);
488
548
  };
489
- }, [stdout]);
549
+ }, [stdout2]);
490
550
  return size;
491
551
  }
492
552
 
@@ -496,7 +556,7 @@ function AgentSidebar(props) {
496
556
  const radius = Math.max(3, Math.floor((props.height - 4) / 2));
497
557
  const start = Math.max(0, props.cursor - radius);
498
558
  const visible = props.agents.slice(start, start + radius * 2 + 1);
499
- return /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", width: 30, borderStyle: "round", borderColor: props.focused ? "cyan" : "gray", paddingX: 1, children: [
559
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", width: 30, borderStyle: "round", borderColor: props.focused ? "cyan" : "gray", paddingX: 1, children: [
500
560
  /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Agents" }),
501
561
  visible.map((agent) => {
502
562
  const index = props.agents.indexOf(agent);
@@ -529,11 +589,11 @@ function labelOf(role, agentName) {
529
589
  function ChatPane(props) {
530
590
  const visibleCount = Math.max(4, Math.floor((props.height - 5) / 2));
531
591
  const visible = props.messages.slice(-visibleCount);
532
- return /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", flexGrow: 1, borderStyle: "round", borderColor: "blue", paddingX: 1, children: [
592
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", flexGrow: 1, borderStyle: "round", borderColor: "blue", paddingX: 1, children: [
533
593
  /* @__PURE__ */ jsx6(Text6, { bold: true, children: props.agent?.workflowName ?? "Agent\uB97C \uC120\uD0DD\uD558\uC138\uC694" }),
534
- /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", flexGrow: 1, children: [
594
+ /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", flexGrow: 1, children: [
535
595
  visible.length === 0 ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: props.agent ? "\uBA54\uC2DC\uC9C0\uB97C \uC785\uB825\uD574 \uB300\uD654\uB97C \uC2DC\uC791\uD558\uC138\uC694." : "\uC67C\uCABD\uC5D0\uC11C Agent\uB97C \uC120\uD0DD\uD558\uC138\uC694." }) : null,
536
- visible.map((message) => /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", marginTop: message.role === "activity" ? 0 : 1, children: [
596
+ visible.map((message) => /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginTop: message.role === "activity" ? 0 : 1, children: [
537
597
  /* @__PURE__ */ jsx6(Text6, { bold: true, color: messageColor(message.role), children: labelOf(message.role, props.agent?.workflowName ?? "Agent") }),
538
598
  /* @__PURE__ */ jsx6(Text6, { dimColor: message.role === "activity", children: message.text || (message.role === "assistant" ? "\u2026" : "") })
539
599
  ] }, message.id))
@@ -545,7 +605,7 @@ function ChatPane(props) {
545
605
  ] });
546
606
  }
547
607
  function Composer(props) {
548
- return /* @__PURE__ */ jsxs6(Box5, { borderStyle: "round", borderColor: props.focused ? "cyan" : "gray", paddingX: 1, children: [
608
+ return /* @__PURE__ */ jsxs6(Box6, { borderStyle: "round", borderColor: props.focused ? "cyan" : "gray", paddingX: 1, children: [
549
609
  /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "\u203A " }),
550
610
  props.disabled ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\uC751\uB2F5\uC744 \uAE30\uB2E4\uB9AC\uB294 \uC911..." }) : /* @__PURE__ */ jsx6(
551
611
  ImeTextInput,
@@ -554,7 +614,6 @@ function Composer(props) {
554
614
  onChange: props.onChange,
555
615
  onSubmit: props.onSubmit,
556
616
  focus: props.focused,
557
- cursorOrigin: props.cursorOrigin,
558
617
  placeholder: "\uBA54\uC2DC\uC9C0\uB97C \uC785\uB825\uD558\uC138\uC694"
559
618
  }
560
619
  )
@@ -743,7 +802,7 @@ function Dashboard(props) {
743
802
  setFocus("agents");
744
803
  }
745
804
  }
746
- ) : /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", flexGrow: 1, children: [
805
+ ) : /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", flexGrow: 1, children: [
747
806
  /* @__PURE__ */ jsx6(ChatPane, { agent: selected, messages: chat.messages, status: chat.status, height: bodyHeight - 3 }),
748
807
  /* @__PURE__ */ jsx6(
749
808
  Composer,
@@ -752,17 +811,16 @@ function Dashboard(props) {
752
811
  onChange: setInput,
753
812
  onSubmit: (value) => void send(value),
754
813
  focused: focus === "composer",
755
- disabled: chat.running || !selected,
756
- cursorOrigin: { x: size.wide ? 34 : 4, y: bodyHeight - 1 }
814
+ disabled: chat.running || !selected
757
815
  }
758
816
  )
759
817
  ] });
760
- body = size.wide ? /* @__PURE__ */ jsxs6(Box5, { height: bodyHeight, children: [
818
+ body = size.wide ? /* @__PURE__ */ jsxs6(Box6, { height: bodyHeight, children: [
761
819
  sidebar,
762
820
  conversation
763
- ] }) : focus === "agents" ? /* @__PURE__ */ jsx6(Box5, { height: bodyHeight, children: sidebar }) : /* @__PURE__ */ jsx6(Box5, { height: bodyHeight, children: conversation });
821
+ ] }) : focus === "agents" ? /* @__PURE__ */ jsx6(Box6, { height: bodyHeight, children: sidebar }) : /* @__PURE__ */ jsx6(Box6, { height: bodyHeight, children: conversation });
764
822
  }
765
- return /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", children: [
823
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
766
824
  /* @__PURE__ */ jsx6(
767
825
  Header,
768
826
  {
@@ -778,7 +836,7 @@ function Dashboard(props) {
778
836
 
779
837
  // src/tui/login-screen.tsx
780
838
  import { useState as useState7 } from "react";
781
- import { Box as Box6, Text as Text7, useInput as useInput6 } from "ink";
839
+ import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
782
840
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
783
841
  function LoginScreen(props) {
784
842
  const [email, setEmail] = useState7("");
@@ -803,16 +861,16 @@ function LoginScreen(props) {
803
861
  props.onSubmit(email.trim(), secret);
804
862
  }
805
863
  };
806
- return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", children: [
864
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
807
865
  /* @__PURE__ */ jsx7(Header, { profile: props.profile, connected: false }),
808
- /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "blue", padding: 1, children: [
866
+ /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", borderStyle: "round", borderColor: "blue", padding: 1, children: [
809
867
  /* @__PURE__ */ jsx7(Text7, { bold: true, children: "\uB85C\uADF8\uC778" }),
810
868
  /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
811
869
  props.serverUrl,
812
870
  " ",
813
871
  /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: "(Ctrl+E \uBC14\uAFB8\uAE30)" })
814
872
  ] }),
815
- /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", marginTop: 1, children: [
873
+ /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginTop: 1, children: [
816
874
  /* @__PURE__ */ jsx7(
817
875
  FormField,
818
876
  {
@@ -821,7 +879,6 @@ function LoginScreen(props) {
821
879
  onChange: setEmail,
822
880
  onSubmit: () => setFocus("password"),
823
881
  focus: !props.busy && focus === "email",
824
- cursorOrigin: { x: 16, y: 6 },
825
882
  placeholder: "me@corp.com"
826
883
  }
827
884
  ),
@@ -833,7 +890,6 @@ function LoginScreen(props) {
833
890
  onChange: setPassword,
834
891
  onSubmit: submit,
835
892
  focus: !props.busy && focus === "password",
836
- cursorOrigin: { x: 16, y: 7 },
837
893
  secret: true
838
894
  }
839
895
  )
@@ -847,7 +903,7 @@ function LoginScreen(props) {
847
903
 
848
904
  // src/tui/profile-screen.tsx
849
905
  import { useEffect as useEffect6, useState as useState8 } from "react";
850
- import { Box as Box7, Text as Text8, useInput as useInput7 } from "ink";
906
+ import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
851
907
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
852
908
  function ProfileScreen(props) {
853
909
  const [cursor, setCursor] = useState8(Math.max(0, props.profiles.findIndex((profile) => profile.current)));
@@ -886,11 +942,11 @@ function ProfileScreen(props) {
886
942
  }
887
943
  if (name.trim() && serverUrl.trim()) props.onCreate(name.trim(), serverUrl.trim());
888
944
  };
889
- return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
945
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
890
946
  /* @__PURE__ */ jsx8(Header, {}),
891
- /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", padding: 1, children: [
947
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", padding: 1, children: [
892
948
  /* @__PURE__ */ jsx8(Text8, { bold: true, children: creating ? "\uC0C8 \uD504\uB85C\uD544" : "\uD504\uB85C\uD544 \uC804\uD658" }),
893
- creating ? /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", marginTop: 1, children: [
949
+ creating ? /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
894
950
  /* @__PURE__ */ jsx8(
895
951
  FormField,
896
952
  {
@@ -899,7 +955,6 @@ function ProfileScreen(props) {
899
955
  onChange: setName,
900
956
  onSubmit: () => setFocus("url"),
901
957
  focus: !props.busy && focus === "name",
902
- cursorOrigin: { x: 16, y: 5 },
903
958
  placeholder: "corp"
904
959
  }
905
960
  ),
@@ -911,11 +966,10 @@ function ProfileScreen(props) {
911
966
  onChange: setServerUrl,
912
967
  onSubmit: create,
913
968
  focus: !props.busy && focus === "url",
914
- cursorOrigin: { x: 16, y: 6 },
915
969
  placeholder: "https://xgen.example.com"
916
970
  }
917
971
  )
918
- ] }) : /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", marginTop: 1, children: [
972
+ ] }) : /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
919
973
  props.profiles.map((profile, index) => /* @__PURE__ */ jsxs8(Text8, { color: index === cursor ? "cyan" : void 0, children: [
920
974
  index === cursor ? "\u203A" : " ",
921
975
  " ",
@@ -942,7 +996,7 @@ function ProfileScreen(props) {
942
996
 
943
997
  // src/tui/server-screen.tsx
944
998
  import { useState as useState9 } from "react";
945
- import { Box as Box8, Text as Text9, useInput as useInput8 } from "ink";
999
+ import { Box as Box9, Text as Text9, useInput as useInput8 } from "ink";
946
1000
  import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
947
1001
  function ServerScreen(props) {
948
1002
  const [serverUrl, setServerUrl] = useState9(props.initialValue ?? "");
@@ -953,12 +1007,12 @@ function ServerScreen(props) {
953
1007
  },
954
1008
  { isActive: !props.busy && !!props.onCancel }
955
1009
  );
956
- return /* @__PURE__ */ jsxs9(Box8, { flexDirection: "column", children: [
1010
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
957
1011
  /* @__PURE__ */ jsx9(Header, { profile: props.profile, connected: false }),
958
- /* @__PURE__ */ jsxs9(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "blue", padding: 1, children: [
1012
+ /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", borderStyle: "round", borderColor: "blue", padding: 1, children: [
959
1013
  /* @__PURE__ */ jsx9(Text9, { bold: true, children: editing ? "\uC11C\uBC84 \uC8FC\uC18C \uBC14\uAFB8\uAE30" : "\uCC98\uC74C \uC624\uC168\uAD70\uC694" }),
960
1014
  /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "\uC5F0\uACB0\uD560 XGEN Gateway \uC8FC\uC18C\uB97C \uC785\uB825\uD558\uC138\uC694." }),
961
- /* @__PURE__ */ jsx9(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx9(
1015
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(
962
1016
  FormField,
963
1017
  {
964
1018
  label: "Server URL",
@@ -966,7 +1020,6 @@ function ServerScreen(props) {
966
1020
  onChange: setServerUrl,
967
1021
  onSubmit: (value) => value.trim() && props.onSubmit(value.trim()),
968
1022
  focus: !props.busy,
969
- cursorOrigin: { x: 16, y: 6 },
970
1023
  placeholder: "xgen.example.com"
971
1024
  }
972
1025
  ) }),
@@ -1119,7 +1172,7 @@ function App({ engine }) {
1119
1172
  }
1120
1173
  };
1121
1174
  if (route === "boot") {
1122
- return /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", children: [
1175
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
1123
1176
  /* @__PURE__ */ jsx10(Header, {}),
1124
1177
  /* @__PURE__ */ jsx10(Loading, { label: "Dex\uB97C \uC900\uBE44\uD558\uB294 \uC911..." }),
1125
1178
  /* @__PURE__ */ jsx10(Footer, { text: "Ctrl+Q \uC885\uB8CC" })
@@ -1184,7 +1237,7 @@ function App({ engine }) {
1184
1237
  }
1185
1238
  );
1186
1239
  }
1187
- return /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", children: [
1240
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
1188
1241
  /* @__PURE__ */ jsx10(Header, {}),
1189
1242
  /* @__PURE__ */ jsx10(Notice, { error: true, children: error ?? "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4." }),
1190
1243
  /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "\uC11C\uBC84\uC640 \uD0A4\uCCB4\uC778 \uC0C1\uD0DC\uB97C \uD655\uC778\uD55C \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694." }),
@@ -1199,16 +1252,71 @@ function RetryInput({ onRetry }) {
1199
1252
  return null;
1200
1253
  }
1201
1254
 
1255
+ // src/tui/screen.ts
1256
+ var ESC = "\x1B";
1257
+ var ENTER_ALT_SCREEN = `${ESC}[?1049h`;
1258
+ var LEAVE_ALT_SCREEN = `${ESC}[?1049l`;
1259
+ var SHOW_CURSOR = `${ESC}[?25h`;
1260
+ var DISABLE_REPORTS = [
1261
+ `${ESC}[?1004l`,
1262
+ // 포커스 들어옴/나감
1263
+ `${ESC}[?1000l`,
1264
+ // 마우스 클릭
1265
+ `${ESC}[?1002l`,
1266
+ // 마우스 드래그
1267
+ `${ESC}[?1003l`,
1268
+ // 마우스 이동 전부
1269
+ `${ESC}[?1006l`
1270
+ // SGR 확장 좌표
1271
+ ].join("");
1272
+ var ENABLE_BRACKETED_PASTE = `${ESC}[?2004h`;
1273
+ var DISABLE_BRACKETED_PASTE = `${ESC}[?2004l`;
1274
+ function createScreenGuard(stream) {
1275
+ const alt = Boolean(stream.isTTY);
1276
+ let entered = false;
1277
+ let restored = false;
1278
+ return {
1279
+ enter() {
1280
+ if (entered || !alt) return;
1281
+ entered = true;
1282
+ stream.write(ENTER_ALT_SCREEN + DISABLE_REPORTS + ENABLE_BRACKETED_PASTE);
1283
+ },
1284
+ restore() {
1285
+ if (restored) return;
1286
+ restored = true;
1287
+ if (entered) stream.write(DISABLE_BRACKETED_PASTE + LEAVE_ALT_SCREEN);
1288
+ stream.write(SHOW_CURSOR);
1289
+ }
1290
+ };
1291
+ }
1292
+
1202
1293
  // src/tui/index.tsx
1203
1294
  import { jsx as jsx11 } from "react/jsx-runtime";
1204
1295
  async function runTui(engine) {
1205
- const instance = render(/* @__PURE__ */ jsx11(App, { engine }), {
1206
- exitOnCtrlC: true,
1207
- patchConsole: true
1208
- });
1209
- await instance.waitUntilExit();
1296
+ const screen = createScreenGuard(stdout);
1297
+ const restore = () => screen.restore();
1298
+ const onSignal = () => {
1299
+ restore();
1300
+ process.exit(0);
1301
+ };
1302
+ process.once("exit", restore);
1303
+ process.once("SIGINT", onSignal);
1304
+ process.once("SIGTERM", onSignal);
1305
+ screen.enter();
1306
+ try {
1307
+ const instance = render(/* @__PURE__ */ jsx11(App, { engine }), {
1308
+ exitOnCtrlC: true,
1309
+ patchConsole: true
1310
+ });
1311
+ await instance.waitUntilExit();
1312
+ } finally {
1313
+ restore();
1314
+ process.off("exit", restore);
1315
+ process.off("SIGINT", onSignal);
1316
+ process.off("SIGTERM", onSignal);
1317
+ }
1210
1318
  }
1211
1319
  export {
1212
1320
  runTui
1213
1321
  };
1214
- //# sourceMappingURL=tui-GDC2NODL.js.map
1322
+ //# sourceMappingURL=tui-HKY3YBAY.js.map