xgen-dex-cli 1.2.2 → 1.4.0

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.
@@ -1,18 +1,19 @@
1
1
  import {
2
2
  DexError,
3
3
  publicError
4
- } from "./chunk-GS2Q7ZZA.js";
4
+ } from "./chunk-ULMEXJOR.js";
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
- import { useCallback, useEffect as useEffect6, useState as useState9 } from "react";
11
- import { Box as Box8, Text as Text9, useApp as useApp2, useInput as useInput7 } from "ink";
11
+ import { useCallback, useEffect as useEffect7, useState as useState10 } from "react";
12
+ import { Box as Box9, Text as Text10, useApp as useApp2, useInput as useInput9 } from "ink";
12
13
 
13
14
  // src/tui/dashboard.tsx
14
- import { useEffect as useEffect4, useReducer, useRef as useRef2, useState as useState5 } from "react";
15
- import { Box as Box4, Text as Text5, useApp, useInput as useInput4 } from "ink";
15
+ import { useEffect as useEffect5, useReducer, useRef as useRef2, useState as useState6 } from "react";
16
+ import { Box as Box5, 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 };
@@ -182,7 +183,7 @@ function TerminalCursor({ origin, offset }) {
182
183
  return null;
183
184
  }
184
185
  function ImeTextInput(props) {
185
- const { stdout } = useStdout();
186
+ const { stdout: stdout2 } = useStdout();
186
187
  const initialSegments = graphemes(props.value);
187
188
  const [cursor, setCursor] = useState(initialSegments.length);
188
189
  const valueRef = useRef(props.value);
@@ -248,7 +249,7 @@ function ImeTextInput(props) {
248
249
  const rawSegments = graphemes(props.value);
249
250
  const safeCursor = clamp(cursor, 0, rawSegments.length);
250
251
  const displayedSegments = props.mask ? rawSegments.map(() => props.mask ?? "") : rawSegments;
251
- const maximumWidth = Math.max(1, (stdout.columns || 100) - props.cursorOrigin.x - 3);
252
+ const maximumWidth = Math.max(1, (stdout2.columns || 100) - props.cursorOrigin.x - 3);
252
253
  const visible = visibleInput(displayedSegments, safeCursor, maximumWidth);
253
254
  return /* @__PURE__ */ jsxs(Fragment, { children: [
254
255
  rawSegments.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: props.placeholder ?? "" }) : /* @__PURE__ */ jsx(Text, { children: visible.text }),
@@ -383,40 +384,126 @@ function HistoryScreen(props) {
383
384
  ] });
384
385
  }
385
386
 
386
- // src/tui/use-terminal-size.ts
387
+ // src/tui/start-panel.tsx
387
388
  import { useEffect as useEffect3, useState as useState4 } from "react";
389
+ import { Box as Box4, Text as Text5, useInput as useInput4 } from "ink";
390
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
391
+ function StartPanel(props) {
392
+ const rows = [
393
+ { kind: "new" },
394
+ ...props.conversations.map((conversation) => ({ kind: "conversation", conversation }))
395
+ ];
396
+ const [cursor, setCursor] = useState4(0);
397
+ const [opening, setOpening] = useState4(false);
398
+ const [error, setError] = useState4();
399
+ useEffect3(() => {
400
+ setCursor((current) => Math.min(current, Math.max(0, rows.length - 1)));
401
+ }, [rows.length]);
402
+ useInput4(
403
+ (_input, key) => {
404
+ if (key.escape) {
405
+ props.onCancel();
406
+ return;
407
+ }
408
+ if (key.upArrow) setCursor((current) => Math.max(0, current - 1));
409
+ if (key.downArrow) setCursor((current) => Math.min(rows.length - 1, current + 1));
410
+ if (!key.return) return;
411
+ const row = rows[cursor];
412
+ if (!row) return;
413
+ if (row.kind === "new" || !row.conversation) {
414
+ props.onNew();
415
+ return;
416
+ }
417
+ const conversation = row.conversation;
418
+ setOpening(true);
419
+ setError(void 0);
420
+ props.engine.historyTurns(
421
+ conversation.workflowId,
422
+ conversation.interactionId,
423
+ conversation.workflowName,
424
+ props.profile
425
+ ).then((turns) => props.onOpen(conversation, turns)).catch((reason) => {
426
+ setError(publicError(reason).message);
427
+ setOpening(false);
428
+ });
429
+ },
430
+ { isActive: !opening }
431
+ );
432
+ return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", flexGrow: 1, borderStyle: "round", borderColor: "cyan", padding: 1, children: [
433
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: props.agentName }),
434
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\uC5B4\uB5BB\uAC8C \uC2DC\uC791\uD560\uAE4C\uC694?" }),
435
+ /* @__PURE__ */ jsx5(Box4, { flexDirection: "column", marginTop: 1, children: rows.map((row, index) => {
436
+ const active = index === cursor;
437
+ const mark = active ? "\u203A" : " ";
438
+ if (row.kind === "new") {
439
+ return /* @__PURE__ */ jsxs5(Text5, { color: active ? "cyan" : void 0, children: [
440
+ mark,
441
+ " \uC0C8 \uB300\uD654"
442
+ ] }, "new");
443
+ }
444
+ const conversation = row.conversation;
445
+ if (!conversation) return null;
446
+ return /* @__PURE__ */ jsxs5(Text5, { color: active ? "cyan" : void 0, children: [
447
+ mark,
448
+ " ",
449
+ when(conversation),
450
+ " ",
451
+ /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
452
+ "\xB7 ",
453
+ conversation.interactionCount,
454
+ "\uD134"
455
+ ] })
456
+ ] }, conversation.interactionId);
457
+ }) }),
458
+ opening ? /* @__PURE__ */ jsx5(Loading, { label: "\uB300\uD654\uB97C \uBD88\uB7EC\uC624\uB294 \uC911..." }) : null,
459
+ error ? /* @__PURE__ */ jsx5(Notice, { error: true, children: error }) : null,
460
+ /* @__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" }) })
461
+ ] });
462
+ }
463
+ function when(conversation) {
464
+ const raw = conversation.updatedAt || conversation.createdAt;
465
+ const date = new Date(raw);
466
+ if (Number.isNaN(date.getTime())) return raw || "(\uC2DC\uAC01 \uC5C6\uC74C)";
467
+ const now = /* @__PURE__ */ new Date();
468
+ const sameDay = date.getFullYear() === now.getFullYear() && date.getMonth() === now.getMonth() && date.getDate() === now.getDate();
469
+ const pad = (n) => String(n).padStart(2, "0");
470
+ return sameDay ? `\uC624\uB298 ${pad(date.getHours())}:${pad(date.getMinutes())}` : `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
471
+ }
472
+
473
+ // src/tui/use-terminal-size.ts
474
+ import { useEffect as useEffect4, useState as useState5 } from "react";
388
475
  import { useStdout as useStdout2 } from "ink";
389
476
  function useTerminalSize() {
390
- const { stdout } = useStdout2();
477
+ const { stdout: stdout2 } = useStdout2();
391
478
  const read = () => {
392
- const columns = stdout.columns || 100;
393
- const rows = stdout.rows || 30;
479
+ const columns = stdout2.columns || 100;
480
+ const rows = stdout2.rows || 30;
394
481
  return { columns, rows, wide: columns >= 88 };
395
482
  };
396
- const [size, setSize] = useState4(read);
397
- useEffect3(() => {
483
+ const [size, setSize] = useState5(read);
484
+ useEffect4(() => {
398
485
  const resize = () => setSize(read());
399
- stdout.on("resize", resize);
486
+ stdout2.on("resize", resize);
400
487
  return () => {
401
- stdout.off("resize", resize);
488
+ stdout2.off("resize", resize);
402
489
  };
403
- }, [stdout]);
490
+ }, [stdout2]);
404
491
  return size;
405
492
  }
406
493
 
407
494
  // src/tui/dashboard.tsx
408
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
495
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
409
496
  function AgentSidebar(props) {
410
497
  const radius = Math.max(3, Math.floor((props.height - 4) / 2));
411
498
  const start = Math.max(0, props.cursor - radius);
412
499
  const visible = props.agents.slice(start, start + radius * 2 + 1);
413
- return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", width: 30, borderStyle: "round", borderColor: props.focused ? "cyan" : "gray", paddingX: 1, children: [
414
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Agents" }),
500
+ return /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", width: 30, borderStyle: "round", borderColor: props.focused ? "cyan" : "gray", paddingX: 1, children: [
501
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Agents" }),
415
502
  visible.map((agent) => {
416
503
  const index = props.agents.indexOf(agent);
417
504
  const cursor = index === props.cursor;
418
505
  const selected = agent.workflowId === props.selected;
419
- return /* @__PURE__ */ jsxs5(Text5, { color: cursor && props.focused ? "cyan" : void 0, wrap: "truncate-end", children: [
506
+ return /* @__PURE__ */ jsxs6(Text6, { color: cursor && props.focused ? "cyan" : void 0, wrap: "truncate-end", children: [
420
507
  cursor ? "\u203A" : " ",
421
508
  " ",
422
509
  selected ? "\u25CF" : "\u25CB",
@@ -424,7 +511,7 @@ function AgentSidebar(props) {
424
511
  agent.workflowName
425
512
  ] }, agent.workflowId);
426
513
  }),
427
- props.agents.length === 0 ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\uC0AC\uC6A9 \uAC00\uB2A5\uD55C Agent\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }) : null
514
+ props.agents.length === 0 ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\uC0AC\uC6A9 \uAC00\uB2A5\uD55C Agent\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }) : null
428
515
  ] });
429
516
  }
430
517
  function messageColor(role) {
@@ -443,25 +530,25 @@ function labelOf(role, agentName) {
443
530
  function ChatPane(props) {
444
531
  const visibleCount = Math.max(4, Math.floor((props.height - 5) / 2));
445
532
  const visible = props.messages.slice(-visibleCount);
446
- return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", flexGrow: 1, borderStyle: "round", borderColor: "blue", paddingX: 1, children: [
447
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: props.agent?.workflowName ?? "Agent\uB97C \uC120\uD0DD\uD558\uC138\uC694" }),
448
- /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", flexGrow: 1, children: [
449
- visible.length === 0 ? /* @__PURE__ */ jsx5(Text5, { 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,
450
- visible.map((message) => /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", marginTop: message.role === "activity" ? 0 : 1, children: [
451
- /* @__PURE__ */ jsx5(Text5, { bold: true, color: messageColor(message.role), children: labelOf(message.role, props.agent?.workflowName ?? "Agent") }),
452
- /* @__PURE__ */ jsx5(Text5, { dimColor: message.role === "activity", children: message.text || (message.role === "assistant" ? "\u2026" : "") })
533
+ return /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", flexGrow: 1, borderStyle: "round", borderColor: "blue", paddingX: 1, children: [
534
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: props.agent?.workflowName ?? "Agent\uB97C \uC120\uD0DD\uD558\uC138\uC694" }),
535
+ /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", flexGrow: 1, children: [
536
+ 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,
537
+ visible.map((message) => /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", marginTop: message.role === "activity" ? 0 : 1, children: [
538
+ /* @__PURE__ */ jsx6(Text6, { bold: true, color: messageColor(message.role), children: labelOf(message.role, props.agent?.workflowName ?? "Agent") }),
539
+ /* @__PURE__ */ jsx6(Text6, { dimColor: message.role === "activity", children: message.text || (message.role === "assistant" ? "\u2026" : "") })
453
540
  ] }, message.id))
454
541
  ] }),
455
- props.status ? /* @__PURE__ */ jsxs5(Text5, { color: "yellow", children: [
542
+ props.status ? /* @__PURE__ */ jsxs6(Text6, { color: "yellow", children: [
456
543
  "\u25C6 ",
457
544
  props.status
458
545
  ] }) : null
459
546
  ] });
460
547
  }
461
548
  function Composer(props) {
462
- return /* @__PURE__ */ jsxs5(Box4, { borderStyle: "round", borderColor: props.focused ? "cyan" : "gray", paddingX: 1, children: [
463
- /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "\u203A " }),
464
- props.disabled ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\uC751\uB2F5\uC744 \uAE30\uB2E4\uB9AC\uB294 \uC911..." }) : /* @__PURE__ */ jsx5(
549
+ return /* @__PURE__ */ jsxs6(Box5, { borderStyle: "round", borderColor: props.focused ? "cyan" : "gray", paddingX: 1, children: [
550
+ /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "\u203A " }),
551
+ props.disabled ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\uC751\uB2F5\uC744 \uAE30\uB2E4\uB9AC\uB294 \uC911..." }) : /* @__PURE__ */ jsx6(
465
552
  ImeTextInput,
466
553
  {
467
554
  value: props.value,
@@ -478,23 +565,46 @@ function Dashboard(props) {
478
565
  const { exit } = useApp();
479
566
  const size = useTerminalSize();
480
567
  const bodyHeight = Math.max(12, size.rows - 5);
481
- const [focus, setFocus] = useState5("agents");
482
- const [cursor, setCursor] = useState5(0);
483
- const [selected, setSelected] = useState5(() => {
568
+ const [focus, setFocus] = useState6("agents");
569
+ const [cursor, setCursor] = useState6(0);
570
+ const [selected, setSelected] = useState6(() => {
484
571
  const first = props.session.agents[0];
485
572
  return first ? { workflowId: first.workflowId, workflowName: first.workflowName } : void 0;
486
573
  });
487
- const [input, setInput] = useState5("");
574
+ const [input, setInput] = useState6("");
488
575
  const [chat, dispatch] = useReducer(chatReducer, initialChatState);
489
- const [palette, setPalette] = useState5(false);
490
- const [history, setHistory] = useState5(false);
576
+ const [palette, setPalette] = useState6(false);
577
+ const [history, setHistory] = useState6(false);
578
+ const [start, setStart] = useState6();
579
+ const [starting, setStarting] = useState6(false);
491
580
  const controller = useRef2(null);
492
- useEffect4(() => () => controller.current?.abort(), []);
493
- const selectAgent = () => {
581
+ useEffect5(() => () => controller.current?.abort(), []);
582
+ const selectAgent = async () => {
494
583
  const agent = props.session.agents[cursor];
495
- if (!agent || chat.running) return;
496
- setSelected({ workflowId: agent.workflowId, workflowName: agent.workflowName });
584
+ if (!agent || chat.running || starting) return;
585
+ const ref = { workflowId: agent.workflowId, workflowName: agent.workflowName };
586
+ setStarting(true);
587
+ let conversations = [];
588
+ try {
589
+ const all = await props.engine.listConversations(props.session.profile);
590
+ conversations = all.filter((item) => item.workflowId === ref.workflowId);
591
+ } catch {
592
+ conversations = [];
593
+ } finally {
594
+ setStarting(false);
595
+ }
596
+ if (conversations.length === 0) {
597
+ openNewChat(ref);
598
+ return;
599
+ }
600
+ setSelected(ref);
601
+ setStart({ agent: ref, conversations });
602
+ };
603
+ const openNewChat = (ref) => {
604
+ setSelected(ref);
497
605
  dispatch({ type: "reset" });
606
+ setInput("");
607
+ setStart(void 0);
498
608
  setFocus("composer");
499
609
  };
500
610
  const openHistory = (conversation, turns) => {
@@ -503,6 +613,7 @@ function Dashboard(props) {
503
613
  const index = props.session.agents.findIndex((agent) => agent.workflowId === conversation.workflowId);
504
614
  if (index >= 0) setCursor(index);
505
615
  setHistory(false);
616
+ setStart(void 0);
506
617
  setFocus("composer");
507
618
  };
508
619
  const cancelTurn = () => {
@@ -543,7 +654,7 @@ function Dashboard(props) {
543
654
  controller.current = null;
544
655
  }
545
656
  };
546
- useInput4(
657
+ useInput5(
547
658
  (keyInput, key) => {
548
659
  if (key.ctrl && keyInput === "k") setPalette(true);
549
660
  else if (key.ctrl && keyInput === "p") {
@@ -559,9 +670,11 @@ function Dashboard(props) {
559
670
  else if (focus === "agents" && key.upArrow) setCursor((current) => Math.max(0, current - 1));
560
671
  else if (focus === "agents" && key.downArrow && props.session.agents.length > 0) {
561
672
  setCursor((current) => Math.min(props.session.agents.length - 1, current + 1));
562
- } else if (focus === "agents" && key.return) selectAgent();
673
+ } else if (focus === "agents" && key.return) void selectAgent();
563
674
  },
564
- { isActive: !palette && !history }
675
+ // 갈림길·팔레트·이력 화면이 있으면 그 화면이 키를 갖는다 — 여기서도 받으면
676
+ // 방향키 하나가 두 곳에서 움직인다.
677
+ { isActive: !palette && !history && !start }
565
678
  );
566
679
  const paletteActions = [
567
680
  { id: "new", label: "\uC0C8 \uB300\uD654", run: newConversation },
@@ -595,9 +708,9 @@ function Dashboard(props) {
595
708
  ];
596
709
  let body;
597
710
  if (palette) {
598
- body = /* @__PURE__ */ jsx5(CommandPalette, { actions: paletteActions, onCancel: () => setPalette(false) });
711
+ body = /* @__PURE__ */ jsx6(CommandPalette, { actions: paletteActions, onCancel: () => setPalette(false) });
599
712
  } else if (history) {
600
- body = /* @__PURE__ */ jsx5(
713
+ body = /* @__PURE__ */ jsx6(
601
714
  HistoryScreen,
602
715
  {
603
716
  engine: props.engine,
@@ -607,7 +720,7 @@ function Dashboard(props) {
607
720
  }
608
721
  );
609
722
  } else {
610
- const sidebar = /* @__PURE__ */ jsx5(
723
+ const sidebar = /* @__PURE__ */ jsx6(
611
724
  AgentSidebar,
612
725
  {
613
726
  agents: props.session.agents,
@@ -617,9 +730,23 @@ function Dashboard(props) {
617
730
  height: bodyHeight
618
731
  }
619
732
  );
620
- const conversation = /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", flexGrow: 1, children: [
621
- /* @__PURE__ */ jsx5(ChatPane, { agent: selected, messages: chat.messages, status: chat.status, height: bodyHeight - 3 }),
622
- /* @__PURE__ */ jsx5(
733
+ const conversation = start ? /* @__PURE__ */ jsx6(
734
+ StartPanel,
735
+ {
736
+ engine: props.engine,
737
+ profile: props.session.profile,
738
+ agentName: start.agent.workflowName,
739
+ conversations: start.conversations,
740
+ onNew: () => openNewChat(start.agent),
741
+ onOpen: openHistory,
742
+ onCancel: () => {
743
+ setStart(void 0);
744
+ setFocus("agents");
745
+ }
746
+ }
747
+ ) : /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", flexGrow: 1, children: [
748
+ /* @__PURE__ */ jsx6(ChatPane, { agent: selected, messages: chat.messages, status: chat.status, height: bodyHeight - 3 }),
749
+ /* @__PURE__ */ jsx6(
623
750
  Composer,
624
751
  {
625
752
  value: input,
@@ -631,13 +758,13 @@ function Dashboard(props) {
631
758
  }
632
759
  )
633
760
  ] });
634
- body = size.wide ? /* @__PURE__ */ jsxs5(Box4, { height: bodyHeight, children: [
761
+ body = size.wide ? /* @__PURE__ */ jsxs6(Box5, { height: bodyHeight, children: [
635
762
  sidebar,
636
763
  conversation
637
- ] }) : focus === "agents" ? /* @__PURE__ */ jsx5(Box4, { height: bodyHeight, children: sidebar }) : /* @__PURE__ */ jsx5(Box4, { height: bodyHeight, children: conversation });
764
+ ] }) : focus === "agents" ? /* @__PURE__ */ jsx6(Box5, { height: bodyHeight, children: sidebar }) : /* @__PURE__ */ jsx6(Box5, { height: bodyHeight, children: conversation });
638
765
  }
639
- return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", children: [
640
- /* @__PURE__ */ jsx5(
766
+ return /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", children: [
767
+ /* @__PURE__ */ jsx6(
641
768
  Header,
642
769
  {
643
770
  profile: props.session.profile,
@@ -646,22 +773,23 @@ function Dashboard(props) {
646
773
  }
647
774
  ),
648
775
  body,
649
- /* @__PURE__ */ jsx5(Footer, { text: "Tab \uD328\uB110 \xB7 Ctrl+K \uBA85\uB839 \xB7 Ctrl+H \uAE30\uB85D \xB7 Ctrl+P \uD504\uB85C\uD544 \xB7 Esc \uCDE8\uC18C \xB7 Ctrl+Q \uC885\uB8CC" })
776
+ /* @__PURE__ */ jsx6(Footer, { text: "Tab \uD328\uB110 \xB7 Ctrl+K \uBA85\uB839 \xB7 Ctrl+H \uAE30\uB85D \xB7 Ctrl+P \uD504\uB85C\uD544 \xB7 Esc \uCDE8\uC18C \xB7 Ctrl+Q \uC885\uB8CC" })
650
777
  ] });
651
778
  }
652
779
 
653
780
  // src/tui/login-screen.tsx
654
- import { useState as useState6 } from "react";
655
- import { Box as Box5, Text as Text6, useInput as useInput5 } from "ink";
656
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
781
+ import { useState as useState7 } from "react";
782
+ import { Box as Box6, Text as Text7, useInput as useInput6 } from "ink";
783
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
657
784
  function LoginScreen(props) {
658
- const [email, setEmail] = useState6("");
659
- const [password, setPassword] = useState6("");
660
- const [focus, setFocus] = useState6("email");
661
- useInput5(
785
+ const [email, setEmail] = useState7("");
786
+ const [password, setPassword] = useState7("");
787
+ const [focus, setFocus] = useState7("email");
788
+ useInput6(
662
789
  (input, key) => {
663
790
  if (key.tab) setFocus((current) => current === "email" ? "password" : "email");
664
791
  if (key.ctrl && input === "p") props.onProfiles();
792
+ if (key.ctrl && input === "e") props.onEditServer();
665
793
  },
666
794
  { isActive: !props.busy }
667
795
  );
@@ -676,13 +804,17 @@ function LoginScreen(props) {
676
804
  props.onSubmit(email.trim(), secret);
677
805
  }
678
806
  };
679
- return /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", children: [
680
- /* @__PURE__ */ jsx6(Header, { profile: props.profile, connected: false }),
681
- /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", borderStyle: "round", borderColor: "blue", padding: 1, children: [
682
- /* @__PURE__ */ jsx6(Text6, { bold: true, children: "\uB85C\uADF8\uC778" }),
683
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: props.serverUrl }),
684
- /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", marginTop: 1, children: [
685
- /* @__PURE__ */ jsx6(
807
+ return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", children: [
808
+ /* @__PURE__ */ jsx7(Header, { profile: props.profile, connected: false }),
809
+ /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "blue", padding: 1, children: [
810
+ /* @__PURE__ */ jsx7(Text7, { bold: true, children: "\uB85C\uADF8\uC778" }),
811
+ /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
812
+ props.serverUrl,
813
+ " ",
814
+ /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: "(Ctrl+E \uBC14\uAFB8\uAE30)" })
815
+ ] }),
816
+ /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", marginTop: 1, children: [
817
+ /* @__PURE__ */ jsx7(
686
818
  FormField,
687
819
  {
688
820
  label: "Email",
@@ -694,7 +826,7 @@ function LoginScreen(props) {
694
826
  placeholder: "me@corp.com"
695
827
  }
696
828
  ),
697
- /* @__PURE__ */ jsx6(
829
+ /* @__PURE__ */ jsx7(
698
830
  FormField,
699
831
  {
700
832
  label: "Password",
@@ -707,25 +839,25 @@ function LoginScreen(props) {
707
839
  }
708
840
  )
709
841
  ] }),
710
- props.busy ? /* @__PURE__ */ jsx6(Loading, { label: "\uB85C\uADF8\uC778\uD558\uB294 \uC911..." }) : null,
711
- props.error ? /* @__PURE__ */ jsx6(Notice, { error: true, children: props.error }) : null
842
+ props.busy ? /* @__PURE__ */ jsx7(Loading, { label: "\uB85C\uADF8\uC778\uD558\uB294 \uC911..." }) : null,
843
+ props.error ? /* @__PURE__ */ jsx7(Notice, { error: true, children: props.error }) : null
712
844
  ] }),
713
- /* @__PURE__ */ jsx6(Footer, { text: "Tab \uC774\uB3D9 \xB7 Enter \uB85C\uADF8\uC778 \xB7 Ctrl+P \uD504\uB85C\uD544 \xB7 Ctrl+Q \uC885\uB8CC" })
845
+ /* @__PURE__ */ jsx7(Footer, { text: "Tab \uC774\uB3D9 \xB7 Enter \uB85C\uADF8\uC778 \xB7 Ctrl+E \uC11C\uBC84 \xB7 Ctrl+P \uD504\uB85C\uD544 \xB7 Ctrl+Q \uC885\uB8CC" })
714
846
  ] });
715
847
  }
716
848
 
717
849
  // src/tui/profile-screen.tsx
718
- import { useEffect as useEffect5, useState as useState7 } from "react";
719
- import { Box as Box6, Text as Text7, useInput as useInput6 } from "ink";
720
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
850
+ import { useEffect as useEffect6, useState as useState8 } from "react";
851
+ import { Box as Box7, Text as Text8, useInput as useInput7 } from "ink";
852
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
721
853
  function ProfileScreen(props) {
722
- const [cursor, setCursor] = useState7(Math.max(0, props.profiles.findIndex((profile) => profile.current)));
723
- const [creating, setCreating] = useState7(false);
724
- const [focus, setFocus] = useState7("name");
725
- const [name, setName] = useState7("");
726
- const [serverUrl, setServerUrl] = useState7("");
727
- useEffect5(() => setCursor((current) => Math.min(current, Math.max(0, props.profiles.length - 1))), [props.profiles]);
728
- useInput6(
854
+ const [cursor, setCursor] = useState8(Math.max(0, props.profiles.findIndex((profile) => profile.current)));
855
+ const [creating, setCreating] = useState8(false);
856
+ const [focus, setFocus] = useState8("name");
857
+ const [name, setName] = useState8("");
858
+ const [serverUrl, setServerUrl] = useState8("");
859
+ useEffect6(() => setCursor((current) => Math.min(current, Math.max(0, props.profiles.length - 1))), [props.profiles]);
860
+ useInput7(
729
861
  (input, key) => {
730
862
  if (key.escape) {
731
863
  if (creating) setCreating(false);
@@ -755,12 +887,12 @@ function ProfileScreen(props) {
755
887
  }
756
888
  if (name.trim() && serverUrl.trim()) props.onCreate(name.trim(), serverUrl.trim());
757
889
  };
758
- return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", children: [
759
- /* @__PURE__ */ jsx7(Header, {}),
760
- /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", padding: 1, children: [
761
- /* @__PURE__ */ jsx7(Text7, { bold: true, children: creating ? "\uC0C8 \uD504\uB85C\uD544" : "\uD504\uB85C\uD544 \uC804\uD658" }),
762
- creating ? /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", marginTop: 1, children: [
763
- /* @__PURE__ */ jsx7(
890
+ return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
891
+ /* @__PURE__ */ jsx8(Header, {}),
892
+ /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", padding: 1, children: [
893
+ /* @__PURE__ */ jsx8(Text8, { bold: true, children: creating ? "\uC0C8 \uD504\uB85C\uD544" : "\uD504\uB85C\uD544 \uC804\uD658" }),
894
+ creating ? /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", marginTop: 1, children: [
895
+ /* @__PURE__ */ jsx8(
764
896
  FormField,
765
897
  {
766
898
  label: "Name",
@@ -772,7 +904,7 @@ function ProfileScreen(props) {
772
904
  placeholder: "corp"
773
905
  }
774
906
  ),
775
- /* @__PURE__ */ jsx7(
907
+ /* @__PURE__ */ jsx8(
776
908
  FormField,
777
909
  {
778
910
  label: "Server URL",
@@ -784,8 +916,8 @@ function ProfileScreen(props) {
784
916
  placeholder: "https://xgen.example.com"
785
917
  }
786
918
  )
787
- ] }) : /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", marginTop: 1, children: [
788
- props.profiles.map((profile, index) => /* @__PURE__ */ jsxs7(Text7, { color: index === cursor ? "cyan" : void 0, children: [
919
+ ] }) : /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", marginTop: 1, children: [
920
+ props.profiles.map((profile, index) => /* @__PURE__ */ jsxs8(Text8, { color: index === cursor ? "cyan" : void 0, children: [
789
921
  index === cursor ? "\u203A" : " ",
790
922
  " ",
791
923
  profile.current ? "\u25CF" : "\u25CB",
@@ -793,14 +925,14 @@ function ProfileScreen(props) {
793
925
  profile.name,
794
926
  " \xB7",
795
927
  " ",
796
- /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: profile.serverUrl })
928
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: profile.serverUrl })
797
929
  ] }, profile.name)),
798
- props.profiles.length === 0 ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\uC800\uC7A5\uB41C \uD504\uB85C\uD544\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." }) : null
930
+ props.profiles.length === 0 ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "\uC800\uC7A5\uB41C \uD504\uB85C\uD544\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." }) : null
799
931
  ] }),
800
- props.busy ? /* @__PURE__ */ jsx7(Loading, { label: "\uD504\uB85C\uD544\uC744 \uC804\uD658\uD558\uB294 \uC911..." }) : null,
801
- props.error ? /* @__PURE__ */ jsx7(Notice, { error: true, children: props.error }) : null
932
+ props.busy ? /* @__PURE__ */ jsx8(Loading, { label: "\uD504\uB85C\uD544\uC744 \uC804\uD658\uD558\uB294 \uC911..." }) : null,
933
+ props.error ? /* @__PURE__ */ jsx8(Notice, { error: true, children: props.error }) : null
802
934
  ] }),
803
- /* @__PURE__ */ jsx7(
935
+ /* @__PURE__ */ jsx8(
804
936
  Footer,
805
937
  {
806
938
  text: creating ? "Tab \uC774\uB3D9 \xB7 Enter \uC800\uC7A5 \xB7 Esc \uB4A4\uB85C" : "\u2191\u2193 \uC774\uB3D9 \xB7 Enter \uC120\uD0DD \xB7 N \uC0C8 \uD504\uB85C\uD544 \xB7 Esc \uB2EB\uAE30"
@@ -809,18 +941,25 @@ function ProfileScreen(props) {
809
941
  ] });
810
942
  }
811
943
 
812
- // src/tui/setup-screen.tsx
813
- import { useState as useState8 } from "react";
814
- import { Box as Box7, Text as Text8 } from "ink";
815
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
816
- function SetupScreen(props) {
817
- const [serverUrl, setServerUrl] = useState8("");
818
- return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
819
- /* @__PURE__ */ jsx8(Header, {}),
820
- /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", borderStyle: "round", borderColor: "blue", padding: 1, children: [
821
- /* @__PURE__ */ jsx8(Text8, { bold: true, children: "\uCC98\uC74C \uC624\uC168\uAD70\uC694" }),
822
- /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "\uC5F0\uACB0\uD560 XGEN Gateway \uC8FC\uC18C\uB97C \uC785\uB825\uD558\uC138\uC694." }),
823
- /* @__PURE__ */ jsx8(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx8(
944
+ // src/tui/server-screen.tsx
945
+ import { useState as useState9 } from "react";
946
+ import { Box as Box8, Text as Text9, useInput as useInput8 } from "ink";
947
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
948
+ function ServerScreen(props) {
949
+ const [serverUrl, setServerUrl] = useState9(props.initialValue ?? "");
950
+ const editing = props.initialValue !== void 0;
951
+ useInput8(
952
+ (_input, key) => {
953
+ if (key.escape) props.onCancel?.();
954
+ },
955
+ { isActive: !props.busy && !!props.onCancel }
956
+ );
957
+ return /* @__PURE__ */ jsxs9(Box8, { flexDirection: "column", children: [
958
+ /* @__PURE__ */ jsx9(Header, { profile: props.profile, connected: false }),
959
+ /* @__PURE__ */ jsxs9(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "blue", padding: 1, children: [
960
+ /* @__PURE__ */ jsx9(Text9, { bold: true, children: editing ? "\uC11C\uBC84 \uC8FC\uC18C \uBC14\uAFB8\uAE30" : "\uCC98\uC74C \uC624\uC168\uAD70\uC694" }),
961
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "\uC5F0\uACB0\uD560 XGEN Gateway \uC8FC\uC18C\uB97C \uC785\uB825\uD558\uC138\uC694." }),
962
+ /* @__PURE__ */ jsx9(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx9(
824
963
  FormField,
825
964
  {
826
965
  label: "Server URL",
@@ -829,27 +968,34 @@ function SetupScreen(props) {
829
968
  onSubmit: (value) => value.trim() && props.onSubmit(value.trim()),
830
969
  focus: !props.busy,
831
970
  cursorOrigin: { x: 16, y: 6 },
832
- placeholder: "https://xgen.example.com"
971
+ placeholder: "xgen.example.com"
833
972
  }
834
973
  ) }),
835
- props.busy ? /* @__PURE__ */ jsx8(Loading, { label: "\uC11C\uBC84 \uD504\uB85C\uD544\uC744 \uC800\uC7A5\uD558\uB294 \uC911..." }) : null,
836
- props.error ? /* @__PURE__ */ jsx8(Notice, { error: true, children: props.error }) : null
974
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "https:// \uB294 \uC0DD\uB7B5\uD574\uB3C4 \uB429\uB2C8\uB2E4." }),
975
+ props.busy ? /* @__PURE__ */ jsx9(Loading, { label: "\uC11C\uBC84 \uD504\uB85C\uD544\uC744 \uC800\uC7A5\uD558\uB294 \uC911..." }) : null,
976
+ props.error ? /* @__PURE__ */ jsx9(Notice, { error: true, children: props.error }) : null
837
977
  ] }),
838
- /* @__PURE__ */ jsx8(Footer, { text: "Enter \uACC4\uC18D \xB7 Ctrl+Q \uC885\uB8CC" })
978
+ /* @__PURE__ */ jsx9(
979
+ Footer,
980
+ {
981
+ text: props.onCancel ? "Enter \uC800\uC7A5 \xB7 Esc \uCDE8\uC18C \xB7 Ctrl+Q \uC885\uB8CC" : "Enter \uACC4\uC18D \xB7 Ctrl+Q \uC885\uB8CC"
982
+ }
983
+ )
839
984
  ] });
840
985
  }
841
986
 
842
987
  // src/tui/app.tsx
843
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
988
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
844
989
  function App({ engine }) {
845
990
  const { exit } = useApp2();
846
- const [route, setRoute] = useState9("boot");
847
- const [session, setSession] = useState9();
848
- const [profiles, setProfiles] = useState9([]);
849
- const [loginTarget, setLoginTarget] = useState9();
850
- const [busy, setBusy] = useState9(false);
851
- const [error, setError] = useState9();
852
- useInput7((input, key) => {
991
+ const [route, setRoute] = useState10("boot");
992
+ const [session, setSession] = useState10();
993
+ const [profiles, setProfiles] = useState10([]);
994
+ const [loginTarget, setLoginTarget] = useState10();
995
+ const [editingServer, setEditingServer] = useState10();
996
+ const [busy, setBusy] = useState10(false);
997
+ const [error, setError] = useState10();
998
+ useInput9((input, key) => {
853
999
  if (key.ctrl && input === "q") exit();
854
1000
  });
855
1001
  const bootstrap = useCallback(
@@ -862,7 +1008,7 @@ function App({ engine }) {
862
1008
  setProfiles(available);
863
1009
  if (available.length === 0) {
864
1010
  setSession(void 0);
865
- setRoute("setup");
1011
+ setRoute("server");
866
1012
  return;
867
1013
  }
868
1014
  if (preferredProfile) {
@@ -899,17 +1045,19 @@ function App({ engine }) {
899
1045
  },
900
1046
  [engine]
901
1047
  );
902
- useEffect6(() => {
1048
+ useEffect7(() => {
903
1049
  void bootstrap();
904
1050
  }, [bootstrap]);
905
1051
  const configure = async (serverUrl) => {
906
1052
  setBusy(true);
907
1053
  setError(void 0);
1054
+ const target = editingServer?.profile ?? loginTarget?.profile ?? "default";
908
1055
  try {
909
- const profile = await engine.setProfile("default", serverUrl);
1056
+ const profile = await engine.setProfile(target, serverUrl);
910
1057
  await engine.useProfile(profile.name);
911
1058
  setLoginTarget({ profile: profile.name, serverUrl: profile.serverUrl });
912
1059
  setProfiles(await engine.listProfiles());
1060
+ setEditingServer(void 0);
913
1061
  setRoute("login");
914
1062
  } catch (reason) {
915
1063
  setError(publicError(reason).message);
@@ -972,17 +1120,31 @@ function App({ engine }) {
972
1120
  }
973
1121
  };
974
1122
  if (route === "boot") {
975
- return /* @__PURE__ */ jsxs9(Box8, { flexDirection: "column", children: [
976
- /* @__PURE__ */ jsx9(Header, {}),
977
- /* @__PURE__ */ jsx9(Loading, { label: "Dex\uB97C \uC900\uBE44\uD558\uB294 \uC911..." }),
978
- /* @__PURE__ */ jsx9(Footer, { text: "Ctrl+Q \uC885\uB8CC" })
1123
+ return /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", children: [
1124
+ /* @__PURE__ */ jsx10(Header, {}),
1125
+ /* @__PURE__ */ jsx10(Loading, { label: "Dex\uB97C \uC900\uBE44\uD558\uB294 \uC911..." }),
1126
+ /* @__PURE__ */ jsx10(Footer, { text: "Ctrl+Q \uC885\uB8CC" })
979
1127
  ] });
980
1128
  }
981
- if (route === "setup") {
982
- return /* @__PURE__ */ jsx9(SetupScreen, { busy, error, onSubmit: (url) => void configure(url) });
1129
+ if (route === "server") {
1130
+ return /* @__PURE__ */ jsx10(
1131
+ ServerScreen,
1132
+ {
1133
+ initialValue: editingServer?.serverUrl,
1134
+ profile: editingServer?.profile,
1135
+ busy,
1136
+ error,
1137
+ onSubmit: (url) => void configure(url),
1138
+ onCancel: editingServer ? () => {
1139
+ setEditingServer(void 0);
1140
+ setError(void 0);
1141
+ setRoute("login");
1142
+ } : void 0
1143
+ }
1144
+ );
983
1145
  }
984
1146
  if (route === "login" && loginTarget) {
985
- return /* @__PURE__ */ jsx9(
1147
+ return /* @__PURE__ */ jsx10(
986
1148
  LoginScreen,
987
1149
  {
988
1150
  profile: loginTarget.profile,
@@ -990,12 +1152,17 @@ function App({ engine }) {
990
1152
  busy,
991
1153
  error,
992
1154
  onSubmit: (email, password) => void login(email, password),
993
- onProfiles: () => void openProfiles()
1155
+ onProfiles: () => void openProfiles(),
1156
+ onEditServer: () => {
1157
+ setEditingServer(loginTarget);
1158
+ setError(void 0);
1159
+ setRoute("server");
1160
+ }
994
1161
  }
995
1162
  );
996
1163
  }
997
1164
  if (route === "profiles") {
998
- return /* @__PURE__ */ jsx9(
1165
+ return /* @__PURE__ */ jsx10(
999
1166
  ProfileScreen,
1000
1167
  {
1001
1168
  profiles,
@@ -1003,12 +1170,12 @@ function App({ engine }) {
1003
1170
  error,
1004
1171
  onSelect: (name) => void bootstrap(name),
1005
1172
  onCreate: (name, url) => void createProfile(name, url),
1006
- onCancel: () => setRoute(session ? "dashboard" : loginTarget ? "login" : "setup")
1173
+ onCancel: () => setRoute(session ? "dashboard" : loginTarget ? "login" : "server")
1007
1174
  }
1008
1175
  );
1009
1176
  }
1010
1177
  if (route === "dashboard" && session) {
1011
- return /* @__PURE__ */ jsx9(
1178
+ return /* @__PURE__ */ jsx10(
1012
1179
  Dashboard,
1013
1180
  {
1014
1181
  engine,
@@ -1018,31 +1185,72 @@ function App({ engine }) {
1018
1185
  }
1019
1186
  );
1020
1187
  }
1021
- return /* @__PURE__ */ jsxs9(Box8, { flexDirection: "column", children: [
1022
- /* @__PURE__ */ jsx9(Header, {}),
1023
- /* @__PURE__ */ jsx9(Notice, { error: true, children: error ?? "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4." }),
1024
- /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "\uC11C\uBC84\uC640 \uD0A4\uCCB4\uC778 \uC0C1\uD0DC\uB97C \uD655\uC778\uD55C \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694." }),
1025
- /* @__PURE__ */ jsx9(Footer, { text: "R \uB2E4\uC2DC \uC2DC\uB3C4 \xB7 Ctrl+Q \uC885\uB8CC" }),
1026
- /* @__PURE__ */ jsx9(RetryInput, { onRetry: () => void bootstrap() })
1188
+ return /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", children: [
1189
+ /* @__PURE__ */ jsx10(Header, {}),
1190
+ /* @__PURE__ */ jsx10(Notice, { error: true, children: error ?? "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4." }),
1191
+ /* @__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." }),
1192
+ /* @__PURE__ */ jsx10(Footer, { text: "R \uB2E4\uC2DC \uC2DC\uB3C4 \xB7 Ctrl+Q \uC885\uB8CC" }),
1193
+ /* @__PURE__ */ jsx10(RetryInput, { onRetry: () => void bootstrap() })
1027
1194
  ] });
1028
1195
  }
1029
1196
  function RetryInput({ onRetry }) {
1030
- useInput7((input) => {
1197
+ useInput9((input) => {
1031
1198
  if (input.toLowerCase() === "r") onRetry();
1032
1199
  });
1033
1200
  return null;
1034
1201
  }
1035
1202
 
1203
+ // src/tui/screen.ts
1204
+ var ESC = "\x1B";
1205
+ var ENTER_ALT_SCREEN = `${ESC}[?1049h`;
1206
+ var LEAVE_ALT_SCREEN = `${ESC}[?1049l`;
1207
+ var SHOW_CURSOR = `${ESC}[?25h`;
1208
+ function createScreenGuard(stream) {
1209
+ const alt = Boolean(stream.isTTY);
1210
+ let entered = false;
1211
+ let restored = false;
1212
+ return {
1213
+ enter() {
1214
+ if (entered || !alt) return;
1215
+ entered = true;
1216
+ stream.write(ENTER_ALT_SCREEN);
1217
+ },
1218
+ restore() {
1219
+ if (restored) return;
1220
+ restored = true;
1221
+ if (entered) stream.write(LEAVE_ALT_SCREEN);
1222
+ stream.write(SHOW_CURSOR);
1223
+ }
1224
+ };
1225
+ }
1226
+
1036
1227
  // src/tui/index.tsx
1037
- import { jsx as jsx10 } from "react/jsx-runtime";
1228
+ import { jsx as jsx11 } from "react/jsx-runtime";
1038
1229
  async function runTui(engine) {
1039
- const instance = render(/* @__PURE__ */ jsx10(App, { engine }), {
1040
- exitOnCtrlC: true,
1041
- patchConsole: true
1042
- });
1043
- await instance.waitUntilExit();
1230
+ const screen = createScreenGuard(stdout);
1231
+ const restore = () => screen.restore();
1232
+ const onSignal = () => {
1233
+ restore();
1234
+ process.exit(0);
1235
+ };
1236
+ process.once("exit", restore);
1237
+ process.once("SIGINT", onSignal);
1238
+ process.once("SIGTERM", onSignal);
1239
+ screen.enter();
1240
+ try {
1241
+ const instance = render(/* @__PURE__ */ jsx11(App, { engine }), {
1242
+ exitOnCtrlC: true,
1243
+ patchConsole: true
1244
+ });
1245
+ await instance.waitUntilExit();
1246
+ } finally {
1247
+ restore();
1248
+ process.off("exit", restore);
1249
+ process.off("SIGINT", onSignal);
1250
+ process.off("SIGTERM", onSignal);
1251
+ }
1044
1252
  }
1045
1253
  export {
1046
1254
  runTui
1047
1255
  };
1048
- //# sourceMappingURL=tui-PNFWGJTJ.js.map
1256
+ //# sourceMappingURL=tui-MLHYSLBS.js.map