@dmrlzz/davecode 0.0.0-stage → 0.3.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.
Files changed (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +33 -2
  3. package/bin/davecode.js +2 -0
  4. package/dist/ChatApp-FO5SABXB.js +893 -0
  5. package/dist/ChatApp-FO5SABXB.js.map +1 -0
  6. package/dist/RunnerView-DLVR7C22.js +18 -0
  7. package/dist/RunnerView-DLVR7C22.js.map +1 -0
  8. package/dist/accounts-MTMGYKEP.js +724 -0
  9. package/dist/accounts-MTMGYKEP.js.map +1 -0
  10. package/dist/budget-RBHCEDPP.js +115 -0
  11. package/dist/budget-RBHCEDPP.js.map +1 -0
  12. package/dist/chat-ZDXX3N6X.js +95 -0
  13. package/dist/chat-ZDXX3N6X.js.map +1 -0
  14. package/dist/chunk-23KTE7IK.js +114 -0
  15. package/dist/chunk-23KTE7IK.js.map +1 -0
  16. package/dist/chunk-7XVXEO7U.js +23 -0
  17. package/dist/chunk-7XVXEO7U.js.map +1 -0
  18. package/dist/chunk-BOLFK3BH.js +20 -0
  19. package/dist/chunk-BOLFK3BH.js.map +1 -0
  20. package/dist/chunk-C7FDRA3U.js +9087 -0
  21. package/dist/chunk-C7FDRA3U.js.map +1 -0
  22. package/dist/chunk-DYQ56FBT.js +47 -0
  23. package/dist/chunk-DYQ56FBT.js.map +1 -0
  24. package/dist/chunk-FRFSJMN4.js +79 -0
  25. package/dist/chunk-FRFSJMN4.js.map +1 -0
  26. package/dist/chunk-IC3TDONV.js +12 -0
  27. package/dist/chunk-IC3TDONV.js.map +1 -0
  28. package/dist/chunk-IVZHKUCD.js +180 -0
  29. package/dist/chunk-IVZHKUCD.js.map +1 -0
  30. package/dist/chunk-MSLLH5XP.js +213 -0
  31. package/dist/chunk-MSLLH5XP.js.map +1 -0
  32. package/dist/chunk-OXRMGR72.js +20 -0
  33. package/dist/chunk-OXRMGR72.js.map +1 -0
  34. package/dist/chunk-OYEYNDOO.js +200 -0
  35. package/dist/chunk-OYEYNDOO.js.map +1 -0
  36. package/dist/chunk-PIIJ3RSI.js +1610 -0
  37. package/dist/chunk-PIIJ3RSI.js.map +1 -0
  38. package/dist/chunk-SF5ZEAP4.js +105 -0
  39. package/dist/chunk-SF5ZEAP4.js.map +1 -0
  40. package/dist/chunk-VVCXGQMY.js +107 -0
  41. package/dist/chunk-VVCXGQMY.js.map +1 -0
  42. package/dist/config-FRMY5OHG.js +158 -0
  43. package/dist/config-FRMY5OHG.js.map +1 -0
  44. package/dist/dashboard/assets/Accounts-BR3ZfVk4.js +1 -0
  45. package/dist/dashboard/assets/Badge-B-qzNRrZ.js +55 -0
  46. package/dist/dashboard/assets/Callout-B9g_wPDi.js +1 -0
  47. package/dist/dashboard/assets/Field-BfabPEcY.js +1 -0
  48. package/dist/dashboard/assets/Goals-BsV-6m94.js +1 -0
  49. package/dist/dashboard/assets/Library-CEA9P1m-.js +1 -0
  50. package/dist/dashboard/assets/Markdown-ClRsddnB.js +69 -0
  51. package/dist/dashboard/assets/Panel-B9tSgX6l.js +1 -0
  52. package/dist/dashboard/assets/RoutesPage-Dh-BJ3ir.js +4 -0
  53. package/dist/dashboard/assets/Runner-C_e9l5l2.js +1 -0
  54. package/dist/dashboard/assets/Settings-C6Yc1Gjg.js +1 -0
  55. package/dist/dashboard/assets/Switch-CUpOJjUT.js +1 -0
  56. package/dist/dashboard/assets/Tasks-CTku0rR3.js +2 -0
  57. package/dist/dashboard/assets/Traffic-CE6wZSMr.js +1 -0
  58. package/dist/dashboard/assets/Workspace-CxFxvkA6.js +1 -0
  59. package/dist/dashboard/assets/arrow-up-DL3CuFOO.js +1 -0
  60. package/dist/dashboard/assets/forms-D6UIYgC5.js +1 -0
  61. package/dist/dashboard/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  62. package/dist/dashboard/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  63. package/dist/dashboard/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  64. package/dist/dashboard/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  65. package/dist/dashboard/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  66. package/dist/dashboard/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  67. package/dist/dashboard/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  68. package/dist/dashboard/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  69. package/dist/dashboard/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  70. package/dist/dashboard/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  71. package/dist/dashboard/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  72. package/dist/dashboard/assets/index-1iH716Xy.css +2 -0
  73. package/dist/dashboard/assets/index-BMWr9EKo.js +104 -0
  74. package/dist/dashboard/assets/task-errors-CE4oEr8H.js +1 -0
  75. package/dist/dashboard/assets/trash-CWqeZ7eg.js +1 -0
  76. package/dist/dashboard/favicon.svg +7 -0
  77. package/dist/dashboard/github.svg +1 -0
  78. package/dist/dashboard/index.html +31 -0
  79. package/dist/dist-ZPXEVBEC.js +357 -0
  80. package/dist/dist-ZPXEVBEC.js.map +1 -0
  81. package/dist/doctor-7RUD4D3A.js +279 -0
  82. package/dist/doctor-7RUD4D3A.js.map +1 -0
  83. package/dist/goal-JFRNPBSD.js +302 -0
  84. package/dist/goal-JFRNPBSD.js.map +1 -0
  85. package/dist/index.js +381 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/init-NFYNO3A3.js +194 -0
  88. package/dist/init-NFYNO3A3.js.map +1 -0
  89. package/dist/prompts-L37J72YW.js +215 -0
  90. package/dist/prompts-L37J72YW.js.map +1 -0
  91. package/dist/run-ARUPXFFB.js +376 -0
  92. package/dist/run-ARUPXFFB.js.map +1 -0
  93. package/dist/runner-state-QFU3LJVJ.js +17 -0
  94. package/dist/runner-state-QFU3LJVJ.js.map +1 -0
  95. package/dist/start-CFPOVIAR.js +285 -0
  96. package/dist/start-CFPOVIAR.js.map +1 -0
  97. package/dist/status-E4EACVE2.js +25 -0
  98. package/dist/status-E4EACVE2.js.map +1 -0
  99. package/dist/tasks-ZIK6BVEX.js +184 -0
  100. package/dist/tasks-ZIK6BVEX.js.map +1 -0
  101. package/package.json +68 -4
@@ -0,0 +1,893 @@
1
+ import {
2
+ Spinner
3
+ } from "./chunk-BOLFK3BH.js";
4
+ import {
5
+ ThemeProvider,
6
+ useTheme
7
+ } from "./chunk-OXRMGR72.js";
8
+ import {
9
+ formatTaskTree,
10
+ summaryLine
11
+ } from "./chunk-23KTE7IK.js";
12
+ import {
13
+ emptyUsage,
14
+ renderAccounts
15
+ } from "./chunk-MSLLH5XP.js";
16
+ import {
17
+ bar,
18
+ formatDuration,
19
+ formatTokens,
20
+ levelStyle,
21
+ padEnd,
22
+ percent,
23
+ visibleWidth
24
+ } from "./chunk-SF5ZEAP4.js";
25
+ import "./chunk-DYQ56FBT.js";
26
+ import "./chunk-VVCXGQMY.js";
27
+ import "./chunk-PIIJ3RSI.js";
28
+ import "./chunk-7XVXEO7U.js";
29
+ import "./chunk-C7FDRA3U.js";
30
+
31
+ // src/tui/ChatApp.tsx
32
+ import { Box as Box4, Static, useApp, useInput, usePaste, useStdout, useWindowSize } from "ink";
33
+ import { useCallback, useEffect, useReducer, useRef, useState } from "react";
34
+
35
+ // src/tui/chat-state.ts
36
+ function initialChatState(model) {
37
+ return {
38
+ entries: [],
39
+ conversation: [],
40
+ model,
41
+ connection: "connecting",
42
+ lastFailovers: 0,
43
+ totalFailovers: 0,
44
+ tokens: { prompt: 0, completion: 0 },
45
+ epoch: 0,
46
+ history: [],
47
+ nextId: 1
48
+ };
49
+ }
50
+ function estimateTokens(text) {
51
+ return Math.ceil(text.length / 4);
52
+ }
53
+ function push(state, kind, text, meta) {
54
+ const entry = { id: state.nextId, kind, text };
55
+ if (meta) entry.meta = meta;
56
+ return { ...state, entries: [...state.entries, entry], nextId: state.nextId + 1 };
57
+ }
58
+ function chatReducer(state, action) {
59
+ switch (action.type) {
60
+ case "banner":
61
+ return push(state, "banner", action.text);
62
+ case "info":
63
+ return push(state, "info", action.text);
64
+ case "error":
65
+ return push(state, "error", action.text);
66
+ case "user": {
67
+ const next = push(state, "user", action.text);
68
+ const history = state.history[state.history.length - 1] === action.text ? state.history : [...state.history, action.text].slice(-200);
69
+ return {
70
+ ...next,
71
+ history,
72
+ conversation: [...state.conversation, { role: "user", content: action.text }]
73
+ };
74
+ }
75
+ case "stream-start":
76
+ return {
77
+ ...state,
78
+ streaming: { id: state.nextId, text: "", startedAt: action.now, model: state.model },
79
+ nextId: state.nextId + 1
80
+ };
81
+ case "stream-meta": {
82
+ if (!state.streaming) return state;
83
+ const streaming = { ...state.streaming };
84
+ if (action.accountId) streaming.accountId = action.accountId;
85
+ if (action.provider) streaming.provider = action.provider;
86
+ if (action.failovers !== void 0) streaming.failovers = action.failovers;
87
+ const lastAccount = action.accountId ? {
88
+ id: action.accountId,
89
+ ...action.accountLabel ? { label: action.accountLabel } : {},
90
+ ...action.provider ? { provider: action.provider } : {}
91
+ } : state.lastAccount;
92
+ return {
93
+ ...state,
94
+ streaming,
95
+ ...lastAccount ? { lastAccount } : {},
96
+ ...action.failovers !== void 0 ? {
97
+ lastFailovers: action.failovers,
98
+ totalFailovers: state.totalFailovers + action.failovers
99
+ } : {}
100
+ };
101
+ }
102
+ case "stream-delta":
103
+ if (!state.streaming) return state;
104
+ return {
105
+ ...state,
106
+ streaming: { ...state.streaming, text: state.streaming.text + action.text }
107
+ };
108
+ case "stream-end": {
109
+ const live = state.streaming;
110
+ if (!live) return state;
111
+ const prompt = action.usage?.prompt_tokens ?? estimateTokens(
112
+ state.conversation.map((m) => typeof m.content === "string" ? m.content : "").join("\n")
113
+ );
114
+ const completion = action.usage?.completion_tokens ?? estimateTokens(live.text);
115
+ const meta = {
116
+ model: live.model,
117
+ latencyMs: action.now - live.startedAt,
118
+ tokens: prompt + completion,
119
+ ...action.usage ? {} : { estimated: true },
120
+ ...live.accountId ? { accountId: live.accountId } : {},
121
+ ...state.lastAccount?.id === live.accountId && state.lastAccount?.label ? { accountLabel: state.lastAccount.label } : {},
122
+ ...live.provider ? { provider: live.provider } : {},
123
+ ...live.failovers ? { failovers: live.failovers } : {},
124
+ ...action.cancelled ? { cancelled: true } : {}
125
+ };
126
+ let next = { ...state };
127
+ delete next.streaming;
128
+ const answered = live.text.length > 0;
129
+ if (answered || action.cancelled) next = push(next, "assistant", live.text, meta);
130
+ if (action.error) next = push(next, "error", action.error);
131
+ if (answered && !action.cancelled && !action.error) {
132
+ next.conversation = [...next.conversation, { role: "assistant", content: live.text }];
133
+ } else if (next.conversation[next.conversation.length - 1]?.role === "user") {
134
+ next.conversation = next.conversation.slice(0, -1);
135
+ }
136
+ if (answered || action.usage) {
137
+ next.tokens = {
138
+ prompt: state.tokens.prompt + prompt,
139
+ completion: state.tokens.completion + completion
140
+ };
141
+ }
142
+ return next;
143
+ }
144
+ case "set-model":
145
+ return { ...state, model: action.model };
146
+ case "connection":
147
+ return state.connection === action.connection ? state : { ...state, connection: action.connection };
148
+ case "quota": {
149
+ if (state.lastAccount?.id !== action.accountId) return state;
150
+ const next = { ...state };
151
+ if (action.utilization === void 0) delete next.quota5h;
152
+ else next.quota5h = action.utilization;
153
+ return next;
154
+ }
155
+ case "clear": {
156
+ const next = {
157
+ ...state,
158
+ entries: [],
159
+ conversation: [],
160
+ epoch: state.epoch + 1
161
+ };
162
+ delete next.streaming;
163
+ return next;
164
+ }
165
+ default:
166
+ return state;
167
+ }
168
+ }
169
+
170
+ // src/tui/components/InputBox.tsx
171
+ import { Box, Text } from "ink";
172
+
173
+ // src/tui/editor.ts
174
+ var emptyEditor = { chars: [], cursor: 0 };
175
+ function fromText(text) {
176
+ const chars = [...text];
177
+ return { chars, cursor: chars.length };
178
+ }
179
+ function textOf(state) {
180
+ return state.chars.join("");
181
+ }
182
+ function normalizeInput(text) {
183
+ return text.replace(/\r\n?/g, "\n").replace(/\t/g, " ").replace(/[\u0000-\u0009\u000b-\u001f\u007f]/g, "");
184
+ }
185
+ function insert(state, text) {
186
+ const add = [...normalizeInput(text)];
187
+ if (add.length === 0) return state;
188
+ const chars = [...state.chars.slice(0, state.cursor), ...add, ...state.chars.slice(state.cursor)];
189
+ return { chars, cursor: state.cursor + add.length };
190
+ }
191
+ function backspace(state) {
192
+ if (state.cursor === 0) return state;
193
+ const chars = [...state.chars.slice(0, state.cursor - 1), ...state.chars.slice(state.cursor)];
194
+ return { chars, cursor: state.cursor - 1 };
195
+ }
196
+ function deleteForward(state) {
197
+ if (state.cursor >= state.chars.length) return state;
198
+ const chars = [...state.chars.slice(0, state.cursor), ...state.chars.slice(state.cursor + 1)];
199
+ return { chars, cursor: state.cursor };
200
+ }
201
+ function left(state) {
202
+ return { ...state, cursor: Math.max(0, state.cursor - 1) };
203
+ }
204
+ function right(state) {
205
+ return { ...state, cursor: Math.min(state.chars.length, state.cursor + 1) };
206
+ }
207
+ function position(state) {
208
+ let line = 0;
209
+ let column = 0;
210
+ for (let i = 0; i < state.cursor; i++) {
211
+ if (state.chars[i] === "\n") {
212
+ line++;
213
+ column = 0;
214
+ } else {
215
+ column++;
216
+ }
217
+ }
218
+ return { line, column };
219
+ }
220
+ function lines(state) {
221
+ const out = [[]];
222
+ for (const ch of state.chars) {
223
+ if (ch === "\n") out.push([]);
224
+ else out[out.length - 1].push(ch);
225
+ }
226
+ return out;
227
+ }
228
+ function offsetOf(all, line, column) {
229
+ let offset = 0;
230
+ for (let i = 0; i < line; i++) offset += all[i].length + 1;
231
+ return offset + Math.min(column, all[line].length);
232
+ }
233
+ function up(state) {
234
+ const { line, column } = position(state);
235
+ if (line === 0) return void 0;
236
+ return { ...state, cursor: offsetOf(lines(state), line - 1, column) };
237
+ }
238
+ function down(state) {
239
+ const all = lines(state);
240
+ const { line, column } = position(state);
241
+ if (line >= all.length - 1) return void 0;
242
+ return { ...state, cursor: offsetOf(all, line + 1, column) };
243
+ }
244
+ function lineStart(state) {
245
+ const { line } = position(state);
246
+ return { ...state, cursor: offsetOf(lines(state), line, 0) };
247
+ }
248
+ function lineEnd(state) {
249
+ const all = lines(state);
250
+ const { line } = position(state);
251
+ return { ...state, cursor: offsetOf(all, line, all[line].length) };
252
+ }
253
+ function killToLineStart(state) {
254
+ const start = lineStart(state).cursor;
255
+ if (start === state.cursor) return state;
256
+ return {
257
+ chars: [...state.chars.slice(0, start), ...state.chars.slice(state.cursor)],
258
+ cursor: start
259
+ };
260
+ }
261
+ function killToLineEnd(state) {
262
+ const end = lineEnd(state).cursor;
263
+ return {
264
+ chars: [...state.chars.slice(0, state.cursor), ...state.chars.slice(end)],
265
+ cursor: state.cursor
266
+ };
267
+ }
268
+ function deleteWordBack(state) {
269
+ let i = state.cursor;
270
+ while (i > 0 && /[ \t]/.test(state.chars[i - 1])) i--;
271
+ while (i > 0 && !/[\s]/.test(state.chars[i - 1])) i--;
272
+ if (i === state.cursor) return backspace(state);
273
+ return { chars: [...state.chars.slice(0, i), ...state.chars.slice(state.cursor)], cursor: i };
274
+ }
275
+ function continuesLine(state) {
276
+ return state.cursor > 0 && state.chars[state.cursor - 1] === "\\";
277
+ }
278
+ function newlineAfterBackslash(state) {
279
+ return insert(backspace(state), "\n");
280
+ }
281
+
282
+ // src/tui/components/InputBox.tsx
283
+ import { jsx, jsxs } from "react/jsx-runtime";
284
+ function cursorParts(line, column) {
285
+ if (column === void 0) return { before: line.join(""), at: "", after: "" };
286
+ return {
287
+ before: line.slice(0, column).join(""),
288
+ at: line[column] ?? " ",
289
+ after: line.slice(column + 1).join("")
290
+ };
291
+ }
292
+ function InputBox({
293
+ editor,
294
+ width,
295
+ placeholder,
296
+ disabled
297
+ }) {
298
+ const theme = useTheme();
299
+ const all = lines(editor);
300
+ const cursor = position(editor);
301
+ const empty = editor.chars.length === 0;
302
+ return /* @__PURE__ */ jsx(
303
+ Box,
304
+ {
305
+ borderStyle: theme.unicode ? "round" : "classic",
306
+ borderDimColor: theme.ink.dim,
307
+ flexDirection: "column",
308
+ paddingX: 1,
309
+ width,
310
+ children: all.map((line, i) => {
311
+ const parts = cursorParts(line, !disabled && i === cursor.line ? cursor.column : void 0);
312
+ return (
313
+ // biome-ignore lint/suspicious/noArrayIndexKey: lines have no identity beyond position
314
+ /* @__PURE__ */ jsxs(Box, { children: [
315
+ /* @__PURE__ */ jsx(Box, { width: 2, flexShrink: 0, children: /* @__PURE__ */ jsx(Text, { color: i === 0 ? theme.ink.accent : void 0, children: i === 0 ? theme.glyph.prompt : "" }) }),
316
+ empty && i === 0 ? /* @__PURE__ */ jsxs(Text, { children: [
317
+ disabled ? null : /* @__PURE__ */ jsx(Text, { inverse: true, children: " " }),
318
+ /* @__PURE__ */ jsx(Text, { dimColor: theme.ink.dim, children: placeholder })
319
+ ] }) : /* @__PURE__ */ jsxs(Text, { children: [
320
+ parts.before,
321
+ parts.at ? /* @__PURE__ */ jsx(Text, { inverse: true, children: parts.at }) : null,
322
+ parts.after
323
+ ] })
324
+ ] }, i)
325
+ );
326
+ })
327
+ }
328
+ );
329
+ }
330
+ function SlashHints({ commands, width }) {
331
+ const theme = useTheme();
332
+ if (commands.length === 0) return null;
333
+ const nameWidth = Math.max(...commands.map((c) => c.name.length + (c.args ? c.args.length + 1 : 0))) + 3;
334
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 2, width, children: commands.map((c, i) => /* @__PURE__ */ jsxs(Text, { wrap: "truncate-end", children: [
335
+ /* @__PURE__ */ jsx(Text, { color: i === 0 ? theme.ink.accent : void 0, children: `/${c.name}${c.args ? ` ${c.args}` : ""}`.padEnd(nameWidth) }),
336
+ /* @__PURE__ */ jsx(Text, { dimColor: theme.ink.dim, children: c.description })
337
+ ] }, c.name)) });
338
+ }
339
+
340
+ // src/tui/components/StatusLine.tsx
341
+ import { Box as Box2, Text as Text2 } from "ink";
342
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
343
+ function statusSegments(theme, state, info, width) {
344
+ const sep = theme.dim(" \xB7 ");
345
+ const left2 = [theme.accent(state.model)];
346
+ if (state.lastAccount) {
347
+ const name = state.lastAccount.label ?? state.lastAccount.id;
348
+ left2.push(
349
+ `${name}${state.lastAccount.provider ? theme.dim(` (${state.lastAccount.provider})`) : ""}`
350
+ );
351
+ }
352
+ const failovers = state.totalFailovers > 0 ? theme.warn(`${state.lastFailovers}/${state.totalFailovers} failovers`) : theme.dim("0 failovers");
353
+ left2.push(failovers);
354
+ const arrows = theme.unicode ? ["\u2191", "\u2193"] : ["in", "out"];
355
+ left2.push(
356
+ theme.dim(
357
+ `${formatTokens(state.tokens.prompt)}${arrows[0]} ${formatTokens(state.tokens.completion)}${arrows[1]}`
358
+ )
359
+ );
360
+ if (state.quota5h !== void 0) {
361
+ left2.push(
362
+ `${theme.dim("5h")} ${bar(theme, state.quota5h, 6)} ${levelStyle(theme, state.quota5h)(percent(state.quota5h))}`
363
+ );
364
+ }
365
+ const dot = theme.glyph.dot;
366
+ let right2;
367
+ if (info.notice) right2 = theme.warn(info.notice);
368
+ else if (state.connection === "online") {
369
+ right2 = `${theme.ok(dot)} ${theme.dim(info.inProcess ? `${info.where}` : "connected")}`;
370
+ } else if (state.connection === "connecting") right2 = theme.dim(`${dot} connecting`);
371
+ else right2 = theme.error(`${dot} offline`);
372
+ const priorities = [0, 1, 2, 4, 3];
373
+ let kept = left2.map((_, i) => i);
374
+ const render = () => kept.map((i) => left2[i]).join(sep);
375
+ while (kept.length > 1 && visibleWidth(render()) + visibleWidth(right2) + 2 > width) {
376
+ const drop = [...priorities].reverse().find((p) => kept.includes(p) && p !== 0);
377
+ if (drop === void 0) break;
378
+ kept = kept.filter((i) => i !== drop);
379
+ }
380
+ return { left: render(), right: right2 };
381
+ }
382
+ function StatusLine({
383
+ state,
384
+ info,
385
+ width
386
+ }) {
387
+ const theme = useTheme();
388
+ const { left: left2, right: right2 } = statusSegments(theme, state, info, width - 2);
389
+ return /* @__PURE__ */ jsxs2(Box2, { width, justifyContent: "space-between", paddingX: 1, children: [
390
+ /* @__PURE__ */ jsx2(Text2, { wrap: "truncate-end", children: left2 }),
391
+ /* @__PURE__ */ jsx2(Text2, { wrap: "truncate-start", children: right2 })
392
+ ] });
393
+ }
394
+
395
+ // src/tui/components/Transcript.tsx
396
+ import { Box as Box3, Text as Text3 } from "ink";
397
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
398
+ function metaLine(meta) {
399
+ const parts = [];
400
+ if (meta.accountLabel ?? meta.accountId) {
401
+ parts.push([meta.provider, meta.accountLabel ?? meta.accountId].filter(Boolean).join(" \xB7 "));
402
+ }
403
+ if (meta.model) parts.push(meta.model);
404
+ if (meta.tokens !== void 0)
405
+ parts.push(`${meta.estimated ? "~" : ""}${formatTokens(meta.tokens)} tok`);
406
+ if (meta.latencyMs !== void 0) parts.push(`${(meta.latencyMs / 1e3).toFixed(1)}s`);
407
+ if (meta.failovers) parts.push(`${meta.failovers} failover${meta.failovers === 1 ? "" : "s"}`);
408
+ if (meta.cancelled) parts.push("cancelled");
409
+ return parts.join(" \xB7 ");
410
+ }
411
+ function Marker({ color, glyph }) {
412
+ return /* @__PURE__ */ jsx3(Box3, { width: 2, flexShrink: 0, children: /* @__PURE__ */ jsx3(Text3, { color, children: glyph }) });
413
+ }
414
+ function EntryView({ entry }) {
415
+ const theme = useTheme();
416
+ switch (entry.kind) {
417
+ case "banner":
418
+ return /* @__PURE__ */ jsx3(Box3, { marginBottom: 1, children: /* @__PURE__ */ jsx3(Text3, { children: entry.text }) });
419
+ case "user":
420
+ return /* @__PURE__ */ jsxs3(Box3, { marginBottom: 1, children: [
421
+ /* @__PURE__ */ jsx3(Marker, { color: theme.ink.accent, glyph: theme.glyph.prompt }),
422
+ /* @__PURE__ */ jsx3(Text3, { dimColor: theme.ink.dim, children: entry.text })
423
+ ] });
424
+ case "assistant":
425
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginBottom: 1, children: [
426
+ /* @__PURE__ */ jsxs3(Box3, { children: [
427
+ /* @__PURE__ */ jsx3(Marker, { color: theme.ink.accent, glyph: theme.glyph.dot }),
428
+ /* @__PURE__ */ jsx3(Text3, { children: entry.text || (entry.meta?.cancelled ? "(cancelled before any output)" : "") })
429
+ ] }),
430
+ entry.meta ? /* @__PURE__ */ jsx3(Box3, { paddingLeft: 2, children: /* @__PURE__ */ jsx3(Text3, { dimColor: theme.ink.dim, children: metaLine(entry.meta) }) }) : null
431
+ ] });
432
+ case "error":
433
+ return /* @__PURE__ */ jsxs3(Box3, { marginBottom: 1, children: [
434
+ /* @__PURE__ */ jsx3(Marker, { color: theme.ink.error, glyph: theme.glyph.fail }),
435
+ /* @__PURE__ */ jsx3(Text3, { color: theme.ink.error, children: entry.text })
436
+ ] });
437
+ default:
438
+ return /* @__PURE__ */ jsx3(Box3, { marginBottom: 1, paddingLeft: 2, children: /* @__PURE__ */ jsx3(Text3, { children: entry.text }) });
439
+ }
440
+ }
441
+ function StreamingView({ streaming, now }) {
442
+ const theme = useTheme();
443
+ const seconds = Math.max(0, (now - streaming.startedAt) / 1e3).toFixed(0);
444
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginBottom: 1, children: [
445
+ streaming.text ? /* @__PURE__ */ jsxs3(Box3, { children: [
446
+ /* @__PURE__ */ jsx3(Marker, { color: theme.ink.accent, glyph: theme.glyph.dot }),
447
+ /* @__PURE__ */ jsx3(Text3, { children: streaming.text })
448
+ ] }) : null,
449
+ /* @__PURE__ */ jsxs3(Box3, { paddingLeft: streaming.text ? 2 : 0, children: [
450
+ /* @__PURE__ */ jsx3(Spinner, {}),
451
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: theme.ink.dim, children: [
452
+ " ",
453
+ streaming.text ? "streaming" : "thinking",
454
+ " ",
455
+ seconds,
456
+ "s \xB7 esc to cancel"
457
+ ] })
458
+ ] })
459
+ ] });
460
+ }
461
+
462
+ // src/tui/slash.ts
463
+ var SLASH_COMMANDS = [
464
+ { name: "model", args: "[id]", description: "show models or switch, e.g. /model davecode/auto" },
465
+ { name: "route", description: "routes and their failover targets" },
466
+ { name: "accounts", description: "accounts with 5h quota usage" },
467
+ { name: "tasks", description: "the project task graph" },
468
+ { name: "status", description: "gateway health and runner state" },
469
+ { name: "clear", description: "clear the transcript and start a new conversation" },
470
+ { name: "help", description: "commands and keyboard shortcuts", aliases: ["?"] },
471
+ { name: "exit", description: "quit DaveCode", aliases: ["quit", "q"] }
472
+ ];
473
+ function parseSlash(input) {
474
+ const text = input.trim();
475
+ if (!text.startsWith("/") || text.startsWith("//")) return { kind: "none" };
476
+ const [head = "", ...rest] = text.slice(1).split(/\s+/);
477
+ const name = head.toLowerCase();
478
+ if (!name) return { kind: "unknown", name: "" };
479
+ const command = SLASH_COMMANDS.find((c) => c.name === name || c.aliases?.includes(name));
480
+ if (!command) return { kind: "unknown", name };
481
+ return { kind: "command", command, args: rest.join(" ").trim() };
482
+ }
483
+ function slashSuggestions(input) {
484
+ if (!input.startsWith("/") || /\s/.test(input)) return [];
485
+ const prefix = input.slice(1).toLowerCase();
486
+ return SLASH_COMMANDS.filter(
487
+ (c) => c.name.startsWith(prefix) || c.aliases?.some((a) => a.startsWith(prefix) && prefix)
488
+ );
489
+ }
490
+ function completeSlash(input) {
491
+ const [first] = slashSuggestions(input);
492
+ if (!first) return void 0;
493
+ return `/${first.name}${first.args ? " " : ""}`;
494
+ }
495
+ var KEY_HINTS = [
496
+ ["Enter", "send"],
497
+ ["Shift+Enter, Ctrl+J or \\ Enter", "new line"],
498
+ ["\u2191 / \u2193", "move between lines, then through history"],
499
+ ["Tab", "complete a slash command"],
500
+ ["Esc", "cancel the response in flight"],
501
+ ["Ctrl+U / Ctrl+K / Ctrl+W", "delete to line start / line end / previous word"],
502
+ ["Ctrl+C twice", "exit"]
503
+ ];
504
+
505
+ // src/tui/slash-actions.ts
506
+ function helpText(theme) {
507
+ const width = Math.max(...SLASH_COMMANDS.map((c) => c.name.length + (c.args?.length ?? 0))) + 4;
508
+ const commands = SLASH_COMMANDS.map(
509
+ (c) => `${theme.accent(padEnd(`/${c.name}${c.args ? ` ${c.args}` : ""}`, width))}${theme.dim(c.description)}`
510
+ );
511
+ const keyWidth = Math.max(...KEY_HINTS.map(([k]) => k.length)) + 2;
512
+ const keys = KEY_HINTS.map(([k, v]) => `${padEnd(k, keyWidth)}${theme.dim(v)}`);
513
+ return [theme.bold("Commands"), ...commands, "", theme.bold("Keys"), ...keys].join("\n");
514
+ }
515
+ async function modelsText(theme, backend, current) {
516
+ const models = await backend.models();
517
+ const ids = models.map((m) => m.id).sort((a, b) => {
518
+ const da = a.startsWith("davecode/") ? 0 : 1;
519
+ const db = b.startsWith("davecode/") ? 0 : 1;
520
+ return da - db || a.localeCompare(b);
521
+ });
522
+ if (ids.length === 0)
523
+ return theme.dim("No models yet. Add an account with `davecode accounts add`.");
524
+ const shown = ids.slice(0, 40);
525
+ const lines2 = shown.map(
526
+ (id) => id === current ? `${theme.accent(theme.glyph.dot)} ${theme.accent(id)}` : ` ${id}`
527
+ );
528
+ if (ids.length > shown.length) lines2.push(theme.dim(` \u2026 and ${ids.length - shown.length} more`));
529
+ lines2.push("", theme.dim("Switch with /model <id>"));
530
+ return lines2.join("\n");
531
+ }
532
+ async function routesText(theme, backend) {
533
+ const { defaultRoute, routes } = await backend.routes();
534
+ if (routes.length === 0) {
535
+ return [
536
+ `${theme.dim("No routes configured.")} davecode/${defaultRoute} uses every account with a default model.`,
537
+ theme.dim("Define routes under routing.routes in ~/.davecode/config.json.")
538
+ ].join("\n");
539
+ }
540
+ const lines2 = [];
541
+ for (const route of routes) {
542
+ const mark = route.name === defaultRoute ? theme.dim(" (default)") : "";
543
+ lines2.push(
544
+ `${theme.accent(`davecode/${route.name}`)}${mark}${route.description ? theme.dim(` ${route.description}`) : ""}`
545
+ );
546
+ route.targets.forEach((t, i) => {
547
+ lines2.push(
548
+ ` ${theme.dim(`${i + 1}.`)} ${t.provider}/${t.model}${t.accountId ? theme.dim(` @${t.accountId}`) : ""}`
549
+ );
550
+ });
551
+ }
552
+ return lines2.join("\n");
553
+ }
554
+ async function accountsText(theme, backend, columns) {
555
+ const [accounts, usage] = await Promise.all([backend.accounts(), backend.usage()]);
556
+ if (accounts.length === 0)
557
+ return theme.dim("No accounts yet. Add one with `davecode accounts add`.");
558
+ const byId = new Map(usage.map((u) => [u.accountId, u]));
559
+ return renderAccounts(
560
+ theme,
561
+ accounts.map((account) => ({ account, usage: byId.get(account.id) ?? emptyUsage(account.id) })),
562
+ columns
563
+ ).join("\n");
564
+ }
565
+ async function tasksText(theme, backend, columns) {
566
+ const { project, graph } = await backend.tasks();
567
+ if (!project)
568
+ return theme.dim("No project brain is being served. Start DaveCode inside a repository.");
569
+ if (graph.tasks.length === 0) {
570
+ return theme.dim(`${project.name}: no tasks yet. Add one with \`davecode tasks add\`.`);
571
+ }
572
+ return [
573
+ `${theme.bold(project.name)} ${summaryLine(theme, graph)}`,
574
+ "",
575
+ ...formatTaskTree(theme, graph, { width: columns })
576
+ ].join("\n");
577
+ }
578
+ async function statusText(theme, backend) {
579
+ const health = await backend.health();
580
+ if (!health) return theme.error(`Gateway at ${backend.url} is not answering.`);
581
+ const runner = await backend.runner().catch(() => void 0);
582
+ const flags = Object.entries(health.experimental).filter(([, on]) => on).map(([k]) => k);
583
+ return [
584
+ `${theme.ok(theme.glyph.dot)} ${backend.url}${backend.inProcess ? theme.dim(" (in-process)") : ""} ${theme.dim(`v${health.version} \xB7 up ${formatDuration(health.uptimeSec)}`)}`,
585
+ `${theme.dim("runner")} ${runner ? runner.state : "unknown"}${runner?.taskId ? theme.dim(` \xB7 ${runner.taskId}`) : ""}`,
586
+ `${theme.dim("experimental")} ${flags.length > 0 ? theme.warn(flags.join(", ")) : "none"}`
587
+ ].join("\n");
588
+ }
589
+
590
+ // src/tui/ChatApp.tsx
591
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
592
+ var PLACEHOLDER = "Ask anything \xB7 / for commands \xB7 Shift+Enter or Ctrl+J for a new line";
593
+ var EXIT_WINDOW_MS = 2e3;
594
+ function errorText(err) {
595
+ return err instanceof Error ? err.message : String(err);
596
+ }
597
+ function Chat({ backend, model, version, pollMs = 1e4, now = Date.now }) {
598
+ const theme = useTheme();
599
+ const { exit } = useApp();
600
+ const { write } = useStdout();
601
+ const { columns } = useWindowSize();
602
+ const width = Math.max(20, columns);
603
+ const [state, dispatch] = useReducer(chatReducer, model, initialChatState);
604
+ const [editor, setEditor] = useState(emptyEditor);
605
+ const [historyIndex, setHistoryIndex] = useState();
606
+ const [notice, setNotice] = useState();
607
+ const [clock, setClock] = useState(now());
608
+ const abortRef = useRef(void 0);
609
+ const exitArmedAt = useRef(void 0);
610
+ const labels = useRef(/* @__PURE__ */ new Map());
611
+ const stateRef = useRef(state);
612
+ stateRef.current = state;
613
+ useEffect(() => {
614
+ const where = backend.inProcess ? `${backend.url} (in-process)` : backend.url;
615
+ dispatch({
616
+ type: "banner",
617
+ text: `${theme.bold(theme.accent("DaveCode"))} ${theme.dim(`v${version}`)}
618
+ ${theme.dim(
619
+ `model ${model} \xB7 gateway ${where} \xB7 /help for commands`
620
+ )}`
621
+ });
622
+ let alive = true;
623
+ const check = async () => {
624
+ const health = await backend.health();
625
+ if (alive) dispatch({ type: "connection", connection: health ? "online" : "offline" });
626
+ };
627
+ void check();
628
+ backend.accounts().then((accounts) => {
629
+ for (const a of accounts) labels.current.set(a.id, a.label);
630
+ }).catch(() => {
631
+ });
632
+ const timer = setInterval(() => void check(), pollMs);
633
+ return () => {
634
+ alive = false;
635
+ clearInterval(timer);
636
+ abortRef.current?.abort();
637
+ };
638
+ }, [backend, model, pollMs, theme, version]);
639
+ useEffect(() => {
640
+ if (!state.streaming) return;
641
+ const timer = setInterval(() => setClock(now()), 250);
642
+ return () => clearInterval(timer);
643
+ }, [state.streaming, now]);
644
+ useEffect(() => {
645
+ if (!notice) return;
646
+ const timer = setTimeout(() => setNotice(void 0), EXIT_WINDOW_MS);
647
+ return () => clearTimeout(timer);
648
+ }, [notice]);
649
+ const refreshQuota = useCallback(
650
+ async (accountId) => {
651
+ try {
652
+ const usage = (await backend.usage()).find((u) => u.accountId === accountId);
653
+ const window = usage?.windows["5h"];
654
+ const limited = window && (window.tokenLimit !== void 0 || window.requestLimit !== void 0);
655
+ dispatch({
656
+ type: "quota",
657
+ accountId,
658
+ utilization: limited ? window.utilization : void 0
659
+ });
660
+ } catch {
661
+ }
662
+ },
663
+ [backend]
664
+ );
665
+ const send = useCallback(
666
+ async (text) => {
667
+ dispatch({ type: "user", text });
668
+ const conversation = [
669
+ ...stateRef.current.conversation,
670
+ { role: "user", content: text }
671
+ ];
672
+ const controller = new AbortController();
673
+ abortRef.current = controller;
674
+ dispatch({ type: "stream-start", now: now() });
675
+ let usage;
676
+ let accountId;
677
+ try {
678
+ const { meta, chunks } = await backend.stream(
679
+ { model: stateRef.current.model, messages: conversation },
680
+ controller.signal
681
+ );
682
+ accountId = meta.accountId;
683
+ dispatch({
684
+ type: "stream-meta",
685
+ failovers: meta.failovers,
686
+ ...meta.accountId ? { accountId: meta.accountId } : {},
687
+ ...meta.provider ? { provider: meta.provider } : {},
688
+ ...meta.accountId && labels.current.has(meta.accountId) ? { accountLabel: labels.current.get(meta.accountId) } : {}
689
+ });
690
+ dispatch({ type: "connection", connection: "online" });
691
+ for await (const chunk of chunks) {
692
+ if (controller.signal.aborted) break;
693
+ const delta = chunk.choices[0]?.delta?.content;
694
+ if (typeof delta === "string" && delta) dispatch({ type: "stream-delta", text: delta });
695
+ if (chunk.usage) usage = chunk.usage;
696
+ }
697
+ dispatch({
698
+ type: "stream-end",
699
+ now: now(),
700
+ ...usage ? { usage } : {},
701
+ ...controller.signal.aborted ? { cancelled: true } : {}
702
+ });
703
+ } catch (err) {
704
+ if (controller.signal.aborted) {
705
+ dispatch({ type: "stream-end", now: now(), cancelled: true });
706
+ } else {
707
+ const status = err.status;
708
+ if (status === 0) dispatch({ type: "connection", connection: "offline" });
709
+ dispatch({ type: "stream-end", now: now(), error: errorText(err) });
710
+ }
711
+ } finally {
712
+ if (abortRef.current === controller) abortRef.current = void 0;
713
+ }
714
+ if (accountId) void refreshQuota(accountId);
715
+ },
716
+ [backend, now, refreshQuota]
717
+ );
718
+ const runSlash = useCallback(
719
+ async (input) => {
720
+ const parsed = parseSlash(input);
721
+ if (parsed.kind === "unknown") {
722
+ dispatch({
723
+ type: "error",
724
+ text: `Unknown command /${parsed.name}. Type /help for the list.`
725
+ });
726
+ return;
727
+ }
728
+ if (parsed.kind !== "command") return;
729
+ const { command, args } = parsed;
730
+ try {
731
+ switch (command.name) {
732
+ case "help":
733
+ dispatch({ type: "info", text: helpText(theme) });
734
+ break;
735
+ case "exit":
736
+ abortRef.current?.abort();
737
+ exit();
738
+ break;
739
+ case "clear":
740
+ abortRef.current?.abort();
741
+ write("\x1B[2J\x1B[3J\x1B[H");
742
+ dispatch({ type: "clear" });
743
+ break;
744
+ case "model":
745
+ if (args) {
746
+ dispatch({ type: "set-model", model: args });
747
+ dispatch({ type: "info", text: `Model set to ${theme.accent(args)}` });
748
+ } else {
749
+ dispatch({
750
+ type: "info",
751
+ text: await modelsText(theme, backend, stateRef.current.model)
752
+ });
753
+ }
754
+ break;
755
+ case "route":
756
+ dispatch({ type: "info", text: await routesText(theme, backend) });
757
+ break;
758
+ case "accounts": {
759
+ const accounts = await backend.accounts();
760
+ for (const a of accounts) labels.current.set(a.id, a.label);
761
+ dispatch({ type: "info", text: await accountsText(theme, backend, width - 4) });
762
+ break;
763
+ }
764
+ case "tasks":
765
+ dispatch({ type: "info", text: await tasksText(theme, backend, width - 4) });
766
+ break;
767
+ case "status":
768
+ dispatch({ type: "info", text: await statusText(theme, backend) });
769
+ break;
770
+ }
771
+ } catch (err) {
772
+ dispatch({ type: "error", text: `/${command.name} failed: ${errorText(err)}` });
773
+ }
774
+ },
775
+ [backend, exit, theme, width, write]
776
+ );
777
+ const submit = useCallback(() => {
778
+ const text = textOf(editor).trim();
779
+ if (!text) return;
780
+ if (parseSlash(text).kind !== "none") {
781
+ setEditor(emptyEditor);
782
+ setHistoryIndex(void 0);
783
+ void runSlash(text);
784
+ return;
785
+ }
786
+ if (stateRef.current.streaming) {
787
+ setNotice("Wait for the reply, or press Esc to cancel it");
788
+ return;
789
+ }
790
+ setEditor(emptyEditor);
791
+ setHistoryIndex(void 0);
792
+ void send(text);
793
+ }, [editor, runSlash, send]);
794
+ const recall = (direction) => {
795
+ const { history } = stateRef.current;
796
+ if (history.length === 0) return;
797
+ const current = historyIndex ?? history.length;
798
+ const next = Math.min(history.length, Math.max(0, current + direction));
799
+ setHistoryIndex(next === history.length ? void 0 : next);
800
+ setEditor(next === history.length ? emptyEditor : fromText(history[next]));
801
+ };
802
+ useInput((input, key) => {
803
+ if (key.ctrl && input === "c") {
804
+ const armed = exitArmedAt.current !== void 0 && now() - exitArmedAt.current < EXIT_WINDOW_MS;
805
+ if (armed) {
806
+ abortRef.current?.abort();
807
+ exit();
808
+ return;
809
+ }
810
+ exitArmedAt.current = now();
811
+ if (stateRef.current.streaming) abortRef.current?.abort();
812
+ else if (editor.chars.length > 0) setEditor(emptyEditor);
813
+ setNotice("Press Ctrl+C again to exit");
814
+ return;
815
+ }
816
+ exitArmedAt.current = void 0;
817
+ if (key.escape) {
818
+ if (stateRef.current.streaming) abortRef.current?.abort();
819
+ return;
820
+ }
821
+ if (input === "\n" && !key.return || key.ctrl && input === "j") {
822
+ setEditor((e) => insert(e, "\n"));
823
+ return;
824
+ }
825
+ if (key.return) {
826
+ if (key.shift || key.meta) setEditor((e) => insert(e, "\n"));
827
+ else if (continuesLine(editor)) setEditor((e) => newlineAfterBackslash(e));
828
+ else submit();
829
+ return;
830
+ }
831
+ if (key.tab) {
832
+ const completed = completeSlash(textOf(editor));
833
+ if (completed) setEditor(fromText(completed));
834
+ return;
835
+ }
836
+ if (key.upArrow) {
837
+ const moved = up(editor);
838
+ if (moved) setEditor(moved);
839
+ else recall(-1);
840
+ return;
841
+ }
842
+ if (key.downArrow) {
843
+ const moved = down(editor);
844
+ if (moved) setEditor(moved);
845
+ else if (historyIndex !== void 0) recall(1);
846
+ return;
847
+ }
848
+ if (key.leftArrow) return setEditor(left);
849
+ if (key.rightArrow) return setEditor(right);
850
+ if (key.home || key.ctrl && input === "a") return setEditor(lineStart);
851
+ if (key.end || key.ctrl && input === "e") return setEditor(lineEnd);
852
+ if (key.backspace) return setEditor(backspace);
853
+ if (key.delete) return setEditor(deleteForward);
854
+ if (key.ctrl && input === "u") return setEditor(killToLineStart);
855
+ if (key.ctrl && input === "k") return setEditor(killToLineEnd);
856
+ if (key.ctrl && input === "w") return setEditor(deleteWordBack);
857
+ if (key.ctrl && input === "l") {
858
+ write("\x1B[2J\x1B[H");
859
+ return;
860
+ }
861
+ if (key.ctrl || key.meta || !input) return;
862
+ setEditor((e) => insert(e, input));
863
+ });
864
+ usePaste((text) => {
865
+ setEditor((e) => insert(e, text));
866
+ });
867
+ const suggestions = slashSuggestions(textOf(editor));
868
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", width, children: [
869
+ /* @__PURE__ */ jsx4(Static, { items: state.entries, children: (entry) => /* @__PURE__ */ jsx4(EntryView, { entry }, entry.id) }, state.epoch),
870
+ state.streaming ? /* @__PURE__ */ jsx4(StreamingView, { streaming: state.streaming, now: clock }) : null,
871
+ /* @__PURE__ */ jsx4(InputBox, { editor, width, placeholder: PLACEHOLDER }),
872
+ /* @__PURE__ */ jsx4(SlashHints, { commands: suggestions, width }),
873
+ /* @__PURE__ */ jsx4(
874
+ StatusLine,
875
+ {
876
+ state,
877
+ width,
878
+ info: {
879
+ where: backend.inProcess ? `in-process ${new URL(backend.url).port}` : backend.url,
880
+ inProcess: backend.inProcess,
881
+ ...notice ? { notice } : {}
882
+ }
883
+ }
884
+ )
885
+ ] });
886
+ }
887
+ function ChatApp(props) {
888
+ return /* @__PURE__ */ jsx4(ThemeProvider, { theme: props.theme, children: /* @__PURE__ */ jsx4(Chat, { ...props }) });
889
+ }
890
+ export {
891
+ ChatApp
892
+ };
893
+ //# sourceMappingURL=ChatApp-FO5SABXB.js.map