xgen-dex-cli 1.2.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.
@@ -0,0 +1,1048 @@
1
+ import {
2
+ DexError,
3
+ publicError
4
+ } from "./chunk-QXSCKZEG.js";
5
+
6
+ // src/tui/index.tsx
7
+ import { render } from "ink";
8
+
9
+ // 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";
12
+
13
+ // 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";
16
+
17
+ // src/tui/chat-state.ts
18
+ var initialChatState = { messages: [], running: false };
19
+ function lastMessageIndex(messages, predicate) {
20
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
21
+ if (predicate(messages[index])) return index;
22
+ }
23
+ return -1;
24
+ }
25
+ function appendAssistant(messages, content) {
26
+ const index = lastMessageIndex(messages, (message) => message.role === "assistant");
27
+ if (index < 0) {
28
+ return [...messages, { id: `assistant-${messages.length}`, role: "assistant", text: content }];
29
+ }
30
+ const next = [...messages];
31
+ next[index] = { ...next[index], text: next[index].text + content };
32
+ return next;
33
+ }
34
+ function upsertActivity(messages, key, text) {
35
+ const index = lastMessageIndex(
36
+ messages,
37
+ (message) => message.role === "activity" && message.activityKey === key
38
+ );
39
+ const startsNewRun = index >= 0 && text.endsWith("\uC2E4\uD589 \uC911") && !messages[index].text.endsWith("\uC2E4\uD589 \uC911");
40
+ if (index < 0 || startsNewRun) {
41
+ return [
42
+ ...messages,
43
+ { id: `activity-${messages.length}`, role: "activity", activityKey: key, text }
44
+ ];
45
+ }
46
+ const next = [...messages];
47
+ next[index] = { ...next[index], text };
48
+ return next;
49
+ }
50
+ function eventState(state, event) {
51
+ if (event.kind === "text") return { ...state, messages: appendAssistant(state.messages, event.content) };
52
+ if (event.kind === "summary") {
53
+ const assistantIndex = lastMessageIndex(state.messages, (message) => message.role === "assistant");
54
+ const assistant = assistantIndex >= 0 ? state.messages[assistantIndex] : void 0;
55
+ return assistant?.text ? state : { ...state, messages: appendAssistant(state.messages, event.text) };
56
+ }
57
+ if (event.kind === "tool") {
58
+ const tool = event.event.toolName ?? "tool";
59
+ const key = event.event.runId ?? tool;
60
+ const suffix = event.event.error ? `\uC2E4\uD328: ${event.event.error}` : event.event.eventType.includes("result") ? "\uC644\uB8CC" : "\uC2E4\uD589 \uC911";
61
+ return { ...state, messages: upsertActivity(state.messages, key, `${tool} \xB7 ${suffix}`) };
62
+ }
63
+ if (event.kind === "node_status") {
64
+ return { ...state, status: `${event.event.nodeId} \xB7 ${event.event.status}` };
65
+ }
66
+ if (event.kind === "status") {
67
+ return { ...state, status: event.detail ?? event.surface };
68
+ }
69
+ if (event.kind === "quota") {
70
+ return {
71
+ ...state,
72
+ messages: [
73
+ ...state.messages,
74
+ { id: `quota-${state.messages.length}`, role: "system", text: `Quota ${event.level}` }
75
+ ]
76
+ };
77
+ }
78
+ if (event.kind === "error") {
79
+ return {
80
+ ...state,
81
+ running: false,
82
+ status: void 0,
83
+ messages: [
84
+ ...state.messages,
85
+ { id: `error-${state.messages.length}`, role: "system", text: event.detail }
86
+ ]
87
+ };
88
+ }
89
+ if (event.kind === "end") return { ...state, running: false, status: void 0 };
90
+ return state;
91
+ }
92
+ function chatReducer(state, action) {
93
+ switch (action.type) {
94
+ case "reset":
95
+ return initialChatState;
96
+ case "history_loaded":
97
+ return {
98
+ interactionId: action.interactionId,
99
+ running: false,
100
+ messages: action.turns.flatMap((turn, index) => [
101
+ { id: `history-user-${index}`, role: "user", text: turn.input },
102
+ { id: `history-assistant-${index}`, role: "assistant", text: turn.output }
103
+ ])
104
+ };
105
+ case "turn_started":
106
+ return {
107
+ ...state,
108
+ interactionId: action.interactionId,
109
+ running: true,
110
+ status: "\uC751\uB2F5\uC744 \uAE30\uB2E4\uB9AC\uB294 \uC911",
111
+ messages: [
112
+ ...state.messages,
113
+ { id: `user-${state.messages.length}`, role: "user", text: action.input },
114
+ { id: `assistant-${state.messages.length + 1}`, role: "assistant", text: "" }
115
+ ]
116
+ };
117
+ case "event_received":
118
+ return eventState(state, action.event);
119
+ case "turn_completed":
120
+ return { ...state, running: false, status: void 0 };
121
+ case "turn_cancelled":
122
+ return { ...state, running: false, status: void 0 };
123
+ case "turn_failed":
124
+ return {
125
+ ...state,
126
+ running: false,
127
+ status: void 0,
128
+ messages: [
129
+ ...state.messages,
130
+ { id: `failure-${state.messages.length}`, role: "system", text: action.message }
131
+ ]
132
+ };
133
+ }
134
+ }
135
+
136
+ // src/tui/command-palette.tsx
137
+ import { useState as useState2 } from "react";
138
+ import { Box as Box2, Text as Text3, useInput as useInput2 } from "ink";
139
+
140
+ // src/tui/components.tsx
141
+ import { Box, Text as Text2 } from "ink";
142
+
143
+ // src/tui/ime-text-input.tsx
144
+ import { useEffect, useRef, useState } from "react";
145
+ import { Text, useCursor, useInput, useStdout } from "ink";
146
+ import stringWidth from "string-width";
147
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
148
+ var segmenter = new Intl.Segmenter("ko", { granularity: "grapheme" });
149
+ function graphemes(value) {
150
+ return [...segmenter.segment(value)].map(({ segment }) => segment);
151
+ }
152
+ function clamp(value, minimum, maximum) {
153
+ return Math.min(Math.max(value, minimum), maximum);
154
+ }
155
+ function visibleInput(segments, cursor, maximumWidth) {
156
+ let start = cursor;
157
+ let widthBeforeCursor = 0;
158
+ const followingWidth = cursor < segments.length ? stringWidth(segments[cursor] ?? "") : 0;
159
+ const beforeLimit = Math.max(0, maximumWidth - Math.min(followingWidth, maximumWidth));
160
+ while (start > 0) {
161
+ const width = stringWidth(segments[start - 1] ?? "");
162
+ if (widthBeforeCursor + width > beforeLimit) break;
163
+ widthBeforeCursor += width;
164
+ start -= 1;
165
+ }
166
+ let end = cursor;
167
+ let totalWidth = widthBeforeCursor;
168
+ while (end < segments.length) {
169
+ const width = stringWidth(segments[end] ?? "");
170
+ if (totalWidth + width > maximumWidth) break;
171
+ totalWidth += width;
172
+ end += 1;
173
+ }
174
+ return {
175
+ text: segments.slice(start, end).join(""),
176
+ cursorWidth: widthBeforeCursor
177
+ };
178
+ }
179
+ function TerminalCursor({ origin, offset }) {
180
+ const { setCursorPosition } = useCursor();
181
+ setCursorPosition({ x: origin.x + offset, y: origin.y });
182
+ return null;
183
+ }
184
+ function ImeTextInput(props) {
185
+ const { stdout } = useStdout();
186
+ const initialSegments = graphemes(props.value);
187
+ const [cursor, setCursor] = useState(initialSegments.length);
188
+ const valueRef = useRef(props.value);
189
+ const cursorRef = useRef(initialSegments.length);
190
+ const moveCursor = (next, length) => {
191
+ const resolved = clamp(next, 0, length);
192
+ cursorRef.current = resolved;
193
+ setCursor(resolved);
194
+ };
195
+ const updateValue = (segments, nextCursor) => {
196
+ const nextValue = segments.join("");
197
+ valueRef.current = nextValue;
198
+ moveCursor(nextCursor, segments.length);
199
+ props.onChange(nextValue);
200
+ };
201
+ useEffect(() => {
202
+ if (props.value === valueRef.current) return;
203
+ const previousLength = graphemes(valueRef.current).length;
204
+ const nextLength = graphemes(props.value).length;
205
+ const wasAtEnd = cursorRef.current >= previousLength;
206
+ valueRef.current = props.value;
207
+ moveCursor(wasAtEnd ? nextLength : cursorRef.current, nextLength);
208
+ }, [props.value]);
209
+ useInput(
210
+ (input, key) => {
211
+ const current = graphemes(valueRef.current);
212
+ const currentCursor = clamp(cursorRef.current, 0, current.length);
213
+ if (key.return) {
214
+ props.onSubmit?.(valueRef.current);
215
+ return;
216
+ }
217
+ if (key.leftArrow) {
218
+ moveCursor(currentCursor - 1, current.length);
219
+ return;
220
+ }
221
+ if (key.rightArrow) {
222
+ moveCursor(currentCursor + 1, current.length);
223
+ return;
224
+ }
225
+ if (key.home) {
226
+ moveCursor(0, current.length);
227
+ return;
228
+ }
229
+ if (key.end) {
230
+ moveCursor(current.length, current.length);
231
+ return;
232
+ }
233
+ if (key.backspace || key.delete) {
234
+ if (currentCursor === 0) return;
235
+ current.splice(currentCursor - 1, 1);
236
+ updateValue(current, currentCursor - 1);
237
+ return;
238
+ }
239
+ if (!input || key.ctrl || key.meta || key.tab || key.escape || key.upArrow || key.downArrow || key.pageUp || key.pageDown) {
240
+ return;
241
+ }
242
+ const inserted = graphemes(input);
243
+ current.splice(currentCursor, 0, ...inserted);
244
+ updateValue(current, currentCursor + inserted.length);
245
+ },
246
+ { isActive: props.focus }
247
+ );
248
+ const rawSegments = graphemes(props.value);
249
+ const safeCursor = clamp(cursor, 0, rawSegments.length);
250
+ const displayedSegments = props.mask ? rawSegments.map(() => props.mask ?? "") : rawSegments;
251
+ const maximumWidth = Math.max(1, (stdout.columns || 100) - props.cursorOrigin.x - 3);
252
+ const visible = visibleInput(displayedSegments, safeCursor, maximumWidth);
253
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
254
+ rawSegments.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: props.placeholder ?? "" }) : /* @__PURE__ */ jsx(Text, { children: visible.text }),
255
+ props.focus ? /* @__PURE__ */ jsx(TerminalCursor, { origin: props.cursorOrigin, offset: visible.cursorWidth }) : null
256
+ ] });
257
+ }
258
+
259
+ // src/tui/components.tsx
260
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
261
+ function Header(props) {
262
+ return /* @__PURE__ */ jsxs2(Box, { paddingX: 1, justifyContent: "space-between", children: [
263
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: "blueBright", children: "XGEN Dex" }),
264
+ props.profile ? /* @__PURE__ */ jsxs2(Text2, { children: [
265
+ props.profile,
266
+ " \xB7 ",
267
+ props.username ?? "\uB85C\uADF8\uC778 \uD544\uC694",
268
+ " \xB7",
269
+ " ",
270
+ /* @__PURE__ */ jsx2(Text2, { color: props.connected ? "green" : "yellow", children: props.connected ? "Connected" : "Offline" })
271
+ ] }) : /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\uC124\uC815 \uD544\uC694" })
272
+ ] });
273
+ }
274
+ function Footer({ text }) {
275
+ return /* @__PURE__ */ jsx2(Box, { paddingX: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: text }) });
276
+ }
277
+ function Loading({ label = "\uBD88\uB7EC\uC624\uB294 \uC911..." }) {
278
+ return /* @__PURE__ */ jsx2(Box, { padding: 1, children: /* @__PURE__ */ jsxs2(Text2, { color: "cyan", children: [
279
+ "\u25C6 ",
280
+ label
281
+ ] }) });
282
+ }
283
+ function Notice({ children, error = false }) {
284
+ return /* @__PURE__ */ jsx2(Box, { borderStyle: "round", borderColor: error ? "red" : "cyan", paddingX: 1, children: /* @__PURE__ */ jsx2(Text2, { color: error ? "red" : void 0, children }) });
285
+ }
286
+ function FormField(props) {
287
+ return /* @__PURE__ */ jsxs2(Box, { children: [
288
+ /* @__PURE__ */ jsx2(Box, { width: 14, children: /* @__PURE__ */ jsxs2(Text2, { color: props.focus ? "cyan" : void 0, children: [
289
+ props.focus ? "\u203A" : " ",
290
+ " ",
291
+ props.label
292
+ ] }) }),
293
+ /* @__PURE__ */ jsx2(
294
+ ImeTextInput,
295
+ {
296
+ value: props.value,
297
+ onChange: props.onChange,
298
+ onSubmit: props.onSubmit,
299
+ focus: props.focus,
300
+ cursorOrigin: props.cursorOrigin,
301
+ placeholder: props.placeholder,
302
+ mask: props.secret ? "\u2022" : void 0
303
+ }
304
+ )
305
+ ] });
306
+ }
307
+
308
+ // src/tui/command-palette.tsx
309
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
310
+ function CommandPalette(props) {
311
+ const [cursor, setCursor] = useState2(0);
312
+ useInput2((_input, key) => {
313
+ if (key.escape) props.onCancel();
314
+ if (key.upArrow) setCursor((current) => Math.max(0, current - 1));
315
+ if (key.downArrow) setCursor((current) => Math.min(props.actions.length - 1, current + 1));
316
+ if (key.return && props.actions[cursor]) props.actions[cursor].run();
317
+ });
318
+ return /* @__PURE__ */ jsxs3(Box2, { flexDirection: "column", flexGrow: 1, borderStyle: "double", borderColor: "magenta", padding: 1, children: [
319
+ /* @__PURE__ */ jsx3(Text3, { bold: true, children: "\uBA85\uB839" }),
320
+ /* @__PURE__ */ jsx3(Box2, { flexDirection: "column", marginTop: 1, children: props.actions.map((action, index) => /* @__PURE__ */ jsxs3(Text3, { color: index === cursor ? "magentaBright" : void 0, children: [
321
+ index === cursor ? "\u203A" : " ",
322
+ " ",
323
+ action.label
324
+ ] }, action.id)) }),
325
+ /* @__PURE__ */ jsx3(Footer, { text: "\u2191\u2193 \uC774\uB3D9 \xB7 Enter \uC2E4\uD589 \xB7 Esc \uB2EB\uAE30" })
326
+ ] });
327
+ }
328
+
329
+ // src/tui/history-screen.tsx
330
+ import { useEffect as useEffect2, useState as useState3 } from "react";
331
+ import { Box as Box3, Text as Text4, useInput as useInput3 } from "ink";
332
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
333
+ function HistoryScreen(props) {
334
+ const [items, setItems] = useState3([]);
335
+ const [cursor, setCursor] = useState3(0);
336
+ const [loading, setLoading] = useState3(true);
337
+ const [error, setError] = useState3();
338
+ useEffect2(() => {
339
+ let alive = true;
340
+ props.engine.listConversations(props.profile).then((result) => alive && setItems(result)).catch((reason) => alive && setError(publicError(reason).message)).finally(() => alive && setLoading(false));
341
+ return () => {
342
+ alive = false;
343
+ };
344
+ }, [props.engine, props.profile]);
345
+ useInput3(
346
+ (_input, key) => {
347
+ if (key.escape) props.onCancel();
348
+ if (key.upArrow) setCursor((current) => Math.max(0, current - 1));
349
+ if (key.downArrow && items.length > 0) {
350
+ setCursor((current) => Math.min(items.length - 1, current + 1));
351
+ }
352
+ if (key.return && items[cursor]) {
353
+ const conversation = items[cursor];
354
+ setLoading(true);
355
+ setError(void 0);
356
+ props.engine.historyTurns(
357
+ conversation.workflowId,
358
+ conversation.interactionId,
359
+ conversation.workflowName,
360
+ props.profile
361
+ ).then((turns) => props.onOpen(conversation, turns)).catch((reason) => setError(publicError(reason).message)).finally(() => setLoading(false));
362
+ }
363
+ },
364
+ { isActive: !loading }
365
+ );
366
+ return /* @__PURE__ */ jsxs4(Box3, { flexDirection: "column", flexGrow: 1, borderStyle: "round", borderColor: "cyan", padding: 1, children: [
367
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: "\uB300\uD654 \uAE30\uB85D" }),
368
+ loading ? /* @__PURE__ */ jsx4(Loading, {}) : null,
369
+ !loading && items.length === 0 ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\uB300\uD654 \uAE30\uB85D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." }) : null,
370
+ !loading ? items.slice(Math.max(0, cursor - 8), cursor + 9).map((item) => {
371
+ const index = items.indexOf(item);
372
+ return /* @__PURE__ */ jsxs4(Text4, { color: index === cursor ? "cyan" : void 0, children: [
373
+ index === cursor ? "\u203A" : " ",
374
+ " ",
375
+ item.workflowName,
376
+ " \xB7",
377
+ " ",
378
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: item.updatedAt || item.createdAt })
379
+ ] }, item.interactionId);
380
+ }) : null,
381
+ error ? /* @__PURE__ */ jsx4(Notice, { error: true, children: error }) : null,
382
+ /* @__PURE__ */ jsx4(Footer, { text: "\u2191\u2193 \uC774\uB3D9 \xB7 Enter \uC5F4\uAE30 \xB7 Esc \uB3CC\uC544\uAC00\uAE30" })
383
+ ] });
384
+ }
385
+
386
+ // src/tui/use-terminal-size.ts
387
+ import { useEffect as useEffect3, useState as useState4 } from "react";
388
+ import { useStdout as useStdout2 } from "ink";
389
+ function useTerminalSize() {
390
+ const { stdout } = useStdout2();
391
+ const read = () => {
392
+ const columns = stdout.columns || 100;
393
+ const rows = stdout.rows || 30;
394
+ return { columns, rows, wide: columns >= 88 };
395
+ };
396
+ const [size, setSize] = useState4(read);
397
+ useEffect3(() => {
398
+ const resize = () => setSize(read());
399
+ stdout.on("resize", resize);
400
+ return () => {
401
+ stdout.off("resize", resize);
402
+ };
403
+ }, [stdout]);
404
+ return size;
405
+ }
406
+
407
+ // src/tui/dashboard.tsx
408
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
409
+ function AgentSidebar(props) {
410
+ const radius = Math.max(3, Math.floor((props.height - 4) / 2));
411
+ const start = Math.max(0, props.cursor - radius);
412
+ 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" }),
415
+ visible.map((agent) => {
416
+ const index = props.agents.indexOf(agent);
417
+ const cursor = index === props.cursor;
418
+ const selected = agent.workflowId === props.selected;
419
+ return /* @__PURE__ */ jsxs5(Text5, { color: cursor && props.focused ? "cyan" : void 0, wrap: "truncate-end", children: [
420
+ cursor ? "\u203A" : " ",
421
+ " ",
422
+ selected ? "\u25CF" : "\u25CB",
423
+ " ",
424
+ agent.workflowName
425
+ ] }, agent.workflowId);
426
+ }),
427
+ props.agents.length === 0 ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\uC0AC\uC6A9 \uAC00\uB2A5\uD55C Agent\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }) : null
428
+ ] });
429
+ }
430
+ function messageColor(role) {
431
+ if (role === "user") return "cyan";
432
+ if (role === "assistant") return "green";
433
+ if (role === "activity") return "yellow";
434
+ if (role === "system") return "red";
435
+ return void 0;
436
+ }
437
+ function labelOf(role, agentName) {
438
+ if (role === "user") return "You";
439
+ if (role === "assistant") return agentName;
440
+ if (role === "activity") return "Tool";
441
+ return "System";
442
+ }
443
+ function ChatPane(props) {
444
+ const visibleCount = Math.max(4, Math.floor((props.height - 5) / 2));
445
+ 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" : "") })
453
+ ] }, message.id))
454
+ ] }),
455
+ props.status ? /* @__PURE__ */ jsxs5(Text5, { color: "yellow", children: [
456
+ "\u25C6 ",
457
+ props.status
458
+ ] }) : null
459
+ ] });
460
+ }
461
+ 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(
465
+ ImeTextInput,
466
+ {
467
+ value: props.value,
468
+ onChange: props.onChange,
469
+ onSubmit: props.onSubmit,
470
+ focus: props.focused,
471
+ cursorOrigin: props.cursorOrigin,
472
+ placeholder: "\uBA54\uC2DC\uC9C0\uB97C \uC785\uB825\uD558\uC138\uC694"
473
+ }
474
+ )
475
+ ] });
476
+ }
477
+ function Dashboard(props) {
478
+ const { exit } = useApp();
479
+ const size = useTerminalSize();
480
+ 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(() => {
484
+ const first = props.session.agents[0];
485
+ return first ? { workflowId: first.workflowId, workflowName: first.workflowName } : void 0;
486
+ });
487
+ const [input, setInput] = useState5("");
488
+ const [chat, dispatch] = useReducer(chatReducer, initialChatState);
489
+ const [palette, setPalette] = useState5(false);
490
+ const [history, setHistory] = useState5(false);
491
+ const controller = useRef2(null);
492
+ useEffect4(() => () => controller.current?.abort(), []);
493
+ const selectAgent = () => {
494
+ const agent = props.session.agents[cursor];
495
+ if (!agent || chat.running) return;
496
+ setSelected({ workflowId: agent.workflowId, workflowName: agent.workflowName });
497
+ dispatch({ type: "reset" });
498
+ setFocus("composer");
499
+ };
500
+ const openHistory = (conversation, turns) => {
501
+ setSelected({ workflowId: conversation.workflowId, workflowName: conversation.workflowName });
502
+ dispatch({ type: "history_loaded", interactionId: conversation.interactionId, turns });
503
+ const index = props.session.agents.findIndex((agent) => agent.workflowId === conversation.workflowId);
504
+ if (index >= 0) setCursor(index);
505
+ setHistory(false);
506
+ setFocus("composer");
507
+ };
508
+ const cancelTurn = () => {
509
+ controller.current?.abort();
510
+ dispatch({ type: "turn_cancelled" });
511
+ };
512
+ const newConversation = () => {
513
+ if (chat.running) return;
514
+ dispatch({ type: "reset" });
515
+ setInput("");
516
+ setPalette(false);
517
+ setFocus("composer");
518
+ };
519
+ const send = async (value) => {
520
+ const text = value.trim();
521
+ if (!text || !selected || chat.running) return;
522
+ try {
523
+ const resolved = await props.engine.resolveChatInput({
524
+ profile: props.session.profile,
525
+ workflowId: selected.workflowId,
526
+ workflowName: selected.workflowName,
527
+ interactionId: chat.interactionId,
528
+ input: text
529
+ });
530
+ setInput("");
531
+ dispatch({ type: "turn_started", interactionId: resolved.interactionId, input: text });
532
+ const active = new AbortController();
533
+ controller.current = active;
534
+ for await (const event of props.engine.chat(resolved, active.signal)) {
535
+ dispatch({ type: "event_received", event });
536
+ }
537
+ if (active.signal.aborted) dispatch({ type: "turn_cancelled" });
538
+ else dispatch({ type: "turn_completed" });
539
+ } catch (error) {
540
+ if (controller.current?.signal.aborted) dispatch({ type: "turn_cancelled" });
541
+ else dispatch({ type: "turn_failed", message: publicError(error).message });
542
+ } finally {
543
+ controller.current = null;
544
+ }
545
+ };
546
+ useInput4(
547
+ (keyInput, key) => {
548
+ if (key.ctrl && keyInput === "k") setPalette(true);
549
+ else if (key.ctrl && keyInput === "p") {
550
+ controller.current?.abort();
551
+ props.onProfiles();
552
+ } else if (key.ctrl && keyInput === "h") {
553
+ if (chat.running) cancelTurn();
554
+ setHistory(true);
555
+ } else if (key.ctrl && keyInput === "n") newConversation();
556
+ else if (key.escape && chat.running) cancelTurn();
557
+ else if (key.escape) setFocus("agents");
558
+ else if (key.tab) setFocus((current) => current === "agents" ? "composer" : "agents");
559
+ else if (focus === "agents" && key.upArrow) setCursor((current) => Math.max(0, current - 1));
560
+ else if (focus === "agents" && key.downArrow && props.session.agents.length > 0) {
561
+ setCursor((current) => Math.min(props.session.agents.length - 1, current + 1));
562
+ } else if (focus === "agents" && key.return) selectAgent();
563
+ },
564
+ { isActive: !palette && !history }
565
+ );
566
+ const paletteActions = [
567
+ { id: "new", label: "\uC0C8 \uB300\uD654", run: newConversation },
568
+ {
569
+ id: "history",
570
+ label: "\uB300\uD654 \uAE30\uB85D",
571
+ run: () => {
572
+ if (chat.running) cancelTurn();
573
+ setPalette(false);
574
+ setHistory(true);
575
+ }
576
+ },
577
+ {
578
+ id: "profile",
579
+ label: "\uD504\uB85C\uD544 \uC804\uD658",
580
+ run: () => {
581
+ controller.current?.abort();
582
+ setPalette(false);
583
+ props.onProfiles();
584
+ }
585
+ },
586
+ {
587
+ id: "logout",
588
+ label: "\uB85C\uADF8\uC544\uC6C3",
589
+ run: () => {
590
+ controller.current?.abort();
591
+ props.onLogout();
592
+ }
593
+ },
594
+ { id: "quit", label: "\uC885\uB8CC", run: exit }
595
+ ];
596
+ let body;
597
+ if (palette) {
598
+ body = /* @__PURE__ */ jsx5(CommandPalette, { actions: paletteActions, onCancel: () => setPalette(false) });
599
+ } else if (history) {
600
+ body = /* @__PURE__ */ jsx5(
601
+ HistoryScreen,
602
+ {
603
+ engine: props.engine,
604
+ profile: props.session.profile,
605
+ onOpen: openHistory,
606
+ onCancel: () => setHistory(false)
607
+ }
608
+ );
609
+ } else {
610
+ const sidebar = /* @__PURE__ */ jsx5(
611
+ AgentSidebar,
612
+ {
613
+ agents: props.session.agents,
614
+ cursor,
615
+ selected: selected?.workflowId,
616
+ focused: focus === "agents",
617
+ height: bodyHeight
618
+ }
619
+ );
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(
623
+ Composer,
624
+ {
625
+ value: input,
626
+ onChange: setInput,
627
+ onSubmit: (value) => void send(value),
628
+ focused: focus === "composer",
629
+ disabled: chat.running || !selected,
630
+ cursorOrigin: { x: size.wide ? 34 : 4, y: bodyHeight - 1 }
631
+ }
632
+ )
633
+ ] });
634
+ body = size.wide ? /* @__PURE__ */ jsxs5(Box4, { height: bodyHeight, children: [
635
+ sidebar,
636
+ conversation
637
+ ] }) : focus === "agents" ? /* @__PURE__ */ jsx5(Box4, { height: bodyHeight, children: sidebar }) : /* @__PURE__ */ jsx5(Box4, { height: bodyHeight, children: conversation });
638
+ }
639
+ return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", children: [
640
+ /* @__PURE__ */ jsx5(
641
+ Header,
642
+ {
643
+ profile: props.session.profile,
644
+ username: props.session.username,
645
+ connected: true
646
+ }
647
+ ),
648
+ 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" })
650
+ ] });
651
+ }
652
+
653
+ // 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";
657
+ function LoginScreen(props) {
658
+ const [email, setEmail] = useState6("");
659
+ const [password, setPassword] = useState6("");
660
+ const [focus, setFocus] = useState6("email");
661
+ useInput5(
662
+ (input, key) => {
663
+ if (key.tab) setFocus((current) => current === "email" ? "password" : "email");
664
+ if (key.ctrl && input === "p") props.onProfiles();
665
+ },
666
+ { isActive: !props.busy }
667
+ );
668
+ const submit = () => {
669
+ if (focus === "email") {
670
+ setFocus("password");
671
+ return;
672
+ }
673
+ if (email.trim() && password) {
674
+ const secret = password;
675
+ setPassword("");
676
+ props.onSubmit(email.trim(), secret);
677
+ }
678
+ };
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(
686
+ FormField,
687
+ {
688
+ label: "Email",
689
+ value: email,
690
+ onChange: setEmail,
691
+ onSubmit: () => setFocus("password"),
692
+ focus: !props.busy && focus === "email",
693
+ cursorOrigin: { x: 16, y: 6 },
694
+ placeholder: "me@corp.com"
695
+ }
696
+ ),
697
+ /* @__PURE__ */ jsx6(
698
+ FormField,
699
+ {
700
+ label: "Password",
701
+ value: password,
702
+ onChange: setPassword,
703
+ onSubmit: submit,
704
+ focus: !props.busy && focus === "password",
705
+ cursorOrigin: { x: 16, y: 7 },
706
+ secret: true
707
+ }
708
+ )
709
+ ] }),
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
712
+ ] }),
713
+ /* @__PURE__ */ jsx6(Footer, { text: "Tab \uC774\uB3D9 \xB7 Enter \uB85C\uADF8\uC778 \xB7 Ctrl+P \uD504\uB85C\uD544 \xB7 Ctrl+Q \uC885\uB8CC" })
714
+ ] });
715
+ }
716
+
717
+ // 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";
721
+ 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(
729
+ (input, key) => {
730
+ if (key.escape) {
731
+ if (creating) setCreating(false);
732
+ else props.onCancel();
733
+ return;
734
+ }
735
+ if (creating) {
736
+ if (key.tab) setFocus((current) => current === "name" ? "url" : "name");
737
+ return;
738
+ }
739
+ if (key.upArrow) setCursor((current) => Math.max(0, current - 1));
740
+ if (key.downArrow && props.profiles.length > 0) {
741
+ setCursor((current) => Math.min(props.profiles.length - 1, current + 1));
742
+ }
743
+ if (key.return && props.profiles[cursor]) props.onSelect(props.profiles[cursor].name);
744
+ if (input === "n") {
745
+ setCreating(true);
746
+ setFocus("name");
747
+ }
748
+ },
749
+ { isActive: !props.busy }
750
+ );
751
+ const create = () => {
752
+ if (focus === "name") {
753
+ setFocus("url");
754
+ return;
755
+ }
756
+ if (name.trim() && serverUrl.trim()) props.onCreate(name.trim(), serverUrl.trim());
757
+ };
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(
764
+ FormField,
765
+ {
766
+ label: "Name",
767
+ value: name,
768
+ onChange: setName,
769
+ onSubmit: () => setFocus("url"),
770
+ focus: !props.busy && focus === "name",
771
+ cursorOrigin: { x: 16, y: 5 },
772
+ placeholder: "corp"
773
+ }
774
+ ),
775
+ /* @__PURE__ */ jsx7(
776
+ FormField,
777
+ {
778
+ label: "Server URL",
779
+ value: serverUrl,
780
+ onChange: setServerUrl,
781
+ onSubmit: create,
782
+ focus: !props.busy && focus === "url",
783
+ cursorOrigin: { x: 16, y: 6 },
784
+ placeholder: "https://xgen.example.com"
785
+ }
786
+ )
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: [
789
+ index === cursor ? "\u203A" : " ",
790
+ " ",
791
+ profile.current ? "\u25CF" : "\u25CB",
792
+ " ",
793
+ profile.name,
794
+ " \xB7",
795
+ " ",
796
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: profile.serverUrl })
797
+ ] }, 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
799
+ ] }),
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
802
+ ] }),
803
+ /* @__PURE__ */ jsx7(
804
+ Footer,
805
+ {
806
+ 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"
807
+ }
808
+ )
809
+ ] });
810
+ }
811
+
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(
824
+ FormField,
825
+ {
826
+ label: "Server URL",
827
+ value: serverUrl,
828
+ onChange: setServerUrl,
829
+ onSubmit: (value) => value.trim() && props.onSubmit(value.trim()),
830
+ focus: !props.busy,
831
+ cursorOrigin: { x: 16, y: 6 },
832
+ placeholder: "https://xgen.example.com"
833
+ }
834
+ ) }),
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
837
+ ] }),
838
+ /* @__PURE__ */ jsx8(Footer, { text: "Enter \uACC4\uC18D \xB7 Ctrl+Q \uC885\uB8CC" })
839
+ ] });
840
+ }
841
+
842
+ // src/tui/app.tsx
843
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
844
+ function App({ engine }) {
845
+ 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) => {
853
+ if (key.ctrl && input === "q") exit();
854
+ });
855
+ const bootstrap = useCallback(
856
+ async (preferredProfile) => {
857
+ setRoute("boot");
858
+ setBusy(true);
859
+ setError(void 0);
860
+ try {
861
+ let available = await engine.listProfiles();
862
+ setProfiles(available);
863
+ if (available.length === 0) {
864
+ setSession(void 0);
865
+ setRoute("setup");
866
+ return;
867
+ }
868
+ if (preferredProfile) {
869
+ await engine.useProfile(preferredProfile);
870
+ available = await engine.listProfiles();
871
+ setProfiles(available);
872
+ }
873
+ const current = available.find((profile) => profile.current) ?? available.find((profile) => profile.name === preferredProfile) ?? available[0];
874
+ if (!current) throw new DexError("config_invalid", "\uC0AC\uC6A9\uD560 \uD504\uB85C\uD544\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
875
+ const status = await engine.authStatus(current.name);
876
+ if (!status.authenticated) {
877
+ if (status.reason === "network") {
878
+ throw new DexError("network_error", `XGEN \uC11C\uBC84\uC5D0 \uC5F0\uACB0\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${current.serverUrl}`);
879
+ }
880
+ setSession(void 0);
881
+ setLoginTarget({ profile: current.name, serverUrl: current.serverUrl });
882
+ setRoute("login");
883
+ return;
884
+ }
885
+ const agents = await engine.listAgents({ pageSize: 100, includeHarness: true }, current.name);
886
+ setSession({
887
+ profile: current.name,
888
+ serverUrl: current.serverUrl,
889
+ username: status.user?.username ?? "unknown",
890
+ agents: agents.items
891
+ });
892
+ setRoute("dashboard");
893
+ } catch (reason) {
894
+ setError(publicError(reason).message);
895
+ setRoute("fatal");
896
+ } finally {
897
+ setBusy(false);
898
+ }
899
+ },
900
+ [engine]
901
+ );
902
+ useEffect6(() => {
903
+ void bootstrap();
904
+ }, [bootstrap]);
905
+ const configure = async (serverUrl) => {
906
+ setBusy(true);
907
+ setError(void 0);
908
+ try {
909
+ const profile = await engine.setProfile("default", serverUrl);
910
+ await engine.useProfile(profile.name);
911
+ setLoginTarget({ profile: profile.name, serverUrl: profile.serverUrl });
912
+ setProfiles(await engine.listProfiles());
913
+ setRoute("login");
914
+ } catch (reason) {
915
+ setError(publicError(reason).message);
916
+ } finally {
917
+ setBusy(false);
918
+ }
919
+ };
920
+ const login = async (email, password) => {
921
+ if (!loginTarget) return;
922
+ setBusy(true);
923
+ setError(void 0);
924
+ try {
925
+ await engine.login(email, password, loginTarget.profile);
926
+ await bootstrap(loginTarget.profile);
927
+ } catch (reason) {
928
+ setError(publicError(reason).message);
929
+ setRoute("login");
930
+ } finally {
931
+ setBusy(false);
932
+ }
933
+ };
934
+ const openProfiles = async () => {
935
+ setBusy(true);
936
+ setError(void 0);
937
+ try {
938
+ setProfiles(await engine.listProfiles());
939
+ setRoute("profiles");
940
+ } catch (reason) {
941
+ setError(publicError(reason).message);
942
+ } finally {
943
+ setBusy(false);
944
+ }
945
+ };
946
+ const createProfile = async (name, serverUrl) => {
947
+ setBusy(true);
948
+ setError(void 0);
949
+ try {
950
+ await engine.setProfile(name, serverUrl);
951
+ await bootstrap(name);
952
+ } catch (reason) {
953
+ setError(publicError(reason).message);
954
+ setRoute("profiles");
955
+ } finally {
956
+ setBusy(false);
957
+ }
958
+ };
959
+ const logout = async () => {
960
+ if (!session) return;
961
+ setBusy(true);
962
+ try {
963
+ await engine.logout(session.profile);
964
+ setLoginTarget({ profile: session.profile, serverUrl: session.serverUrl });
965
+ setSession(void 0);
966
+ setRoute("login");
967
+ } catch (reason) {
968
+ setError(publicError(reason).message);
969
+ setRoute("fatal");
970
+ } finally {
971
+ setBusy(false);
972
+ }
973
+ };
974
+ 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" })
979
+ ] });
980
+ }
981
+ if (route === "setup") {
982
+ return /* @__PURE__ */ jsx9(SetupScreen, { busy, error, onSubmit: (url) => void configure(url) });
983
+ }
984
+ if (route === "login" && loginTarget) {
985
+ return /* @__PURE__ */ jsx9(
986
+ LoginScreen,
987
+ {
988
+ profile: loginTarget.profile,
989
+ serverUrl: loginTarget.serverUrl,
990
+ busy,
991
+ error,
992
+ onSubmit: (email, password) => void login(email, password),
993
+ onProfiles: () => void openProfiles()
994
+ }
995
+ );
996
+ }
997
+ if (route === "profiles") {
998
+ return /* @__PURE__ */ jsx9(
999
+ ProfileScreen,
1000
+ {
1001
+ profiles,
1002
+ busy,
1003
+ error,
1004
+ onSelect: (name) => void bootstrap(name),
1005
+ onCreate: (name, url) => void createProfile(name, url),
1006
+ onCancel: () => setRoute(session ? "dashboard" : loginTarget ? "login" : "setup")
1007
+ }
1008
+ );
1009
+ }
1010
+ if (route === "dashboard" && session) {
1011
+ return /* @__PURE__ */ jsx9(
1012
+ Dashboard,
1013
+ {
1014
+ engine,
1015
+ session,
1016
+ onProfiles: () => void openProfiles(),
1017
+ onLogout: () => void logout()
1018
+ }
1019
+ );
1020
+ }
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() })
1027
+ ] });
1028
+ }
1029
+ function RetryInput({ onRetry }) {
1030
+ useInput7((input) => {
1031
+ if (input.toLowerCase() === "r") onRetry();
1032
+ });
1033
+ return null;
1034
+ }
1035
+
1036
+ // src/tui/index.tsx
1037
+ import { jsx as jsx10 } from "react/jsx-runtime";
1038
+ async function runTui(engine) {
1039
+ const instance = render(/* @__PURE__ */ jsx10(App, { engine }), {
1040
+ exitOnCtrlC: true,
1041
+ patchConsole: true
1042
+ });
1043
+ await instance.waitUntilExit();
1044
+ }
1045
+ export {
1046
+ runTui
1047
+ };
1048
+ //# sourceMappingURL=tui-DMHUMWPG.js.map