code-ollama 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/tui-DSR1MJGd.js +438 -0
- package/dist/cli.js +420 -42
- package/package.json +2 -2
- package/dist/tui-Bu6wAbeu.js +0 -559
- package/dist/utils-DBXrYZEs.js +0 -283
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import { a as streamChat, c as createSystemMessage, i as listModels, l as ROLE, n as TOOLS_REQUIRING_APPROVAL, o as loadConfig, r as executeTool, s as saveConfig, t as TOOLS, u as VERSION } from "../cli.js";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { Box, Text, render, useInput } from "ink";
|
|
4
|
+
import { useCallback, useEffect, useState } from "react";
|
|
5
|
+
import { Select, Spinner, TextInput } from "@inkjs/ui";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
//#region src/constants/commands.ts
|
|
8
|
+
var COMMANDS = [{
|
|
9
|
+
name: "/model",
|
|
10
|
+
description: "switch the model"
|
|
11
|
+
}];
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/constants/ui.ts
|
|
14
|
+
var HEADER_PREFIX = "🦙";
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/components/Autocomplete.tsx
|
|
17
|
+
function getMatches(input) {
|
|
18
|
+
if (!input.startsWith("/")) return [];
|
|
19
|
+
return COMMANDS.filter((command) => command.name.startsWith(input));
|
|
20
|
+
}
|
|
21
|
+
function Autocomplete({ isDisabled = false, onSubmit }) {
|
|
22
|
+
const [value, setValue] = useState("");
|
|
23
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
24
|
+
const [inputKey, setInputKey] = useState(0);
|
|
25
|
+
const matches = getMatches(value);
|
|
26
|
+
const isCommandMode = value.startsWith("/");
|
|
27
|
+
useInput((_char, key) => {
|
|
28
|
+
// v8 ignore next
|
|
29
|
+
if (!isCommandMode) return;
|
|
30
|
+
if (key.upArrow) {
|
|
31
|
+
setSelectedIndex((i) => Math.max(0, i - 1));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (key.downArrow) {
|
|
35
|
+
setSelectedIndex((i) => Math.min(matches.length - 1, i + 1));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (key.tab && matches.length > 0) {
|
|
39
|
+
setValue((matches[selectedIndex] ?? matches[0]).name);
|
|
40
|
+
setSelectedIndex(0);
|
|
41
|
+
setInputKey((key) => key + 1);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}, { isActive: !isDisabled && isCommandMode });
|
|
45
|
+
const handleSubmit = useCallback((input) => {
|
|
46
|
+
const trimmed = (isCommandMode && matches.length > 0 && matches[selectedIndex] ? matches[selectedIndex].name : input).trim();
|
|
47
|
+
if (trimmed) {
|
|
48
|
+
onSubmit(trimmed);
|
|
49
|
+
setValue("");
|
|
50
|
+
setSelectedIndex(0);
|
|
51
|
+
setInputKey((key) => key + 1);
|
|
52
|
+
}
|
|
53
|
+
}, [
|
|
54
|
+
isCommandMode,
|
|
55
|
+
matches,
|
|
56
|
+
onSubmit,
|
|
57
|
+
selectedIndex
|
|
58
|
+
]);
|
|
59
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
60
|
+
flexDirection: "column",
|
|
61
|
+
children: [/* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, { children: "> " }), /* @__PURE__ */ jsx(TextInput, {
|
|
62
|
+
isDisabled,
|
|
63
|
+
defaultValue: value,
|
|
64
|
+
onChange: setValue,
|
|
65
|
+
onSubmit: handleSubmit
|
|
66
|
+
}, inputKey)] }), isCommandMode && matches.length > 0 && /* @__PURE__ */ jsx(Box, {
|
|
67
|
+
flexDirection: "column",
|
|
68
|
+
marginLeft: 2,
|
|
69
|
+
children: matches.map((command, index) => {
|
|
70
|
+
const isHighlighted = index === selectedIndex;
|
|
71
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
72
|
+
gap: 3,
|
|
73
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
74
|
+
color: isHighlighted ? "cyan" : void 0,
|
|
75
|
+
bold: isHighlighted,
|
|
76
|
+
children: command.name
|
|
77
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
78
|
+
dimColor: true,
|
|
79
|
+
children: command.description
|
|
80
|
+
})]
|
|
81
|
+
}, command.name);
|
|
82
|
+
})
|
|
83
|
+
})]
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/components/Messages.tsx
|
|
88
|
+
function getMessageColor(role) {
|
|
89
|
+
switch (role) {
|
|
90
|
+
case ROLE.USER: return "black";
|
|
91
|
+
case ROLE.ASSISTANT: return "blue";
|
|
92
|
+
case ROLE.SYSTEM: return "gray";
|
|
93
|
+
default: return;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function Messages({ messages, isLoading }) {
|
|
97
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
98
|
+
flexDirection: "column",
|
|
99
|
+
children: [messages.map((message, index) => /* @__PURE__ */ jsx(Box, {
|
|
100
|
+
marginBottom: 1,
|
|
101
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
102
|
+
color: getMessageColor(message.role),
|
|
103
|
+
dimColor: message.role === ROLE.SYSTEM,
|
|
104
|
+
children: [message.role === ROLE.USER ? "> " : "", message.content]
|
|
105
|
+
})
|
|
106
|
+
}, index)), isLoading && messages[messages.length - 1]?.content === "" && /* @__PURE__ */ jsx(Box, {
|
|
107
|
+
marginTop: -1,
|
|
108
|
+
marginBottom: 1,
|
|
109
|
+
children: /* @__PURE__ */ jsx(Spinner, { label: "Thinking..." })
|
|
110
|
+
})]
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/components/ToolApproval.tsx
|
|
115
|
+
function ToolApproval({ toolCall, onApprove, onReject }) {
|
|
116
|
+
const [selected, setSelected] = useState("yes");
|
|
117
|
+
useInput((_, key) => {
|
|
118
|
+
if (key.return) if (selected === "yes") onApprove();
|
|
119
|
+
else onReject();
|
|
120
|
+
else if (key.leftArrow || key.rightArrow) setSelected((prev) => prev === "yes" ? "no" : "yes");
|
|
121
|
+
// v8 ignore stop
|
|
122
|
+
});
|
|
123
|
+
const args = JSON.stringify(toolCall.function.arguments, null, 2);
|
|
124
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
125
|
+
flexDirection: "column",
|
|
126
|
+
marginY: 1,
|
|
127
|
+
children: [
|
|
128
|
+
/* @__PURE__ */ jsx(Text, {
|
|
129
|
+
color: "yellow",
|
|
130
|
+
bold: true,
|
|
131
|
+
children: "⚠️ Tool requires approval:"
|
|
132
|
+
}),
|
|
133
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
134
|
+
marginX: 2,
|
|
135
|
+
flexDirection: "column",
|
|
136
|
+
children: [/* @__PURE__ */ jsxs(Text, { children: [
|
|
137
|
+
/* @__PURE__ */ jsx(Text, {
|
|
138
|
+
bold: true,
|
|
139
|
+
children: "Tool:"
|
|
140
|
+
}),
|
|
141
|
+
" ",
|
|
142
|
+
toolCall.function.name
|
|
143
|
+
] }), /* @__PURE__ */ jsxs(Text, { children: [
|
|
144
|
+
/* @__PURE__ */ jsx(Text, {
|
|
145
|
+
bold: true,
|
|
146
|
+
children: "Arguments:"
|
|
147
|
+
}),
|
|
148
|
+
" ",
|
|
149
|
+
args
|
|
150
|
+
] })]
|
|
151
|
+
}),
|
|
152
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
153
|
+
marginTop: 1,
|
|
154
|
+
gap: 2,
|
|
155
|
+
children: [/* @__PURE__ */ jsx(Text, { children: /* @__PURE__ */ jsxs(Text, {
|
|
156
|
+
color: selected === "yes" ? "green" : void 0,
|
|
157
|
+
children: [selected === "yes" ? "▶ " : " ", "✓ Yes (Enter)"]
|
|
158
|
+
}) }), /* @__PURE__ */ jsx(Text, { children: /* @__PURE__ */ jsxs(Text, {
|
|
159
|
+
color: selected === "no" ? "red" : void 0,
|
|
160
|
+
children: [selected === "no" ? "▶ " : " ", "✗ No (Esc)"]
|
|
161
|
+
}) })]
|
|
162
|
+
})
|
|
163
|
+
]
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
//#endregion
|
|
167
|
+
//#region src/components/Chat.tsx
|
|
168
|
+
function Chat({ model, onCommand, autoExecute }) {
|
|
169
|
+
const [messages, setMessages] = useState([createSystemMessage()]);
|
|
170
|
+
const [submitKey, setSubmitKey] = useState(0);
|
|
171
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
172
|
+
const [pendingToolCall, setPendingToolCall] = useState(null);
|
|
173
|
+
const processStream = useCallback(async (currentMessages) => {
|
|
174
|
+
const assistantMessage = {
|
|
175
|
+
role: ROLE.ASSISTANT,
|
|
176
|
+
content: ""
|
|
177
|
+
};
|
|
178
|
+
setMessages((previousMessages) => [...previousMessages, assistantMessage]);
|
|
179
|
+
try {
|
|
180
|
+
for await (const chunk of streamChat(currentMessages, model, TOOLS)) if (chunk.type === "content") {
|
|
181
|
+
assistantMessage.content += chunk.content;
|
|
182
|
+
setMessages((previousMessages) => {
|
|
183
|
+
const newMessages = [...previousMessages];
|
|
184
|
+
newMessages[newMessages.length - 1] = { ...assistantMessage };
|
|
185
|
+
return newMessages;
|
|
186
|
+
});
|
|
187
|
+
} else if (chunk.type === "tool_calls") for (const toolCall of chunk.tool_calls) {
|
|
188
|
+
const requiresApproval = TOOLS_REQUIRING_APPROVAL.has(toolCall.function.name);
|
|
189
|
+
if (!autoExecute && requiresApproval) {
|
|
190
|
+
setPendingToolCall(toolCall);
|
|
191
|
+
setIsLoading(false);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const result = await executeTool(toolCall.function.name, toolCall.function.arguments);
|
|
195
|
+
const toolResultMessage = {
|
|
196
|
+
role: ROLE.SYSTEM,
|
|
197
|
+
content: `Tool ${toolCall.function.name} result:\n${result.content}${result.error ? `\nError: ${result.error}` : ""}`
|
|
198
|
+
};
|
|
199
|
+
const newMessages = [
|
|
200
|
+
...currentMessages,
|
|
201
|
+
assistantMessage,
|
|
202
|
+
toolResultMessage
|
|
203
|
+
];
|
|
204
|
+
setMessages((previousMessages) => [...previousMessages, toolResultMessage]);
|
|
205
|
+
await processStream(newMessages);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
} catch (error) {
|
|
209
|
+
assistantMessage.content = `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
210
|
+
setMessages((previousMessages) => {
|
|
211
|
+
const newMessages = [...previousMessages];
|
|
212
|
+
newMessages[newMessages.length - 1] = { ...assistantMessage };
|
|
213
|
+
return newMessages;
|
|
214
|
+
});
|
|
215
|
+
} finally {
|
|
216
|
+
setIsLoading(false);
|
|
217
|
+
}
|
|
218
|
+
}, [model, autoExecute]);
|
|
219
|
+
const handleToolApproval = useCallback(async (approved) => {
|
|
220
|
+
// v8 ignore next
|
|
221
|
+
if (!pendingToolCall) return;
|
|
222
|
+
const toolCall = pendingToolCall;
|
|
223
|
+
setPendingToolCall(null);
|
|
224
|
+
setIsLoading(true);
|
|
225
|
+
if (approved) {
|
|
226
|
+
const result = await executeTool(toolCall.function.name, toolCall.function.arguments);
|
|
227
|
+
const toolResultMessage = {
|
|
228
|
+
role: ROLE.SYSTEM,
|
|
229
|
+
content: `Tool ${toolCall.function.name} result:\n${result.content}${result.error ? `\nError: ${result.error}` : ""}`
|
|
230
|
+
};
|
|
231
|
+
const newMessages = [...messages, toolResultMessage];
|
|
232
|
+
setMessages((previousMessages) => [...previousMessages, toolResultMessage]);
|
|
233
|
+
await processStream(newMessages);
|
|
234
|
+
} else {
|
|
235
|
+
const rejectionMessage = {
|
|
236
|
+
role: ROLE.SYSTEM,
|
|
237
|
+
content: `User declined to execute tool ${toolCall.function.name}`
|
|
238
|
+
};
|
|
239
|
+
const newMessages = [...messages, rejectionMessage];
|
|
240
|
+
setMessages((previousMessages) => [...previousMessages, rejectionMessage]);
|
|
241
|
+
await processStream(newMessages);
|
|
242
|
+
}
|
|
243
|
+
}, [
|
|
244
|
+
pendingToolCall,
|
|
245
|
+
messages,
|
|
246
|
+
processStream
|
|
247
|
+
]);
|
|
248
|
+
const handleSubmit = useCallback(async (value) => {
|
|
249
|
+
const userContent = value.trim();
|
|
250
|
+
if (!userContent) return;
|
|
251
|
+
setSubmitKey((key) => key + 1);
|
|
252
|
+
if (userContent.startsWith("/")) {
|
|
253
|
+
onCommand(userContent);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
setIsLoading(true);
|
|
257
|
+
const userMessage = {
|
|
258
|
+
role: ROLE.USER,
|
|
259
|
+
content: userContent
|
|
260
|
+
};
|
|
261
|
+
setMessages((previousMessages) => [...previousMessages, userMessage]);
|
|
262
|
+
await processStream([...messages, userMessage]);
|
|
263
|
+
}, [
|
|
264
|
+
messages,
|
|
265
|
+
onCommand,
|
|
266
|
+
processStream
|
|
267
|
+
]);
|
|
268
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
269
|
+
flexDirection: "column",
|
|
270
|
+
children: [
|
|
271
|
+
/* @__PURE__ */ jsx(Messages, {
|
|
272
|
+
messages: messages.slice(1),
|
|
273
|
+
isLoading
|
|
274
|
+
}),
|
|
275
|
+
pendingToolCall && /* @__PURE__ */ jsx(ToolApproval, {
|
|
276
|
+
toolCall: pendingToolCall,
|
|
277
|
+
onApprove: () => void handleToolApproval(true),
|
|
278
|
+
onReject: () => void handleToolApproval(false)
|
|
279
|
+
}),
|
|
280
|
+
!pendingToolCall && /* @__PURE__ */ jsx(Autocomplete, {
|
|
281
|
+
isDisabled: isLoading,
|
|
282
|
+
onSubmit: (val) => {
|
|
283
|
+
handleSubmit(val);
|
|
284
|
+
}
|
|
285
|
+
}, submitKey)
|
|
286
|
+
]
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
//#endregion
|
|
290
|
+
//#region src/components/Footer.tsx
|
|
291
|
+
function Footer({ autoExecute, onToggleMode }) {
|
|
292
|
+
useInput((_, key) => {
|
|
293
|
+
if (key.tab && key.shift) onToggleMode();
|
|
294
|
+
});
|
|
295
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
296
|
+
justifyContent: "space-between",
|
|
297
|
+
marginTop: 1,
|
|
298
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
299
|
+
dimColor: true,
|
|
300
|
+
children: [
|
|
301
|
+
"Mode: ",
|
|
302
|
+
autoExecute ? "Auto" : "Safe",
|
|
303
|
+
" (Shift+Tab to toggle)"
|
|
304
|
+
]
|
|
305
|
+
})
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
//#endregion
|
|
309
|
+
//#region src/components/Header.tsx
|
|
310
|
+
function abbreviatePath(dir) {
|
|
311
|
+
const home = homedir();
|
|
312
|
+
return dir.startsWith(home) ? `~${dir.slice(home.length)}` : dir;
|
|
313
|
+
}
|
|
314
|
+
function Header({ model }) {
|
|
315
|
+
const directory = abbreviatePath(process.cwd());
|
|
316
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
317
|
+
borderStyle: "round",
|
|
318
|
+
flexDirection: "column",
|
|
319
|
+
paddingX: 1,
|
|
320
|
+
children: [
|
|
321
|
+
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsxs(Text, {
|
|
322
|
+
bold: true,
|
|
323
|
+
children: [HEADER_PREFIX, "Code Ollama"]
|
|
324
|
+
}), /* @__PURE__ */ jsxs(Text, {
|
|
325
|
+
dimColor: true,
|
|
326
|
+
children: [
|
|
327
|
+
" (v",
|
|
328
|
+
VERSION,
|
|
329
|
+
")"
|
|
330
|
+
]
|
|
331
|
+
})] }),
|
|
332
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
333
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
334
|
+
/* @__PURE__ */ jsx(Text, {
|
|
335
|
+
dimColor: true,
|
|
336
|
+
children: "model:".padEnd(11)
|
|
337
|
+
}),
|
|
338
|
+
/* @__PURE__ */ jsxs(Text, { children: [model, " "] }),
|
|
339
|
+
/* @__PURE__ */ jsx(Text, {
|
|
340
|
+
color: "cyan",
|
|
341
|
+
children: "/model"
|
|
342
|
+
}),
|
|
343
|
+
/* @__PURE__ */ jsx(Text, {
|
|
344
|
+
dimColor: true,
|
|
345
|
+
children: " to switch"
|
|
346
|
+
})
|
|
347
|
+
] }),
|
|
348
|
+
/* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
|
|
349
|
+
dimColor: true,
|
|
350
|
+
children: "directory:".padEnd(11)
|
|
351
|
+
}), /* @__PURE__ */ jsx(Text, { children: directory })] })
|
|
352
|
+
]
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
//#endregion
|
|
356
|
+
//#region src/components/ModelPicker.tsx
|
|
357
|
+
function ModelPicker({ currentModel, onSelect, onCancel }) {
|
|
358
|
+
const [options, setOptions] = useState([]);
|
|
359
|
+
const [error, setError] = useState(null);
|
|
360
|
+
useEffect(() => {
|
|
361
|
+
async function load() {
|
|
362
|
+
try {
|
|
363
|
+
setOptions((await listModels()).map((name) => ({
|
|
364
|
+
label: name,
|
|
365
|
+
value: name
|
|
366
|
+
})));
|
|
367
|
+
} catch (err) {
|
|
368
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
load();
|
|
372
|
+
}, []);
|
|
373
|
+
useInput((_, key) => {
|
|
374
|
+
if (key.escape) onCancel();
|
|
375
|
+
});
|
|
376
|
+
if (error) return /* @__PURE__ */ jsxs(Text, {
|
|
377
|
+
color: "red",
|
|
378
|
+
children: ["Error loading models: ", error]
|
|
379
|
+
});
|
|
380
|
+
if (!options.length) return /* @__PURE__ */ jsx(Spinner, { label: "Loading models..." });
|
|
381
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
382
|
+
flexDirection: "column",
|
|
383
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
384
|
+
dimColor: true,
|
|
385
|
+
children: "Select a model (↑↓ + Enter to confirm, Esc to cancel)"
|
|
386
|
+
}), /* @__PURE__ */ jsx(Select, {
|
|
387
|
+
options,
|
|
388
|
+
defaultValue: currentModel,
|
|
389
|
+
onChange: onSelect
|
|
390
|
+
})]
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
//#endregion
|
|
394
|
+
//#region src/components/App.tsx
|
|
395
|
+
function App() {
|
|
396
|
+
const [model, setModel] = useState(() => loadConfig().model);
|
|
397
|
+
const [picking, setPicking] = useState(false);
|
|
398
|
+
const [autoExecute, setAutoExecute] = useState(false);
|
|
399
|
+
const handleCommand = useCallback((command) => {
|
|
400
|
+
if (command === "/model") setPicking(true);
|
|
401
|
+
}, []);
|
|
402
|
+
const handleSelect = useCallback((selected) => {
|
|
403
|
+
setModel(selected);
|
|
404
|
+
saveConfig({ model: selected });
|
|
405
|
+
setPicking(false);
|
|
406
|
+
}, []);
|
|
407
|
+
const handleCancel = useCallback(() => {
|
|
408
|
+
setPicking(false);
|
|
409
|
+
}, []);
|
|
410
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
411
|
+
flexDirection: "column",
|
|
412
|
+
children: [
|
|
413
|
+
/* @__PURE__ */ jsx(Header, { model }),
|
|
414
|
+
picking ? /* @__PURE__ */ jsx(ModelPicker, {
|
|
415
|
+
currentModel: model,
|
|
416
|
+
onSelect: handleSelect,
|
|
417
|
+
onCancel: handleCancel
|
|
418
|
+
}) : /* @__PURE__ */ jsx(Chat, {
|
|
419
|
+
model,
|
|
420
|
+
onCommand: handleCommand,
|
|
421
|
+
autoExecute
|
|
422
|
+
}),
|
|
423
|
+
/* @__PURE__ */ jsx(Footer, {
|
|
424
|
+
autoExecute,
|
|
425
|
+
onToggleMode: () => {
|
|
426
|
+
setAutoExecute((isAutoExecute) => !isAutoExecute);
|
|
427
|
+
}
|
|
428
|
+
})
|
|
429
|
+
]
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
//#endregion
|
|
433
|
+
//#region src/tui.tsx
|
|
434
|
+
function renderApp() {
|
|
435
|
+
render(/* @__PURE__ */ jsx(App, {}));
|
|
436
|
+
}
|
|
437
|
+
//#endregion
|
|
438
|
+
export { renderApp };
|