xgen-dex-cli 1.7.1 → 1.8.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.
@@ -2,19 +2,19 @@ import {
2
2
  DexError,
3
3
  dataDirectory,
4
4
  publicError
5
- } from "./chunk-ULMEXJOR.js";
5
+ } from "./chunk-KY3K4GV4.js";
6
6
 
7
7
  // src/tui/index.tsx
8
8
  import { render } from "ink";
9
9
  import { stdin, stdout } from "node:process";
10
10
 
11
11
  // src/tui/app.tsx
12
- import { useCallback, useEffect as useEffect8, useState as useState11 } from "react";
13
- import { Box as Box10, Text as Text10, useApp as useApp2, useInput as useInput9 } from "ink";
12
+ import { useCallback, useEffect as useEffect9, useState as useState12 } from "react";
13
+ import { Box as Box11, Text as Text11, useApp as useApp2, useInput as useInput10 } from "ink";
14
14
 
15
15
  // src/tui/dashboard.tsx
16
- import { useEffect as useEffect6, useReducer, useRef as useRef3, useState as useState7 } from "react";
17
- import { Box as Box6, Text as Text6, useApp, useInput as useInput5 } from "ink";
16
+ import { useEffect as useEffect7, useReducer, useRef as useRef3, useState as useState8 } from "react";
17
+ import { Box as Box7, Text as Text7, useApp, useInput as useInput6 } from "ink";
18
18
 
19
19
  // src/tui/chat-state.ts
20
20
  var initialChatState = { messages: [], running: false };
@@ -288,7 +288,7 @@ import { Box as Box2, Text as Text2 } from "ink";
288
288
 
289
289
  // src/tui/ime-text-input.tsx
290
290
  import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
291
- import { Box, Text, useCursor, useInput, useStdin } from "ink";
291
+ import { Box, Text, useCursor, useInput } from "ink";
292
292
  import stringWidth2 from "string-width";
293
293
 
294
294
  // src/tui/terminal-input.ts
@@ -504,67 +504,6 @@ function backspace(session) {
504
504
  };
505
505
  }
506
506
 
507
- // src/tui/kitty.ts
508
- var ESC = "\x1B";
509
- var QUERY = `${ESC}[?u`;
510
- var KEY_CODES = {
511
- 57358: "capslock",
512
- 57449: "rightalt"
513
- };
514
- var CAPS_LOCK_BIT = 64;
515
- function parseKeyEvents(chunk) {
516
- const events = [];
517
- const pattern = /\u001B\[(\d+)(?::\d+)*(?:;(\d+)(?::(\d+))?)?(?:;[\d:]*)?u/g;
518
- for (const match of chunk.matchAll(pattern)) {
519
- const code = Number(match[1]);
520
- const modifiers = match[2] ? Number(match[2]) - 1 : 0;
521
- events.push({
522
- name: KEY_CODES[code],
523
- // eslint-disable-next-line no-bitwise
524
- capsLock: (modifiers & CAPS_LOCK_BIT) !== 0,
525
- eventType: match[3] ? Number(match[3]) : 1
526
- });
527
- }
528
- return events;
529
- }
530
- function knownKittyTerminal(env = process.env) {
531
- if (env.KITTY_WINDOW_ID) return true;
532
- if (env.GHOSTTY_RESOURCES_DIR) return true;
533
- const term = (env.TERM ?? "").toLowerCase();
534
- if (term === "xterm-kitty" || term === "xterm-ghostty" || term.includes("foot")) return true;
535
- const program = (env.TERM_PROGRAM ?? "").toLowerCase();
536
- return program === "wezterm" || program === "ghostty" || program === "kitty";
537
- }
538
- async function supportsKittyKeyboard(streams, timeoutMs = 200, env = process.env) {
539
- if (env.DEX_NO_KITTY === "1") return false;
540
- if (!streams.stdout.isTTY || !streams.isTTY) return false;
541
- if (knownKittyTerminal(env)) return true;
542
- const wasRaw = streams.stdin.isRaw === true;
543
- const wasPaused = !wasRaw;
544
- streams.stdin.setRawMode?.(true);
545
- streams.stdin.resume?.();
546
- return new Promise((resolve) => {
547
- let settled = false;
548
- let seen = "";
549
- const finish = (supported) => {
550
- if (settled) return;
551
- settled = true;
552
- clearTimeout(timer);
553
- streams.stdin.removeListener("data", onData);
554
- if (!wasRaw) streams.stdin.setRawMode?.(false);
555
- if (wasPaused) streams.stdin.pause?.();
556
- resolve(supported);
557
- };
558
- const onData = (data) => {
559
- seen += typeof data === "string" ? data : data.toString("utf8");
560
- if (/\u001B\[\?\d*u/.test(seen)) finish(true);
561
- };
562
- const timer = setTimeout(() => finish(false), timeoutMs);
563
- streams.stdin.on("data", onData);
564
- streams.stdout.write(QUERY);
565
- });
566
- }
567
-
568
507
  // src/tui/ime-text-input.tsx
569
508
  import { jsx, jsxs } from "react/jsx-runtime";
570
509
  var segmenter = new Intl.Segmenter("ko", { granularity: "grapheme" });
@@ -628,22 +567,6 @@ function ImeTextInput(props) {
628
567
  moveCursor(nextCursor, segments.length);
629
568
  props.onChange(nextValue);
630
569
  };
631
- const { stdin: stdin2, isRawModeSupported } = useStdin();
632
- const onModeKeyRef = useRef2(void 0);
633
- onModeKeyRef.current = () => props.onHangulModeChange?.(!props.hangulMode);
634
- useEffect2(() => {
635
- if (!props.focus || !isRawModeSupported) return void 0;
636
- const onData = (data) => {
637
- const chunk = typeof data === "string" ? data : data.toString("utf8");
638
- for (const event of parseKeyEvents(chunk)) {
639
- typingRef.current = { ...typingRef.current, capsLock: event.capsLock };
640
- if (event.eventType !== 1) continue;
641
- if (event.name === "rightalt" || event.name === "capslock") onModeKeyRef.current?.();
642
- }
643
- };
644
- stdin2?.on("data", onData);
645
- return () => void stdin2?.off("data", onData);
646
- }, [props.focus, isRawModeSupported, stdin2]);
647
570
  useEffect2(() => {
648
571
  if (!props.focus || !props.hangulMode) {
649
572
  typingRef.current = { ...IDLE, capsLock: typingRef.current.capsLock };
@@ -994,8 +917,204 @@ function when(conversation) {
994
917
  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())}`;
995
918
  }
996
919
 
920
+ // src/tui/agent-create.tsx
921
+ import { useEffect as useEffect5, useMemo, useState as useState6 } from "react";
922
+ import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
923
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
924
+ var ADVANCED_ORDER = [
925
+ "system_prompt",
926
+ "temperature",
927
+ "max_tokens",
928
+ "max_iterations",
929
+ "context_window",
930
+ "tool_exposure",
931
+ "enable_builtin_tools",
932
+ "enable_self_evolution",
933
+ "enable_delegation",
934
+ "enable_memory",
935
+ "enable_compaction",
936
+ "streaming",
937
+ "base_url"
938
+ ];
939
+ function advancedFields(settings) {
940
+ const rank = (id) => {
941
+ const i = ADVANCED_ORDER.indexOf(id);
942
+ return i === -1 ? ADVANCED_ORDER.length : i;
943
+ };
944
+ return [...settings].sort((a, b) => rank(a.id) - rank(b.id)).map((setting) => {
945
+ const type = (setting.type || "").toUpperCase();
946
+ if (type === "BOOL") return { key: setting.id, label: setting.label, kind: "toggle" };
947
+ if (setting.options && setting.options.length > 0) {
948
+ return {
949
+ key: setting.id,
950
+ label: setting.label,
951
+ kind: "choice",
952
+ choices: setting.options
953
+ };
954
+ }
955
+ return { key: setting.id, label: setting.label, kind: "text" };
956
+ });
957
+ }
958
+ function AgentCreateScreen(props) {
959
+ const [options, setOptions] = useState6();
960
+ const [loadError, setLoadError] = useState6();
961
+ const [values, setValues] = useState6({});
962
+ const [cursor, setCursor] = useState6(0);
963
+ const [advanced, setAdvanced] = useState6(false);
964
+ const [busy, setBusy] = useState6(false);
965
+ const [error, setError] = useState6();
966
+ useEffect5(() => {
967
+ let cancelled = false;
968
+ props.engine.agentCreateOptions(props.profile).then((data) => {
969
+ if (cancelled) return;
970
+ const provider = data.providers.find((p) => p.value === data.defaultProvider)?.value ?? data.providers[0]?.value ?? "";
971
+ const info = data.providers.find((p) => p.value === provider);
972
+ setOptions(data);
973
+ const seeded = {};
974
+ for (const setting of data.settings) seeded[setting.id] = setting.default;
975
+ setValues({
976
+ ...seeded,
977
+ ...data.defaults,
978
+ name: "",
979
+ provider,
980
+ model: info?.defaultModel || info?.models[0]?.value || ""
981
+ });
982
+ }).catch((err) => {
983
+ if (!cancelled) setLoadError(publicError(err).message);
984
+ });
985
+ return () => {
986
+ cancelled = true;
987
+ };
988
+ }, [props.engine, props.profile]);
989
+ const providerInfo = useMemo(
990
+ () => options?.providers.find((p) => p.value === values.provider),
991
+ [options, values.provider]
992
+ );
993
+ const fields = useMemo(() => {
994
+ if (!options) return [];
995
+ const base = [
996
+ { key: "name", label: "\uC774\uB984", kind: "text", hint: "\uC608: \uC601\uC5C5 \uB9AC\uC11C\uCE58 \uB3C4\uC6B0\uBBF8" },
997
+ {
998
+ key: "provider",
999
+ label: "AI \uC81C\uACF5\uC0AC",
1000
+ kind: "choice",
1001
+ choices: options.providers.map((p) => ({ value: p.value, label: p.label }))
1002
+ },
1003
+ { key: "model", label: "\uBAA8\uB378", kind: "choice", choices: providerInfo?.models ?? [] }
1004
+ ];
1005
+ return advanced ? [...base, ...advancedFields(options.settings)] : base;
1006
+ }, [options, providerInfo, advanced]);
1007
+ const active = fields[cursor];
1008
+ const name = String(values.name ?? "").trim();
1009
+ const setValue = (key, value) => {
1010
+ setValues((prev) => {
1011
+ if (key !== "provider") return { ...prev, [key]: value };
1012
+ const info = options?.providers.find((p) => p.value === value);
1013
+ return { ...prev, provider: value, model: info?.defaultModel || info?.models[0]?.value || "" };
1014
+ });
1015
+ };
1016
+ const cycle = (field, step) => {
1017
+ if (field.kind === "toggle") {
1018
+ setValue(field.key, !values[field.key]);
1019
+ return;
1020
+ }
1021
+ const choices = field.choices ?? [];
1022
+ if (choices.length === 0) return;
1023
+ const at = Math.max(0, choices.findIndex((c) => c.value === String(values[field.key] ?? "")));
1024
+ const next = (at + step + choices.length) % choices.length;
1025
+ setValue(field.key, choices[next].value);
1026
+ };
1027
+ const submit = async () => {
1028
+ if (!name || busy) return;
1029
+ setBusy(true);
1030
+ setError(void 0);
1031
+ try {
1032
+ const { name: _n, provider: _p, model: _m, ...settings } = values;
1033
+ const created = await props.engine.createAgent(
1034
+ {
1035
+ name,
1036
+ provider: String(values.provider ?? ""),
1037
+ model: String(values.model ?? ""),
1038
+ settings
1039
+ },
1040
+ props.profile
1041
+ );
1042
+ props.onCreated(created);
1043
+ } catch (err) {
1044
+ setError(publicError(err).message);
1045
+ } finally {
1046
+ setBusy(false);
1047
+ }
1048
+ };
1049
+ useInput5((input, key) => {
1050
+ const onText = active?.kind === "text";
1051
+ if (key.escape) {
1052
+ props.onCancel();
1053
+ return;
1054
+ }
1055
+ if (key.tab) {
1056
+ setAdvanced((v) => !v);
1057
+ return;
1058
+ }
1059
+ if (key.upArrow) {
1060
+ setCursor((c) => Math.max(0, c - 1));
1061
+ return;
1062
+ }
1063
+ if (key.downArrow) {
1064
+ setCursor((c) => Math.min(fields.length - 1, c + 1));
1065
+ return;
1066
+ }
1067
+ if (!active || onText) return;
1068
+ if (key.leftArrow) cycle(active, -1);
1069
+ else if (key.rightArrow) cycle(active, 1);
1070
+ else if (input === " " && !key.ctrl && !key.meta) cycle(active, 1);
1071
+ else if (key.return) void submit();
1072
+ });
1073
+ if (loadError) {
1074
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", padding: 1, children: [
1075
+ /* @__PURE__ */ jsx6(Text6, { color: "red", children: loadError }),
1076
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Esc \uB85C \uB3CC\uC544\uAC11\uB2C8\uB2E4." })
1077
+ ] });
1078
+ }
1079
+ if (!options) {
1080
+ return /* @__PURE__ */ jsx6(Box6, { padding: 1, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\uBD88\uB7EC\uC624\uB294 \uC911..." }) });
1081
+ }
1082
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
1083
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: "\uC0C8 \uC5D0\uC774\uC804\uD2B8" }),
1084
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, wrap: "truncate-end", children: "\uC774\uB984\uACFC \uBAA8\uB378\uB9CC \uC815\uD558\uBA74 \uB429\uB2C8\uB2E4. \uB3C4\uAD6C\xB7\uAE30\uC5B5\xB7\uC790\uAE30\uC9C4\uD654\uB294 \uC774\uBBF8 \uC548\uC5D0 \uC788\uC2B5\uB2C8\uB2E4." }),
1085
+ /* @__PURE__ */ jsx6(Box6, { height: 1 }),
1086
+ fields.map((field, index) => {
1087
+ const focused = index === cursor;
1088
+ const raw = values[field.key];
1089
+ const shown = field.kind === "toggle" ? raw ? "\uCF2C" : "\uB054" : field.kind === "choice" ? field.choices?.find((c) => c.value === String(raw ?? ""))?.label ?? String(raw ?? "") : String(raw ?? "");
1090
+ return /* @__PURE__ */ jsxs6(Box6, { children: [
1091
+ /* @__PURE__ */ jsx6(Box6, { width: 24, flexShrink: 0, children: /* @__PURE__ */ jsxs6(Text6, { color: focused ? "cyan" : void 0, wrap: "truncate-end", children: [
1092
+ focused ? "\u203A" : " ",
1093
+ " ",
1094
+ field.label
1095
+ ] }) }),
1096
+ field.kind === "text" && focused ? /* @__PURE__ */ jsx6(
1097
+ ImeTextInput,
1098
+ {
1099
+ value: String(raw ?? ""),
1100
+ onChange: (v) => setValue(field.key, v),
1101
+ onSubmit: () => void submit(),
1102
+ focus: true,
1103
+ placeholder: field.hint ?? "",
1104
+ hangulMode: props.hangulMode,
1105
+ onHangulModeChange: props.onHangulModeChange
1106
+ }
1107
+ ) : /* @__PURE__ */ jsx6(Text6, { dimColor: !focused, wrap: "truncate-end", children: field.kind === "text" ? shown || field.hint || "" : `\u2039 ${shown} \u203A` })
1108
+ ] }, field.key);
1109
+ }),
1110
+ /* @__PURE__ */ jsx6(Box6, { height: 1 }),
1111
+ error ? /* @__PURE__ */ jsx6(Text6, { color: "red", children: error }) : null,
1112
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, wrap: "truncate-end", children: busy ? "\uB9CC\uB4DC\uB294 \uC911..." : `\u2191\u2193 \uC774\uB3D9 \xB7 \u2190\u2192 \uACE0\uB974\uAE30 \xB7 Tab ${advanced ? "\uC138\uBD80\uC124\uC815 \uC811\uAE30" : "\uC138\uBD80\uC124\uC815"} \xB7 Enter \uB9CC\uB4E4\uAE30 \xB7 Esc \uCDE8\uC18C` })
1113
+ ] });
1114
+ }
1115
+
997
1116
  // src/tui/use-terminal-size.ts
998
- import { useEffect as useEffect5, useState as useState6 } from "react";
1117
+ import { useEffect as useEffect6, useState as useState7 } from "react";
999
1118
  import { useStdout } from "ink";
1000
1119
  function useTerminalSize() {
1001
1120
  const { stdout: stdout2 } = useStdout();
@@ -1004,8 +1123,8 @@ function useTerminalSize() {
1004
1123
  const rows = stdout2.rows || 30;
1005
1124
  return { columns, rows, wide: columns >= 88 };
1006
1125
  };
1007
- const [size, setSize] = useState6(read);
1008
- useEffect5(() => {
1126
+ const [size, setSize] = useState7(read);
1127
+ useEffect6(() => {
1009
1128
  const resize = () => setSize(read());
1010
1129
  stdout2.on("resize", resize);
1011
1130
  return () => {
@@ -1016,18 +1135,22 @@ function useTerminalSize() {
1016
1135
  }
1017
1136
 
1018
1137
  // src/tui/dashboard.tsx
1019
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1138
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1020
1139
  function AgentSidebar(props) {
1021
1140
  const radius = Math.max(3, Math.floor((props.height - 4) / 2));
1022
1141
  const start = Math.max(0, props.cursor - radius);
1023
1142
  const visible = props.agents.slice(start, start + radius * 2 + 1);
1024
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", width: 30, borderStyle: "round", borderColor: props.focused ? "cyan" : "gray", paddingX: 1, children: [
1025
- /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Agents" }),
1143
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", width: 30, borderStyle: "round", borderColor: props.focused ? "cyan" : "gray", paddingX: 1, children: [
1144
+ /* @__PURE__ */ jsx7(Text7, { bold: true, children: "Agents" }),
1145
+ /* @__PURE__ */ jsxs7(Text7, { color: props.cursor === -1 && props.focused ? "cyan" : void 0, wrap: "truncate-end", children: [
1146
+ props.cursor === -1 ? "\u203A" : " ",
1147
+ " \uFF0B \uC0C8 \uC5D0\uC774\uC804\uD2B8"
1148
+ ] }),
1026
1149
  visible.map((agent) => {
1027
1150
  const index = props.agents.indexOf(agent);
1028
1151
  const cursor = index === props.cursor;
1029
1152
  const selected = agent.workflowId === props.selected;
1030
- return /* @__PURE__ */ jsxs6(Text6, { color: cursor && props.focused ? "cyan" : void 0, wrap: "truncate-end", children: [
1153
+ return /* @__PURE__ */ jsxs7(Text7, { color: cursor && props.focused ? "cyan" : void 0, wrap: "truncate-end", children: [
1031
1154
  cursor ? "\u203A" : " ",
1032
1155
  " ",
1033
1156
  selected ? "\u25CF" : "\u25CB",
@@ -1035,7 +1158,7 @@ function AgentSidebar(props) {
1035
1158
  agent.workflowName
1036
1159
  ] }, agent.workflowId);
1037
1160
  }),
1038
- props.agents.length === 0 ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\uC0AC\uC6A9 \uAC00\uB2A5\uD55C Agent\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }) : null
1161
+ props.agents.length === 0 ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\uC0AC\uC6A9 \uAC00\uB2A5\uD55C Agent\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }) : null
1039
1162
  ] });
1040
1163
  }
1041
1164
  function ChatPane(props) {
@@ -1044,22 +1167,22 @@ function ChatPane(props) {
1044
1167
  const height = box?.height ?? 1;
1045
1168
  const lines = renderTranscript(props.messages, props.agent?.workflowName ?? "Agent", width);
1046
1169
  const view = viewportOf(lines, height, props.scrollUp);
1047
- useEffect6(() => {
1170
+ useEffect7(() => {
1048
1171
  props.onViewport(lines.length, height);
1049
1172
  }, [lines.length, height]);
1050
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", flexGrow: 1, borderStyle: "round", borderColor: "blue", paddingX: 1, children: [
1051
- /* @__PURE__ */ jsxs6(Box6, { children: [
1052
- /* @__PURE__ */ jsx6(Text6, { bold: true, wrap: "truncate-end", children: props.agent?.workflowName ?? "Agent\uB97C \uC120\uD0DD\uD558\uC138\uC694" }),
1053
- view.below > 0 ? /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1173
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", flexGrow: 1, borderStyle: "round", borderColor: "blue", paddingX: 1, children: [
1174
+ /* @__PURE__ */ jsxs7(Box7, { children: [
1175
+ /* @__PURE__ */ jsx7(Text7, { bold: true, wrap: "truncate-end", children: props.agent?.workflowName ?? "Agent\uB97C \uC120\uD0DD\uD558\uC138\uC694" }),
1176
+ view.below > 0 ? /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
1054
1177
  " \xB7 \u2193",
1055
1178
  view.below,
1056
1179
  "\uC904"
1057
1180
  ] }) : null
1058
1181
  ] }),
1059
- /* @__PURE__ */ jsxs6(Box6, { ref, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: [
1060
- view.lines.length === 0 ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, wrap: "truncate-end", 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,
1061
- view.lines.map((line) => /* @__PURE__ */ jsx6(
1062
- Text6,
1182
+ /* @__PURE__ */ jsxs7(Box7, { ref, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: [
1183
+ view.lines.length === 0 ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, wrap: "truncate-end", 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,
1184
+ view.lines.map((line) => /* @__PURE__ */ jsx7(
1185
+ Text7,
1063
1186
  {
1064
1187
  wrap: "truncate-end",
1065
1188
  bold: line.role === "label",
@@ -1070,17 +1193,17 @@ function ChatPane(props) {
1070
1193
  line.key
1071
1194
  ))
1072
1195
  ] }),
1073
- props.status ? /* @__PURE__ */ jsxs6(Text6, { color: "yellow", wrap: "truncate-end", children: [
1196
+ props.status ? /* @__PURE__ */ jsxs7(Text7, { color: "yellow", wrap: "truncate-end", children: [
1074
1197
  "\u25C6 ",
1075
1198
  props.status
1076
1199
  ] }) : null
1077
1200
  ] });
1078
1201
  }
1079
1202
  function Composer(props) {
1080
- return /* @__PURE__ */ jsxs6(Box6, { borderStyle: "round", borderColor: props.focused ? "cyan" : "gray", paddingX: 1, children: [
1081
- /* @__PURE__ */ jsx6(Text6, { color: props.hangulMode ? "yellow" : void 0, dimColor: !props.hangulMode, children: props.hangulMode ? "\uD55C" : "EN" }),
1082
- /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: " \u203A " }),
1083
- props.disabled ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\uC751\uB2F5\uC744 \uAE30\uB2E4\uB9AC\uB294 \uC911..." }) : /* @__PURE__ */ jsx6(
1203
+ return /* @__PURE__ */ jsxs7(Box7, { borderStyle: "round", borderColor: props.focused ? "cyan" : "gray", paddingX: 1, children: [
1204
+ /* @__PURE__ */ jsx7(Text7, { color: props.hangulMode ? "yellow" : void 0, dimColor: !props.hangulMode, children: props.hangulMode ? "\uD55C" : "EN" }),
1205
+ /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: " \u203A " }),
1206
+ props.disabled ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\uC751\uB2F5\uC744 \uAE30\uB2E4\uB9AC\uB294 \uC911..." }) : /* @__PURE__ */ jsx7(
1084
1207
  ImeTextInput,
1085
1208
  {
1086
1209
  value: props.value,
@@ -1098,28 +1221,42 @@ function Dashboard(props) {
1098
1221
  const { exit } = useApp();
1099
1222
  const size = useTerminalSize();
1100
1223
  const bodyHeight = Math.max(12, size.rows - 5);
1101
- const [focus, setFocus] = useState7("agents");
1102
- const [cursor, setCursor] = useState7(0);
1103
- const [selected, setSelected] = useState7(() => {
1224
+ const [focus, setFocus] = useState8("agents");
1225
+ const [cursor, setCursor] = useState8(0);
1226
+ const [creatingAgent, setCreatingAgent] = useState8(false);
1227
+ const [selected, setSelected] = useState8(() => {
1104
1228
  const first = props.session.agents[0];
1105
1229
  return first ? { workflowId: first.workflowId, workflowName: first.workflowName } : void 0;
1106
1230
  });
1107
- const [input, setInput] = useState7("");
1231
+ const [input, setInput] = useState8("");
1108
1232
  const [chat, dispatch] = useReducer(chatReducer, initialChatState);
1109
- const [palette, setPalette] = useState7(false);
1110
- const [history, setHistory] = useState7(false);
1111
- const [start, setStart] = useState7();
1112
- const [scrollUp, setScrollUp] = useState7(0);
1113
- const [hangulMode, setHangulMode] = useState7(props.preferences?.hangulMode ?? false);
1233
+ const [palette, setPalette] = useState8(false);
1234
+ const [history, setHistory] = useState8(false);
1235
+ const [start, setStart] = useState8();
1236
+ const [scrollUp, setScrollUp] = useState8(0);
1237
+ const [hangulMode, setHangulMode] = useState8(props.preferences?.hangulMode ?? false);
1114
1238
  const changeHangulMode = (enabled) => {
1115
1239
  setHangulMode(enabled);
1116
1240
  props.preferences?.onHangulModeChange?.(enabled);
1117
1241
  };
1242
+ useEffect7(
1243
+ () => props.preferences?.onModeKey?.(
1244
+ () => setHangulMode((current) => {
1245
+ props.preferences?.onHangulModeChange?.(!current);
1246
+ return !current;
1247
+ })
1248
+ ),
1249
+ [props.preferences]
1250
+ );
1118
1251
  const viewport = useRef3({ lineCount: 0, height: 0 });
1119
- const [starting, setStarting] = useState7(false);
1252
+ const [starting, setStarting] = useState8(false);
1120
1253
  const controller = useRef3(null);
1121
- useEffect6(() => () => controller.current?.abort(), []);
1254
+ useEffect7(() => () => controller.current?.abort(), []);
1122
1255
  const selectAgent = async () => {
1256
+ if (cursor === -1) {
1257
+ setCreatingAgent(true);
1258
+ return;
1259
+ }
1123
1260
  const agent = props.session.agents[cursor];
1124
1261
  if (!agent || chat.running || starting) return;
1125
1262
  const ref = { workflowId: agent.workflowId, workflowName: agent.workflowName };
@@ -1204,7 +1341,7 @@ function Dashboard(props) {
1204
1341
  const limit = maximumScroll(lineCount, height);
1205
1342
  setScrollUp((current) => Math.min(limit, Math.max(0, current - direction * step)));
1206
1343
  };
1207
- useInput5(
1344
+ useInput6(
1208
1345
  (keyInput, key) => {
1209
1346
  if (key.ctrl && keyInput === "k") setPalette(true);
1210
1347
  else if (key.ctrl && keyInput === "p") {
@@ -1219,14 +1356,14 @@ function Dashboard(props) {
1219
1356
  else if (key.escape && chat.running) cancelTurn();
1220
1357
  else if (key.escape) setFocus("agents");
1221
1358
  else if (key.tab) setFocus((current) => current === "agents" ? "composer" : "agents");
1222
- else if (focus === "agents" && key.upArrow) setCursor((current) => Math.max(0, current - 1));
1359
+ else if (focus === "agents" && key.upArrow) setCursor((current) => Math.max(-1, current - 1));
1223
1360
  else if (focus === "agents" && key.downArrow && props.session.agents.length > 0) {
1224
1361
  setCursor((current) => Math.min(props.session.agents.length - 1, current + 1));
1225
1362
  } else if (focus === "agents" && key.return) void selectAgent();
1226
1363
  },
1227
1364
  // 갈림길·팔레트·이력 화면이 떠 있으면 그 화면이 키를 갖는다 — 여기서도 받으면
1228
1365
  // 방향키 하나가 두 곳에서 움직인다.
1229
- { isActive: !palette && !history && !start }
1366
+ { isActive: !palette && !history && !start && !creatingAgent }
1230
1367
  );
1231
1368
  const paletteActions = [
1232
1369
  { id: "new", label: "\uC0C8 \uB300\uD654", run: newConversation },
@@ -1259,10 +1396,25 @@ function Dashboard(props) {
1259
1396
  { id: "quit", label: "\uC885\uB8CC", run: exit }
1260
1397
  ];
1261
1398
  let body;
1262
- if (palette) {
1263
- body = /* @__PURE__ */ jsx6(CommandPalette, { actions: paletteActions, onCancel: () => setPalette(false) });
1399
+ if (creatingAgent) {
1400
+ body = /* @__PURE__ */ jsx7(
1401
+ AgentCreateScreen,
1402
+ {
1403
+ engine: props.engine,
1404
+ profile: props.session.profile,
1405
+ hangulMode,
1406
+ onHangulModeChange: changeHangulMode,
1407
+ onCancel: () => setCreatingAgent(false),
1408
+ onCreated: (agent) => {
1409
+ setCreatingAgent(false);
1410
+ openNewChat(agent);
1411
+ }
1412
+ }
1413
+ );
1414
+ } else if (palette) {
1415
+ body = /* @__PURE__ */ jsx7(CommandPalette, { actions: paletteActions, onCancel: () => setPalette(false) });
1264
1416
  } else if (history) {
1265
- body = /* @__PURE__ */ jsx6(
1417
+ body = /* @__PURE__ */ jsx7(
1266
1418
  HistoryScreen,
1267
1419
  {
1268
1420
  engine: props.engine,
@@ -1272,7 +1424,7 @@ function Dashboard(props) {
1272
1424
  }
1273
1425
  );
1274
1426
  } else {
1275
- const sidebar = /* @__PURE__ */ jsx6(
1427
+ const sidebar = /* @__PURE__ */ jsx7(
1276
1428
  AgentSidebar,
1277
1429
  {
1278
1430
  agents: props.session.agents,
@@ -1282,7 +1434,7 @@ function Dashboard(props) {
1282
1434
  height: bodyHeight
1283
1435
  }
1284
1436
  );
1285
- const conversation = start ? /* @__PURE__ */ jsx6(
1437
+ const conversation = start ? /* @__PURE__ */ jsx7(
1286
1438
  StartPanel,
1287
1439
  {
1288
1440
  engine: props.engine,
@@ -1296,8 +1448,8 @@ function Dashboard(props) {
1296
1448
  setFocus("agents");
1297
1449
  }
1298
1450
  }
1299
- ) : /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", flexGrow: 1, children: [
1300
- /* @__PURE__ */ jsx6(
1451
+ ) : /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", flexGrow: 1, children: [
1452
+ /* @__PURE__ */ jsx7(
1301
1453
  ChatPane,
1302
1454
  {
1303
1455
  agent: selected,
@@ -1309,7 +1461,7 @@ function Dashboard(props) {
1309
1461
  }
1310
1462
  }
1311
1463
  ),
1312
- /* @__PURE__ */ jsx6(
1464
+ /* @__PURE__ */ jsx7(
1313
1465
  Composer,
1314
1466
  {
1315
1467
  value: input,
@@ -1322,13 +1474,13 @@ function Dashboard(props) {
1322
1474
  }
1323
1475
  )
1324
1476
  ] });
1325
- body = size.wide ? /* @__PURE__ */ jsxs6(Box6, { height: bodyHeight, children: [
1477
+ body = size.wide ? /* @__PURE__ */ jsxs7(Box7, { height: bodyHeight, children: [
1326
1478
  sidebar,
1327
1479
  conversation
1328
- ] }) : focus === "agents" ? /* @__PURE__ */ jsx6(Box6, { height: bodyHeight, children: sidebar }) : /* @__PURE__ */ jsx6(Box6, { height: bodyHeight, children: conversation });
1480
+ ] }) : focus === "agents" ? /* @__PURE__ */ jsx7(Box7, { height: bodyHeight, children: sidebar }) : /* @__PURE__ */ jsx7(Box7, { height: bodyHeight, children: conversation });
1329
1481
  }
1330
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
1331
- /* @__PURE__ */ jsx6(
1482
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
1483
+ /* @__PURE__ */ jsx7(
1332
1484
  Header,
1333
1485
  {
1334
1486
  profile: props.session.profile,
@@ -1337,7 +1489,7 @@ function Dashboard(props) {
1337
1489
  }
1338
1490
  ),
1339
1491
  body,
1340
- /* @__PURE__ */ jsx6(
1492
+ /* @__PURE__ */ jsx7(
1341
1493
  Footer,
1342
1494
  {
1343
1495
  mode: hangulMode ? "\uD55C" : "EN",
@@ -1348,14 +1500,14 @@ function Dashboard(props) {
1348
1500
  }
1349
1501
 
1350
1502
  // src/tui/login-screen.tsx
1351
- import { useState as useState8 } from "react";
1352
- import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
1353
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1503
+ import { useState as useState9 } from "react";
1504
+ import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
1505
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1354
1506
  function LoginScreen(props) {
1355
- const [email, setEmail] = useState8("");
1356
- const [password, setPassword] = useState8("");
1357
- const [focus, setFocus] = useState8("email");
1358
- useInput6(
1507
+ const [email, setEmail] = useState9("");
1508
+ const [password, setPassword] = useState9("");
1509
+ const [focus, setFocus] = useState9("email");
1510
+ useInput7(
1359
1511
  (input, key) => {
1360
1512
  if (key.tab) setFocus((current) => current === "email" ? "password" : "email");
1361
1513
  if (key.ctrl && input === "p") props.onProfiles();
@@ -1374,17 +1526,17 @@ function LoginScreen(props) {
1374
1526
  props.onSubmit(email.trim(), secret);
1375
1527
  }
1376
1528
  };
1377
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
1378
- /* @__PURE__ */ jsx7(Header, { profile: props.profile, connected: false }),
1379
- /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", borderStyle: "round", borderColor: "blue", padding: 1, children: [
1380
- /* @__PURE__ */ jsx7(Text7, { bold: true, children: "\uB85C\uADF8\uC778" }),
1381
- /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
1529
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
1530
+ /* @__PURE__ */ jsx8(Header, { profile: props.profile, connected: false }),
1531
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "blue", padding: 1, children: [
1532
+ /* @__PURE__ */ jsx8(Text8, { bold: true, children: "\uB85C\uADF8\uC778" }),
1533
+ /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
1382
1534
  props.serverUrl,
1383
1535
  " ",
1384
- /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: "(Ctrl+E \uBC14\uAFB8\uAE30)" })
1536
+ /* @__PURE__ */ jsx8(Text8, { color: "cyan", children: "(Ctrl+E \uBC14\uAFB8\uAE30)" })
1385
1537
  ] }),
1386
- /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginTop: 1, children: [
1387
- /* @__PURE__ */ jsx7(
1538
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
1539
+ /* @__PURE__ */ jsx8(
1388
1540
  FormField,
1389
1541
  {
1390
1542
  label: "Email",
@@ -1395,7 +1547,7 @@ function LoginScreen(props) {
1395
1547
  placeholder: "me@corp.com"
1396
1548
  }
1397
1549
  ),
1398
- /* @__PURE__ */ jsx7(
1550
+ /* @__PURE__ */ jsx8(
1399
1551
  FormField,
1400
1552
  {
1401
1553
  label: "Password",
@@ -1407,25 +1559,25 @@ function LoginScreen(props) {
1407
1559
  }
1408
1560
  )
1409
1561
  ] }),
1410
- props.busy ? /* @__PURE__ */ jsx7(Loading, { label: "\uB85C\uADF8\uC778\uD558\uB294 \uC911..." }) : null,
1411
- props.error ? /* @__PURE__ */ jsx7(Notice, { error: true, children: props.error }) : null
1562
+ props.busy ? /* @__PURE__ */ jsx8(Loading, { label: "\uB85C\uADF8\uC778\uD558\uB294 \uC911..." }) : null,
1563
+ props.error ? /* @__PURE__ */ jsx8(Notice, { error: true, children: props.error }) : null
1412
1564
  ] }),
1413
- /* @__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" })
1565
+ /* @__PURE__ */ jsx8(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" })
1414
1566
  ] });
1415
1567
  }
1416
1568
 
1417
1569
  // src/tui/profile-screen.tsx
1418
- import { useEffect as useEffect7, useState as useState9 } from "react";
1419
- import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
1420
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1570
+ import { useEffect as useEffect8, useState as useState10 } from "react";
1571
+ import { Box as Box9, Text as Text9, useInput as useInput8 } from "ink";
1572
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1421
1573
  function ProfileScreen(props) {
1422
- const [cursor, setCursor] = useState9(Math.max(0, props.profiles.findIndex((profile) => profile.current)));
1423
- const [creating, setCreating] = useState9(false);
1424
- const [focus, setFocus] = useState9("name");
1425
- const [name, setName] = useState9("");
1426
- const [serverUrl, setServerUrl] = useState9("");
1427
- useEffect7(() => setCursor((current) => Math.min(current, Math.max(0, props.profiles.length - 1))), [props.profiles]);
1428
- useInput7(
1574
+ const [cursor, setCursor] = useState10(Math.max(0, props.profiles.findIndex((profile) => profile.current)));
1575
+ const [creating, setCreating] = useState10(false);
1576
+ const [focus, setFocus] = useState10("name");
1577
+ const [name, setName] = useState10("");
1578
+ const [serverUrl, setServerUrl] = useState10("");
1579
+ useEffect8(() => setCursor((current) => Math.min(current, Math.max(0, props.profiles.length - 1))), [props.profiles]);
1580
+ useInput8(
1429
1581
  (input, key) => {
1430
1582
  if (key.escape) {
1431
1583
  if (creating) setCreating(false);
@@ -1455,12 +1607,12 @@ function ProfileScreen(props) {
1455
1607
  }
1456
1608
  if (name.trim() && serverUrl.trim()) props.onCreate(name.trim(), serverUrl.trim());
1457
1609
  };
1458
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
1459
- /* @__PURE__ */ jsx8(Header, {}),
1460
- /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", padding: 1, children: [
1461
- /* @__PURE__ */ jsx8(Text8, { bold: true, children: creating ? "\uC0C8 \uD504\uB85C\uD544" : "\uD504\uB85C\uD544 \uC804\uD658" }),
1462
- creating ? /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
1463
- /* @__PURE__ */ jsx8(
1610
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
1611
+ /* @__PURE__ */ jsx9(Header, {}),
1612
+ /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", padding: 1, children: [
1613
+ /* @__PURE__ */ jsx9(Text9, { bold: true, children: creating ? "\uC0C8 \uD504\uB85C\uD544" : "\uD504\uB85C\uD544 \uC804\uD658" }),
1614
+ creating ? /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginTop: 1, children: [
1615
+ /* @__PURE__ */ jsx9(
1464
1616
  FormField,
1465
1617
  {
1466
1618
  label: "Name",
@@ -1471,7 +1623,7 @@ function ProfileScreen(props) {
1471
1623
  placeholder: "corp"
1472
1624
  }
1473
1625
  ),
1474
- /* @__PURE__ */ jsx8(
1626
+ /* @__PURE__ */ jsx9(
1475
1627
  FormField,
1476
1628
  {
1477
1629
  label: "Server URL",
@@ -1482,8 +1634,8 @@ function ProfileScreen(props) {
1482
1634
  placeholder: "https://xgen.example.com"
1483
1635
  }
1484
1636
  )
1485
- ] }) : /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
1486
- props.profiles.map((profile, index) => /* @__PURE__ */ jsxs8(Text8, { color: index === cursor ? "cyan" : void 0, children: [
1637
+ ] }) : /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginTop: 1, children: [
1638
+ props.profiles.map((profile, index) => /* @__PURE__ */ jsxs9(Text9, { color: index === cursor ? "cyan" : void 0, children: [
1487
1639
  index === cursor ? "\u203A" : " ",
1488
1640
  " ",
1489
1641
  profile.current ? "\u25CF" : "\u25CB",
@@ -1491,14 +1643,14 @@ function ProfileScreen(props) {
1491
1643
  profile.name,
1492
1644
  " \xB7",
1493
1645
  " ",
1494
- /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: profile.serverUrl })
1646
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: profile.serverUrl })
1495
1647
  ] }, profile.name)),
1496
- props.profiles.length === 0 ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "\uC800\uC7A5\uB41C \uD504\uB85C\uD544\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." }) : null
1648
+ props.profiles.length === 0 ? /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "\uC800\uC7A5\uB41C \uD504\uB85C\uD544\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." }) : null
1497
1649
  ] }),
1498
- props.busy ? /* @__PURE__ */ jsx8(Loading, { label: "\uD504\uB85C\uD544\uC744 \uC804\uD658\uD558\uB294 \uC911..." }) : null,
1499
- props.error ? /* @__PURE__ */ jsx8(Notice, { error: true, children: props.error }) : null
1650
+ props.busy ? /* @__PURE__ */ jsx9(Loading, { label: "\uD504\uB85C\uD544\uC744 \uC804\uD658\uD558\uB294 \uC911..." }) : null,
1651
+ props.error ? /* @__PURE__ */ jsx9(Notice, { error: true, children: props.error }) : null
1500
1652
  ] }),
1501
- /* @__PURE__ */ jsx8(
1653
+ /* @__PURE__ */ jsx9(
1502
1654
  Footer,
1503
1655
  {
1504
1656
  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"
@@ -1508,24 +1660,24 @@ function ProfileScreen(props) {
1508
1660
  }
1509
1661
 
1510
1662
  // src/tui/server-screen.tsx
1511
- import { useState as useState10 } from "react";
1512
- import { Box as Box9, Text as Text9, useInput as useInput8 } from "ink";
1513
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1663
+ import { useState as useState11 } from "react";
1664
+ import { Box as Box10, Text as Text10, useInput as useInput9 } from "ink";
1665
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1514
1666
  function ServerScreen(props) {
1515
- const [serverUrl, setServerUrl] = useState10(props.initialValue ?? "");
1667
+ const [serverUrl, setServerUrl] = useState11(props.initialValue ?? "");
1516
1668
  const editing = props.initialValue !== void 0;
1517
- useInput8(
1669
+ useInput9(
1518
1670
  (_input, key) => {
1519
1671
  if (key.escape) props.onCancel?.();
1520
1672
  },
1521
1673
  { isActive: !props.busy && !!props.onCancel }
1522
1674
  );
1523
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
1524
- /* @__PURE__ */ jsx9(Header, { profile: props.profile, connected: false }),
1525
- /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", borderStyle: "round", borderColor: "blue", padding: 1, children: [
1526
- /* @__PURE__ */ jsx9(Text9, { bold: true, children: editing ? "\uC11C\uBC84 \uC8FC\uC18C \uBC14\uAFB8\uAE30" : "\uCC98\uC74C \uC624\uC168\uAD70\uC694" }),
1527
- /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "\uC5F0\uACB0\uD560 XGEN Gateway \uC8FC\uC18C\uB97C \uC785\uB825\uD558\uC138\uC694." }),
1528
- /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(
1675
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
1676
+ /* @__PURE__ */ jsx10(Header, { profile: props.profile, connected: false }),
1677
+ /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", borderStyle: "round", borderColor: "blue", padding: 1, children: [
1678
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: editing ? "\uC11C\uBC84 \uC8FC\uC18C \uBC14\uAFB8\uAE30" : "\uCC98\uC74C \uC624\uC168\uAD70\uC694" }),
1679
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "\uC5F0\uACB0\uD560 XGEN Gateway \uC8FC\uC18C\uB97C \uC785\uB825\uD558\uC138\uC694." }),
1680
+ /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(
1529
1681
  FormField,
1530
1682
  {
1531
1683
  label: "Server URL",
@@ -1536,11 +1688,11 @@ function ServerScreen(props) {
1536
1688
  placeholder: "xgen.example.com"
1537
1689
  }
1538
1690
  ) }),
1539
- /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "https:// \uB294 \uC0DD\uB7B5\uD574\uB3C4 \uB429\uB2C8\uB2E4." }),
1540
- props.busy ? /* @__PURE__ */ jsx9(Loading, { label: "\uC11C\uBC84 \uD504\uB85C\uD544\uC744 \uC800\uC7A5\uD558\uB294 \uC911..." }) : null,
1541
- props.error ? /* @__PURE__ */ jsx9(Notice, { error: true, children: props.error }) : null
1691
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "https:// \uB294 \uC0DD\uB7B5\uD574\uB3C4 \uB429\uB2C8\uB2E4." }),
1692
+ props.busy ? /* @__PURE__ */ jsx10(Loading, { label: "\uC11C\uBC84 \uD504\uB85C\uD544\uC744 \uC800\uC7A5\uD558\uB294 \uC911..." }) : null,
1693
+ props.error ? /* @__PURE__ */ jsx10(Notice, { error: true, children: props.error }) : null
1542
1694
  ] }),
1543
- /* @__PURE__ */ jsx9(
1695
+ /* @__PURE__ */ jsx10(
1544
1696
  Footer,
1545
1697
  {
1546
1698
  text: props.onCancel ? "Enter \uC800\uC7A5 \xB7 Esc \uCDE8\uC18C \xB7 Ctrl+Q \uC885\uB8CC" : "Enter \uACC4\uC18D \xB7 Ctrl+Q \uC885\uB8CC"
@@ -1550,20 +1702,20 @@ function ServerScreen(props) {
1550
1702
  }
1551
1703
 
1552
1704
  // src/tui/app.tsx
1553
- import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1705
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1554
1706
  function App({
1555
1707
  engine,
1556
1708
  preferences
1557
1709
  }) {
1558
1710
  const { exit } = useApp2();
1559
- const [route, setRoute] = useState11("boot");
1560
- const [session, setSession] = useState11();
1561
- const [profiles, setProfiles] = useState11([]);
1562
- const [loginTarget, setLoginTarget] = useState11();
1563
- const [editingServer, setEditingServer] = useState11();
1564
- const [busy, setBusy] = useState11(false);
1565
- const [error, setError] = useState11();
1566
- useInput9((input, key) => {
1711
+ const [route, setRoute] = useState12("boot");
1712
+ const [session, setSession] = useState12();
1713
+ const [profiles, setProfiles] = useState12([]);
1714
+ const [loginTarget, setLoginTarget] = useState12();
1715
+ const [editingServer, setEditingServer] = useState12();
1716
+ const [busy, setBusy] = useState12(false);
1717
+ const [error, setError] = useState12();
1718
+ useInput10((input, key) => {
1567
1719
  if (key.ctrl && input === "q") exit();
1568
1720
  });
1569
1721
  const bootstrap = useCallback(
@@ -1613,7 +1765,7 @@ function App({
1613
1765
  },
1614
1766
  [engine]
1615
1767
  );
1616
- useEffect8(() => {
1768
+ useEffect9(() => {
1617
1769
  void bootstrap();
1618
1770
  }, [bootstrap]);
1619
1771
  const configure = async (serverUrl) => {
@@ -1688,14 +1840,14 @@ function App({
1688
1840
  }
1689
1841
  };
1690
1842
  if (route === "boot") {
1691
- return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
1692
- /* @__PURE__ */ jsx10(Header, {}),
1693
- /* @__PURE__ */ jsx10(Loading, { label: "Dex\uB97C \uC900\uBE44\uD558\uB294 \uC911..." }),
1694
- /* @__PURE__ */ jsx10(Footer, { text: "Ctrl+Q \uC885\uB8CC" })
1843
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
1844
+ /* @__PURE__ */ jsx11(Header, {}),
1845
+ /* @__PURE__ */ jsx11(Loading, { label: "Dex\uB97C \uC900\uBE44\uD558\uB294 \uC911..." }),
1846
+ /* @__PURE__ */ jsx11(Footer, { text: "Ctrl+Q \uC885\uB8CC" })
1695
1847
  ] });
1696
1848
  }
1697
1849
  if (route === "server") {
1698
- return /* @__PURE__ */ jsx10(
1850
+ return /* @__PURE__ */ jsx11(
1699
1851
  ServerScreen,
1700
1852
  {
1701
1853
  initialValue: editingServer?.serverUrl,
@@ -1712,7 +1864,7 @@ function App({
1712
1864
  );
1713
1865
  }
1714
1866
  if (route === "login" && loginTarget) {
1715
- return /* @__PURE__ */ jsx10(
1867
+ return /* @__PURE__ */ jsx11(
1716
1868
  LoginScreen,
1717
1869
  {
1718
1870
  profile: loginTarget.profile,
@@ -1730,7 +1882,7 @@ function App({
1730
1882
  );
1731
1883
  }
1732
1884
  if (route === "profiles") {
1733
- return /* @__PURE__ */ jsx10(
1885
+ return /* @__PURE__ */ jsx11(
1734
1886
  ProfileScreen,
1735
1887
  {
1736
1888
  profiles,
@@ -1743,7 +1895,7 @@ function App({
1743
1895
  );
1744
1896
  }
1745
1897
  if (route === "dashboard" && session) {
1746
- return /* @__PURE__ */ jsx10(
1898
+ return /* @__PURE__ */ jsx11(
1747
1899
  Dashboard,
1748
1900
  {
1749
1901
  preferences,
@@ -1754,41 +1906,41 @@ function App({
1754
1906
  }
1755
1907
  );
1756
1908
  }
1757
- return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
1758
- /* @__PURE__ */ jsx10(Header, {}),
1759
- /* @__PURE__ */ jsx10(Notice, { error: true, children: error ?? "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4." }),
1760
- /* @__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." }),
1761
- /* @__PURE__ */ jsx10(Footer, { text: "R \uB2E4\uC2DC \uC2DC\uB3C4 \xB7 Ctrl+Q \uC885\uB8CC" }),
1762
- /* @__PURE__ */ jsx10(RetryInput, { onRetry: () => void bootstrap() })
1909
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
1910
+ /* @__PURE__ */ jsx11(Header, {}),
1911
+ /* @__PURE__ */ jsx11(Notice, { error: true, children: error ?? "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4." }),
1912
+ /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "\uC11C\uBC84\uC640 \uD0A4\uCCB4\uC778 \uC0C1\uD0DC\uB97C \uD655\uC778\uD55C \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694." }),
1913
+ /* @__PURE__ */ jsx11(Footer, { text: "R \uB2E4\uC2DC \uC2DC\uB3C4 \xB7 Ctrl+Q \uC885\uB8CC" }),
1914
+ /* @__PURE__ */ jsx11(RetryInput, { onRetry: () => void bootstrap() })
1763
1915
  ] });
1764
1916
  }
1765
1917
  function RetryInput({ onRetry }) {
1766
- useInput9((input) => {
1918
+ useInput10((input) => {
1767
1919
  if (input.toLowerCase() === "r") onRetry();
1768
1920
  });
1769
1921
  return null;
1770
1922
  }
1771
1923
 
1772
1924
  // src/tui/screen.ts
1773
- var ESC2 = "\x1B";
1774
- var ENTER_ALT_SCREEN = `${ESC2}[?1049h`;
1775
- var LEAVE_ALT_SCREEN = `${ESC2}[?1049l`;
1776
- var SHOW_CURSOR = `${ESC2}[?25h`;
1925
+ var ESC = "\x1B";
1926
+ var ENTER_ALT_SCREEN = `${ESC}[?1049h`;
1927
+ var LEAVE_ALT_SCREEN = `${ESC}[?1049l`;
1928
+ var SHOW_CURSOR = `${ESC}[?25h`;
1777
1929
  var DISABLE_REPORTS = [
1778
- `${ESC2}[?1004l`,
1930
+ `${ESC}[?1004l`,
1779
1931
  // 포커스 들어옴/나감
1780
- `${ESC2}[?1000l`,
1932
+ `${ESC}[?1000l`,
1781
1933
  // 마우스 클릭
1782
- `${ESC2}[?1002l`,
1934
+ `${ESC}[?1002l`,
1783
1935
  // 마우스 드래그
1784
- `${ESC2}[?1003l`,
1936
+ `${ESC}[?1003l`,
1785
1937
  // 마우스 이동 전부
1786
- `${ESC2}[?1006l`
1938
+ `${ESC}[?1006l`
1787
1939
  // SGR 확장 좌표
1788
1940
  ].join("");
1789
- var ENABLE_BRACKETED_PASTE = `${ESC2}[?2004h`;
1790
- var DISABLE_BRACKETED_PASTE = `${ESC2}[?2004l`;
1791
- var POP_KITTY_KEYBOARD = `${ESC2}[<u`;
1941
+ var ENABLE_BRACKETED_PASTE = `${ESC}[?2004h`;
1942
+ var DISABLE_BRACKETED_PASTE = `${ESC}[?2004l`;
1943
+ var POP_KITTY_KEYBOARD = `${ESC}[<u`;
1792
1944
  function createScreenGuard(stream, options = {}) {
1793
1945
  const alt = Boolean(stream.isTTY);
1794
1946
  let entered = false;
@@ -1841,8 +1993,133 @@ async function writePreferences(preferences, env = process.env) {
1841
1993
  }
1842
1994
  }
1843
1995
 
1996
+ // src/tui/kitty.ts
1997
+ var ESC2 = "\x1B";
1998
+ var QUERY = `${ESC2}[?u`;
1999
+ var KEY_CODES = {
2000
+ 57358: "capslock",
2001
+ 57449: "rightalt"
2002
+ };
2003
+ var CAPS_LOCK_BIT = 64;
2004
+ function parseKeyEvents(chunk) {
2005
+ const events = [];
2006
+ const pattern = /\u001B\[(\d+)(?::\d+)*(?:;(\d+)(?::(\d+))?)?(?:;[\d:]*)?u/g;
2007
+ for (const match of chunk.matchAll(pattern)) {
2008
+ const code = Number(match[1]);
2009
+ const modifiers = match[2] ? Number(match[2]) - 1 : 0;
2010
+ events.push({
2011
+ name: KEY_CODES[code],
2012
+ // eslint-disable-next-line no-bitwise
2013
+ capsLock: (modifiers & CAPS_LOCK_BIT) !== 0,
2014
+ eventType: match[3] ? Number(match[3]) : 1
2015
+ });
2016
+ }
2017
+ return events;
2018
+ }
2019
+ function knownKittyTerminal(env = process.env) {
2020
+ if (env.KITTY_WINDOW_ID) return true;
2021
+ if (env.GHOSTTY_RESOURCES_DIR) return true;
2022
+ const term = (env.TERM ?? "").toLowerCase();
2023
+ if (term === "xterm-kitty" || term === "xterm-ghostty" || term.includes("foot")) return true;
2024
+ const program = (env.TERM_PROGRAM ?? "").toLowerCase();
2025
+ return program === "wezterm" || program === "ghostty" || program === "kitty";
2026
+ }
2027
+ async function supportsKittyKeyboard(streams, timeoutMs = 200, env = process.env) {
2028
+ if (env.DEX_NO_KITTY === "1") return false;
2029
+ if (!streams.stdout.isTTY || !streams.isTTY) return false;
2030
+ if (knownKittyTerminal(env)) return true;
2031
+ const wasRaw = streams.stdin.isRaw === true;
2032
+ const wasPaused = !wasRaw;
2033
+ streams.stdin.setRawMode?.(true);
2034
+ streams.stdin.resume?.();
2035
+ return new Promise((resolve) => {
2036
+ let settled = false;
2037
+ let seen = "";
2038
+ const finish = (supported) => {
2039
+ if (settled) return;
2040
+ settled = true;
2041
+ clearTimeout(timer);
2042
+ streams.stdin.removeListener("data", onData);
2043
+ if (!wasRaw) streams.stdin.setRawMode?.(false);
2044
+ if (wasPaused) streams.stdin.pause?.();
2045
+ resolve(supported);
2046
+ };
2047
+ const onData = (data) => {
2048
+ seen += typeof data === "string" ? data : data.toString("utf8");
2049
+ if (/\u001B\[\?\d*u/.test(seen)) finish(true);
2050
+ };
2051
+ const timer = setTimeout(() => finish(false), timeoutMs);
2052
+ streams.stdin.on("data", onData);
2053
+ streams.stdout.write(QUERY);
2054
+ });
2055
+ }
2056
+ var KEY_EVENT = /\u001B\[([0-9]+)(?::[0-9:]*)?(?:;([0-9]*(?::[0-9]+)?))?(?:;([0-9:]+))?u/g;
2057
+ function normalizeKeyEvents(chunk) {
2058
+ return chunk.replace(
2059
+ KEY_EVENT,
2060
+ (all, code, modifiers, text) => {
2061
+ const hasAlternate = all.slice(0, all.indexOf(";") === -1 ? all.length : all.indexOf(";")).includes(":");
2062
+ const needsModifier = modifiers !== void 0 && modifiers.length === 0;
2063
+ if (!hasAlternate && !needsModifier) return all;
2064
+ const mods = modifiers === void 0 ? void 0 : modifiers.length > 0 ? modifiers : "1";
2065
+ const fields = [code, mods, text].filter((field) => field !== void 0).join(";");
2066
+ return `\x1B[${fields}u`;
2067
+ }
2068
+ );
2069
+ }
2070
+ var PARTIAL_TAIL = /\u001B\[[0-9:;]*$/;
2071
+ var MAX_PARTIAL = 64;
2072
+ function createNormalizer() {
2073
+ let pending = "";
2074
+ return (chunk) => {
2075
+ const combined = pending + chunk;
2076
+ const match = PARTIAL_TAIL.exec(combined);
2077
+ if (match && combined.length - match.index <= MAX_PARTIAL) {
2078
+ pending = combined.slice(match.index);
2079
+ return normalizeKeyEvents(combined.slice(0, match.index));
2080
+ }
2081
+ pending = "";
2082
+ return normalizeKeyEvents(combined);
2083
+ };
2084
+ }
2085
+ function normalizedStdin(stdin2, onSpecialKey) {
2086
+ const normalize = createNormalizer();
2087
+ return new Proxy(stdin2, {
2088
+ get(target, property, receiver) {
2089
+ if (property === "read") {
2090
+ return (...args) => {
2091
+ const chunk = target.read(...args);
2092
+ if (chunk === null || chunk === void 0) return null;
2093
+ const text = typeof chunk === "string" ? chunk : String(chunk);
2094
+ if (onSpecialKey) {
2095
+ for (const event of parseKeyEvents(text)) {
2096
+ if (event.name && event.eventType === 1) onSpecialKey(event.name);
2097
+ }
2098
+ }
2099
+ return normalize(text);
2100
+ };
2101
+ }
2102
+ void receiver;
2103
+ const value = Reflect.get(target, property, target);
2104
+ return typeof value === "function" ? value.bind(target) : value;
2105
+ }
2106
+ });
2107
+ }
2108
+ function createKeySignal() {
2109
+ const listeners = /* @__PURE__ */ new Set();
2110
+ return {
2111
+ notify(name) {
2112
+ for (const listener of listeners) listener(name);
2113
+ },
2114
+ subscribe(listener) {
2115
+ listeners.add(listener);
2116
+ return () => void listeners.delete(listener);
2117
+ }
2118
+ };
2119
+ }
2120
+
1844
2121
  // src/tui/index.tsx
1845
- import { jsx as jsx11 } from "react/jsx-runtime";
2122
+ import { jsx as jsx12 } from "react/jsx-runtime";
1846
2123
  async function runTui(engine) {
1847
2124
  let screen = createScreenGuard(stdout);
1848
2125
  const restore = () => screen.restore();
@@ -1856,22 +2133,31 @@ async function runTui(engine) {
1856
2133
  const preferences = await readPreferences();
1857
2134
  const kitty = await supportsKittyKeyboard({ stdin, stdout, isTTY: stdin.isTTY });
1858
2135
  screen = createScreenGuard(stdout, { kittyKeyboard: kitty });
2136
+ const modeKeys = createKeySignal();
1859
2137
  screen.enter();
1860
2138
  try {
1861
2139
  const instance = render(
1862
- /* @__PURE__ */ jsx11(
2140
+ /* @__PURE__ */ jsx12(
1863
2141
  App,
1864
2142
  {
1865
2143
  engine,
1866
2144
  preferences: {
1867
2145
  hangulMode: preferences.hangulMode,
1868
- onHangulModeChange: (enabled) => void writePreferences({ hangulMode: enabled })
2146
+ onHangulModeChange: (enabled) => void writePreferences({ hangulMode: enabled }),
2147
+ onModeKey: modeKeys.subscribe
1869
2148
  }
1870
2149
  }
1871
2150
  ),
1872
2151
  {
1873
2152
  exitOnCtrlC: true,
1874
2153
  patchConsole: true,
2154
+ // 프로토콜을 켜면 kitty 가 ink 이 못 읽는 모양으로 키를 보낸다 — 사이에서
2155
+ // 고쳐 넘기지 않으면 글자가 하나도 입력되지 않는다.
2156
+ ...kitty ? {
2157
+ stdin: normalizedStdin(stdin, (name) => {
2158
+ if (name === "rightalt" || name === "capslock") modeKeys.notify(name);
2159
+ })
2160
+ } : {},
1875
2161
  // 모든 키를 이스케이프로 받아야 한/영 키(오른쪽 Alt)와 Caps Lock 이 사건으로
1876
2162
  // 온다. 그런데 그렇게만 켜면 터미널이 **글쇠 코드만** 보내서 Shift+r 이
1877
2163
  // 소문자 `r` 로 도착한다 — 된소리도 영문 대문자도 사라진다. 그래서 글자까지
@@ -1899,4 +2185,4 @@ async function runTui(engine) {
1899
2185
  export {
1900
2186
  runTui
1901
2187
  };
1902
- //# sourceMappingURL=tui-H3VM2MGV.js.map
2188
+ //# sourceMappingURL=tui-WG5AIWUR.js.map