@dmrlzz/davecode 0.0.0-stage → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +34 -2
  3. package/bin/davecode.js +2 -0
  4. package/dist/ChatApp-BO7QMBWD.js +1045 -0
  5. package/dist/ChatApp-BO7QMBWD.js.map +1 -0
  6. package/dist/RunnerView-A7JSPUW5.js +18 -0
  7. package/dist/RunnerView-A7JSPUW5.js.map +1 -0
  8. package/dist/account-UGNLJPQ4.js +23 -0
  9. package/dist/account-UGNLJPQ4.js.map +1 -0
  10. package/dist/accounts-NM7IWP53.js +37 -0
  11. package/dist/accounts-NM7IWP53.js.map +1 -0
  12. package/dist/budget-INAW6FXD.js +115 -0
  13. package/dist/budget-INAW6FXD.js.map +1 -0
  14. package/dist/chat-U4QJQ7M6.js +359 -0
  15. package/dist/chat-U4QJQ7M6.js.map +1 -0
  16. package/dist/chunk-3Q2KCXLN.js +9608 -0
  17. package/dist/chunk-3Q2KCXLN.js.map +1 -0
  18. package/dist/chunk-5LKOJBUL.js +20 -0
  19. package/dist/chunk-5LKOJBUL.js.map +1 -0
  20. package/dist/chunk-7XVXEO7U.js +23 -0
  21. package/dist/chunk-7XVXEO7U.js.map +1 -0
  22. package/dist/chunk-CWBVNS3L.js +731 -0
  23. package/dist/chunk-CWBVNS3L.js.map +1 -0
  24. package/dist/chunk-DZ2FDABF.js +1614 -0
  25. package/dist/chunk-DZ2FDABF.js.map +1 -0
  26. package/dist/chunk-FRFSJMN4.js +79 -0
  27. package/dist/chunk-FRFSJMN4.js.map +1 -0
  28. package/dist/chunk-GE22VBCY.js +200 -0
  29. package/dist/chunk-GE22VBCY.js.map +1 -0
  30. package/dist/chunk-GHDOUII5.js +144 -0
  31. package/dist/chunk-GHDOUII5.js.map +1 -0
  32. package/dist/chunk-GZJ6JSO7.js +213 -0
  33. package/dist/chunk-GZJ6JSO7.js.map +1 -0
  34. package/dist/chunk-IC3TDONV.js +12 -0
  35. package/dist/chunk-IC3TDONV.js.map +1 -0
  36. package/dist/chunk-MC7RIPL7.js +116 -0
  37. package/dist/chunk-MC7RIPL7.js.map +1 -0
  38. package/dist/chunk-MOK5D3XJ.js +114 -0
  39. package/dist/chunk-MOK5D3XJ.js.map +1 -0
  40. package/dist/chunk-PGPXE6LF.js +20 -0
  41. package/dist/chunk-PGPXE6LF.js.map +1 -0
  42. package/dist/chunk-QA3O6RA2.js +81 -0
  43. package/dist/chunk-QA3O6RA2.js.map +1 -0
  44. package/dist/chunk-ROIUTNQV.js +20 -0
  45. package/dist/chunk-ROIUTNQV.js.map +1 -0
  46. package/dist/chunk-SF5ZEAP4.js +105 -0
  47. package/dist/chunk-SF5ZEAP4.js.map +1 -0
  48. package/dist/chunk-Z6NQDJEN.js +180 -0
  49. package/dist/chunk-Z6NQDJEN.js.map +1 -0
  50. package/dist/config-YEAPBMBX.js +158 -0
  51. package/dist/config-YEAPBMBX.js.map +1 -0
  52. package/dist/dashboard/assets/Accounts-CeVyX4a3.js +1 -0
  53. package/dist/dashboard/assets/Badge-CGY6DgGx.js +55 -0
  54. package/dist/dashboard/assets/Callout-DauCk_cW.js +1 -0
  55. package/dist/dashboard/assets/Field-OXMnT9Tb.js +1 -0
  56. package/dist/dashboard/assets/Goals-DH-KDh8M.js +1 -0
  57. package/dist/dashboard/assets/Library-C15szLjZ.js +1 -0
  58. package/dist/dashboard/assets/Markdown-Xhao3sgL.js +69 -0
  59. package/dist/dashboard/assets/Panel-Du40pM7n.js +1 -0
  60. package/dist/dashboard/assets/RoutesPage-Cj1OelGO.js +4 -0
  61. package/dist/dashboard/assets/Runner-BK1-ZF7h.js +1 -0
  62. package/dist/dashboard/assets/Settings-C1nzQQhe.js +1 -0
  63. package/dist/dashboard/assets/Switch-bwtIJ00H.js +1 -0
  64. package/dist/dashboard/assets/Tasks-x4s8IiPG.js +2 -0
  65. package/dist/dashboard/assets/Traffic-CV7TaTid.js +1 -0
  66. package/dist/dashboard/assets/Workspace-DAL-MBc0.js +1 -0
  67. package/dist/dashboard/assets/arrow-up-goM9r-I9.js +1 -0
  68. package/dist/dashboard/assets/forms-DtpfI65-.js +1 -0
  69. package/dist/dashboard/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  70. package/dist/dashboard/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  71. package/dist/dashboard/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  72. package/dist/dashboard/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  73. package/dist/dashboard/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  74. package/dist/dashboard/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  75. package/dist/dashboard/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  76. package/dist/dashboard/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  77. package/dist/dashboard/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  78. package/dist/dashboard/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  79. package/dist/dashboard/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  80. package/dist/dashboard/assets/index-BCi-Vuvh.js +104 -0
  81. package/dist/dashboard/assets/index-CcJFhc98.css +2 -0
  82. package/dist/dashboard/assets/task-errors-B2pgjUGN.js +1 -0
  83. package/dist/dashboard/assets/trash-BVojETtF.js +1 -0
  84. package/dist/dashboard/favicon.svg +1 -0
  85. package/dist/dashboard/github.svg +1 -0
  86. package/dist/dashboard/index.html +31 -0
  87. package/dist/dist-RRQGUBL3.js +379 -0
  88. package/dist/dist-RRQGUBL3.js.map +1 -0
  89. package/dist/doctor-MQYK44S2.js +279 -0
  90. package/dist/doctor-MQYK44S2.js.map +1 -0
  91. package/dist/goal-6NUX4AIT.js +302 -0
  92. package/dist/goal-6NUX4AIT.js.map +1 -0
  93. package/dist/index.js +399 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/init-AXYIWAB3.js +194 -0
  96. package/dist/init-AXYIWAB3.js.map +1 -0
  97. package/dist/prompts-TZ7UC42M.js +219 -0
  98. package/dist/prompts-TZ7UC42M.js.map +1 -0
  99. package/dist/run-QRTLHFB3.js +376 -0
  100. package/dist/run-QRTLHFB3.js.map +1 -0
  101. package/dist/runner-state-QFU3LJVJ.js +17 -0
  102. package/dist/runner-state-QFU3LJVJ.js.map +1 -0
  103. package/dist/runtime.js +18 -0
  104. package/dist/runtime.js.map +1 -0
  105. package/dist/start-O5I5TSO2.js +285 -0
  106. package/dist/start-O5I5TSO2.js.map +1 -0
  107. package/dist/status-LEA3RVUX.js +25 -0
  108. package/dist/status-LEA3RVUX.js.map +1 -0
  109. package/dist/tasks-XOTVICVZ.js +184 -0
  110. package/dist/tasks-XOTVICVZ.js.map +1 -0
  111. package/package.json +73 -3
@@ -0,0 +1,1045 @@
1
+ import {
2
+ formatTaskTree,
3
+ summaryLine
4
+ } from "./chunk-MOK5D3XJ.js";
5
+ import {
6
+ brandBanner
7
+ } from "./chunk-ROIUTNQV.js";
8
+ import {
9
+ Spinner
10
+ } from "./chunk-5LKOJBUL.js";
11
+ import {
12
+ ThemeProvider,
13
+ useTheme
14
+ } from "./chunk-PGPXE6LF.js";
15
+ import {
16
+ emptyUsage,
17
+ renderAccounts
18
+ } from "./chunk-GZJ6JSO7.js";
19
+ import {
20
+ bar,
21
+ formatTokens,
22
+ levelStyle,
23
+ padEnd,
24
+ percent,
25
+ visibleWidth
26
+ } from "./chunk-SF5ZEAP4.js";
27
+ import "./chunk-QA3O6RA2.js";
28
+ import "./chunk-DZ2FDABF.js";
29
+ import "./chunk-7XVXEO7U.js";
30
+ import "./chunk-3Q2KCXLN.js";
31
+ import "./chunk-MC7RIPL7.js";
32
+
33
+ // src/tui/ChatApp.tsx
34
+ import { Box as Box5, Static, useApp, useInput, usePaste, useStdout, useWindowSize } from "ink";
35
+ import { useCallback, useEffect, useReducer, useRef, useState } from "react";
36
+
37
+ // src/tui/chat-state.ts
38
+ function initialChatState(model) {
39
+ return {
40
+ entries: [],
41
+ conversation: [],
42
+ model,
43
+ connection: "connecting",
44
+ lastFailovers: 0,
45
+ totalFailovers: 0,
46
+ tokens: { prompt: 0, completion: 0 },
47
+ epoch: 0,
48
+ history: [],
49
+ nextId: 1
50
+ };
51
+ }
52
+ function estimateTokens(text) {
53
+ return Math.ceil(text.length / 4);
54
+ }
55
+ function push(state, kind, text, meta) {
56
+ const entry = { id: state.nextId, kind, text };
57
+ if (meta) entry.meta = meta;
58
+ return { ...state, entries: [...state.entries, entry], nextId: state.nextId + 1 };
59
+ }
60
+ function chatReducer(state, action) {
61
+ switch (action.type) {
62
+ case "banner":
63
+ return push(state, "banner", action.text);
64
+ case "info":
65
+ return push(state, "info", action.text);
66
+ case "error":
67
+ return push(state, "error", action.text);
68
+ case "user": {
69
+ const next = push(state, "user", action.text);
70
+ const history = state.history[state.history.length - 1] === action.text ? state.history : [...state.history, action.text].slice(-200);
71
+ return {
72
+ ...next,
73
+ history,
74
+ conversation: [...state.conversation, { role: "user", content: action.text }]
75
+ };
76
+ }
77
+ case "stream-start":
78
+ return {
79
+ ...state,
80
+ streaming: { id: state.nextId, text: "", startedAt: action.now, model: state.model },
81
+ nextId: state.nextId + 1
82
+ };
83
+ case "stream-meta": {
84
+ if (!state.streaming) return state;
85
+ const streaming = { ...state.streaming };
86
+ if (action.accountId) streaming.accountId = action.accountId;
87
+ if (action.provider) streaming.provider = action.provider;
88
+ if (action.failovers !== void 0) streaming.failovers = action.failovers;
89
+ const lastAccount = action.accountId ? {
90
+ id: action.accountId,
91
+ ...action.accountLabel ? { label: action.accountLabel } : {},
92
+ ...action.provider ? { provider: action.provider } : {}
93
+ } : state.lastAccount;
94
+ return {
95
+ ...state,
96
+ streaming,
97
+ ...lastAccount ? { lastAccount } : {},
98
+ ...action.failovers !== void 0 ? {
99
+ lastFailovers: action.failovers,
100
+ totalFailovers: state.totalFailovers + action.failovers
101
+ } : {}
102
+ };
103
+ }
104
+ case "stream-delta":
105
+ if (!state.streaming) return state;
106
+ return {
107
+ ...state,
108
+ streaming: { ...state.streaming, text: state.streaming.text + action.text }
109
+ };
110
+ case "stream-end": {
111
+ const live = state.streaming;
112
+ if (!live) return state;
113
+ const prompt = action.usage?.prompt_tokens ?? estimateTokens(
114
+ state.conversation.map((m) => typeof m.content === "string" ? m.content : "").join("\n")
115
+ );
116
+ const completion = action.usage?.completion_tokens ?? estimateTokens(live.text);
117
+ const meta = {
118
+ model: live.model,
119
+ latencyMs: action.now - live.startedAt,
120
+ tokens: prompt + completion,
121
+ ...action.usage ? {} : { estimated: true },
122
+ ...live.accountId ? { accountId: live.accountId } : {},
123
+ ...state.lastAccount?.id === live.accountId && state.lastAccount?.label ? { accountLabel: state.lastAccount.label } : {},
124
+ ...live.provider ? { provider: live.provider } : {},
125
+ ...live.failovers ? { failovers: live.failovers } : {},
126
+ ...action.cancelled ? { cancelled: true } : {}
127
+ };
128
+ let next = { ...state };
129
+ delete next.streaming;
130
+ const answered = live.text.length > 0;
131
+ if (answered || action.cancelled) next = push(next, "assistant", live.text, meta);
132
+ if (action.error) next = push(next, "error", action.error);
133
+ if (answered && !action.cancelled && !action.error) {
134
+ next.conversation = [...next.conversation, { role: "assistant", content: live.text }];
135
+ } else if (next.conversation[next.conversation.length - 1]?.role === "user") {
136
+ next.conversation = next.conversation.slice(0, -1);
137
+ }
138
+ if (answered || action.usage) {
139
+ next.tokens = {
140
+ prompt: state.tokens.prompt + prompt,
141
+ completion: state.tokens.completion + completion
142
+ };
143
+ }
144
+ return next;
145
+ }
146
+ case "set-model":
147
+ return { ...state, model: action.model };
148
+ case "connection":
149
+ return state.connection === action.connection ? state : { ...state, connection: action.connection };
150
+ case "quota": {
151
+ if (state.lastAccount?.id !== action.accountId) return state;
152
+ const next = { ...state };
153
+ if (action.utilization === void 0) delete next.quota5h;
154
+ else next.quota5h = action.utilization;
155
+ return next;
156
+ }
157
+ case "clear": {
158
+ const next = {
159
+ ...state,
160
+ entries: [],
161
+ conversation: [],
162
+ epoch: state.epoch + 1
163
+ };
164
+ delete next.streaming;
165
+ return next;
166
+ }
167
+ default:
168
+ return state;
169
+ }
170
+ }
171
+
172
+ // src/tui/components/InputBox.tsx
173
+ import { Box, Text } from "ink";
174
+
175
+ // src/tui/editor.ts
176
+ var emptyEditor = { chars: [], cursor: 0 };
177
+ function fromText(text) {
178
+ const chars = [...text];
179
+ return { chars, cursor: chars.length };
180
+ }
181
+ function textOf(state) {
182
+ return state.chars.join("");
183
+ }
184
+ function normalizeInput(text) {
185
+ return text.replace(/\r\n?/g, "\n").replace(/\t/g, " ").replace(/[\u0000-\u0009\u000b-\u001f\u007f]/g, "");
186
+ }
187
+ function insert(state, text) {
188
+ const add = [...normalizeInput(text)];
189
+ if (add.length === 0) return state;
190
+ const chars = [...state.chars.slice(0, state.cursor), ...add, ...state.chars.slice(state.cursor)];
191
+ return { chars, cursor: state.cursor + add.length };
192
+ }
193
+ function backspace(state) {
194
+ if (state.cursor === 0) return state;
195
+ const chars = [...state.chars.slice(0, state.cursor - 1), ...state.chars.slice(state.cursor)];
196
+ return { chars, cursor: state.cursor - 1 };
197
+ }
198
+ function deleteForward(state) {
199
+ if (state.cursor >= state.chars.length) return state;
200
+ const chars = [...state.chars.slice(0, state.cursor), ...state.chars.slice(state.cursor + 1)];
201
+ return { chars, cursor: state.cursor };
202
+ }
203
+ function left(state) {
204
+ return { ...state, cursor: Math.max(0, state.cursor - 1) };
205
+ }
206
+ function right(state) {
207
+ return { ...state, cursor: Math.min(state.chars.length, state.cursor + 1) };
208
+ }
209
+ function position(state) {
210
+ let line = 0;
211
+ let column = 0;
212
+ for (let i = 0; i < state.cursor; i++) {
213
+ if (state.chars[i] === "\n") {
214
+ line++;
215
+ column = 0;
216
+ } else {
217
+ column++;
218
+ }
219
+ }
220
+ return { line, column };
221
+ }
222
+ function lines(state) {
223
+ const out = [[]];
224
+ for (const ch of state.chars) {
225
+ if (ch === "\n") out.push([]);
226
+ else out[out.length - 1].push(ch);
227
+ }
228
+ return out;
229
+ }
230
+ function offsetOf(all, line, column) {
231
+ let offset = 0;
232
+ for (let i = 0; i < line; i++) offset += all[i].length + 1;
233
+ return offset + Math.min(column, all[line].length);
234
+ }
235
+ function up(state) {
236
+ const { line, column } = position(state);
237
+ if (line === 0) return void 0;
238
+ return { ...state, cursor: offsetOf(lines(state), line - 1, column) };
239
+ }
240
+ function down(state) {
241
+ const all = lines(state);
242
+ const { line, column } = position(state);
243
+ if (line >= all.length - 1) return void 0;
244
+ return { ...state, cursor: offsetOf(all, line + 1, column) };
245
+ }
246
+ function lineStart(state) {
247
+ const { line } = position(state);
248
+ return { ...state, cursor: offsetOf(lines(state), line, 0) };
249
+ }
250
+ function lineEnd(state) {
251
+ const all = lines(state);
252
+ const { line } = position(state);
253
+ return { ...state, cursor: offsetOf(all, line, all[line].length) };
254
+ }
255
+ function killToLineStart(state) {
256
+ const start = lineStart(state).cursor;
257
+ if (start === state.cursor) return state;
258
+ return {
259
+ chars: [...state.chars.slice(0, start), ...state.chars.slice(state.cursor)],
260
+ cursor: start
261
+ };
262
+ }
263
+ function killToLineEnd(state) {
264
+ const end = lineEnd(state).cursor;
265
+ return {
266
+ chars: [...state.chars.slice(0, state.cursor), ...state.chars.slice(end)],
267
+ cursor: state.cursor
268
+ };
269
+ }
270
+ function deleteWordBack(state) {
271
+ let i = state.cursor;
272
+ while (i > 0 && /[ \t]/.test(state.chars[i - 1])) i--;
273
+ while (i > 0 && !/[\s]/.test(state.chars[i - 1])) i--;
274
+ if (i === state.cursor) return backspace(state);
275
+ return { chars: [...state.chars.slice(0, i), ...state.chars.slice(state.cursor)], cursor: i };
276
+ }
277
+ function continuesLine(state) {
278
+ return state.cursor > 0 && state.chars[state.cursor - 1] === "\\";
279
+ }
280
+ function newlineAfterBackslash(state) {
281
+ return insert(backspace(state), "\n");
282
+ }
283
+
284
+ // src/tui/components/InputBox.tsx
285
+ import { jsx, jsxs } from "react/jsx-runtime";
286
+ function cursorParts(line, column) {
287
+ if (column === void 0) return { before: line.join(""), at: "", after: "" };
288
+ return {
289
+ before: line.slice(0, column).join(""),
290
+ at: line[column] ?? " ",
291
+ after: line.slice(column + 1).join("")
292
+ };
293
+ }
294
+ function InputBox({
295
+ editor,
296
+ width,
297
+ placeholder,
298
+ disabled
299
+ }) {
300
+ const theme = useTheme();
301
+ const all = lines(editor);
302
+ const cursor = position(editor);
303
+ const empty = editor.chars.length === 0;
304
+ return /* @__PURE__ */ jsx(
305
+ Box,
306
+ {
307
+ borderStyle: theme.unicode ? "round" : "classic",
308
+ borderDimColor: theme.ink.dim,
309
+ flexDirection: "column",
310
+ paddingX: 1,
311
+ width,
312
+ children: all.map((line, i) => {
313
+ const parts = cursorParts(line, !disabled && i === cursor.line ? cursor.column : void 0);
314
+ return (
315
+ // biome-ignore lint/suspicious/noArrayIndexKey: lines have no identity beyond position
316
+ /* @__PURE__ */ jsxs(Box, { children: [
317
+ /* @__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 : "" }) }),
318
+ empty && i === 0 ? /* @__PURE__ */ jsxs(Text, { children: [
319
+ disabled ? null : /* @__PURE__ */ jsx(Text, { inverse: true, children: " " }),
320
+ /* @__PURE__ */ jsx(Text, { dimColor: theme.ink.dim, children: placeholder })
321
+ ] }) : /* @__PURE__ */ jsxs(Text, { children: [
322
+ parts.before,
323
+ parts.at ? /* @__PURE__ */ jsx(Text, { inverse: true, children: parts.at }) : null,
324
+ parts.after
325
+ ] })
326
+ ] }, i)
327
+ );
328
+ })
329
+ }
330
+ );
331
+ }
332
+ function SlashHints({
333
+ commands,
334
+ width,
335
+ selected = 0
336
+ }) {
337
+ const theme = useTheme();
338
+ if (commands.length === 0) return null;
339
+ const nameWidth = Math.max(...commands.map((c) => c.name.length + (c.args ? c.args.length + 1 : 0))) + 3;
340
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, width, children: commands.map((c, i) => {
341
+ const active = i === selected;
342
+ return /* @__PURE__ */ jsxs(Text, { wrap: "truncate-end", children: [
343
+ /* @__PURE__ */ jsx(Text, { bold: active, dimColor: !active && theme.ink.dim, children: `${active ? theme.glyph.prompt : " "} ${`/${c.name}${c.args ? ` ${c.args}` : ""}`.padEnd(nameWidth)}` }),
344
+ /* @__PURE__ */ jsx(Text, { dimColor: theme.ink.dim, children: c.description })
345
+ ] }, c.name);
346
+ }) });
347
+ }
348
+
349
+ // src/tui/components/Picker.tsx
350
+ import { Box as Box2, Text as Text2 } from "ink";
351
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
352
+ function Picker({
353
+ title,
354
+ options,
355
+ index
356
+ }) {
357
+ const theme = useTheme();
358
+ const numbered = options.length <= 9;
359
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: 1, children: [
360
+ /* @__PURE__ */ jsx2(Text2, { bold: true, children: title }),
361
+ options.map((option, i) => {
362
+ const on = i === index;
363
+ return /* @__PURE__ */ jsxs2(Text2, { dimColor: !on && theme.ink.dim, children: [
364
+ on ? "\u203A " : " ",
365
+ numbered ? `${i + 1}. ` : "",
366
+ /* @__PURE__ */ jsx2(Text2, { bold: on, children: option.label }),
367
+ option.hint ? /* @__PURE__ */ jsxs2(Text2, { dimColor: theme.ink.dim, children: [
368
+ " ",
369
+ option.hint
370
+ ] }) : null
371
+ ] }, option.value);
372
+ }),
373
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: theme.ink.dim, children: [
374
+ "\u2191\u2193 select \xB7 enter choose",
375
+ numbered ? " \xB7 1-9" : "",
376
+ " \xB7 esc cancel"
377
+ ] })
378
+ ] });
379
+ }
380
+
381
+ // src/tui/components/StatusLine.tsx
382
+ import { Box as Box3, Text as Text3 } from "ink";
383
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
384
+ function statusSegments(theme, state, info, width) {
385
+ const sep = theme.dim(" \xB7 ");
386
+ const left2 = [[9, theme.accent(state.model)]];
387
+ if (state.lastAccount) {
388
+ const name = state.lastAccount.label ?? state.lastAccount.id;
389
+ left2.push([
390
+ 8,
391
+ `${name}${state.lastAccount.provider ? theme.dim(` (${state.lastAccount.provider})`) : ""}`
392
+ ]);
393
+ }
394
+ if (state.totalFailovers > 0) {
395
+ left2.push([7, theme.warn(`${state.lastFailovers}/${state.totalFailovers} failovers`)]);
396
+ }
397
+ if (state.tokens.prompt + state.tokens.completion > 0) {
398
+ const arrows = theme.unicode ? ["\u2191", "\u2193"] : ["in", "out"];
399
+ left2.push([
400
+ 5,
401
+ theme.dim(
402
+ `${formatTokens(state.tokens.prompt)}${arrows[0]} ${formatTokens(state.tokens.completion)}${arrows[1]}`
403
+ )
404
+ ]);
405
+ }
406
+ if (state.quota5h !== void 0) {
407
+ left2.push([
408
+ 6,
409
+ `${theme.dim("5h")} ${bar(theme, state.quota5h, 6)} ${levelStyle(theme, state.quota5h)(percent(state.quota5h))}`
410
+ ]);
411
+ }
412
+ let right2 = "";
413
+ if (info.notice) right2 = theme.warn(info.notice);
414
+ else if (state.connection === "connecting") right2 = theme.dim("connecting");
415
+ else if (state.connection === "offline") right2 = theme.error("offline");
416
+ let kept = left2;
417
+ const render = () => kept.map(([, text]) => text).join(sep);
418
+ while (kept.length > 1 && visibleWidth(render()) + visibleWidth(right2) + 2 > width) {
419
+ const lowest = Math.min(...kept.map(([p]) => p));
420
+ kept = kept.filter(([p]) => p !== lowest);
421
+ }
422
+ return { left: render(), right: right2 };
423
+ }
424
+ function StatusLine({
425
+ state,
426
+ info,
427
+ width
428
+ }) {
429
+ const theme = useTheme();
430
+ const { left: left2, right: right2 } = statusSegments(theme, state, info, width - 2);
431
+ return /* @__PURE__ */ jsxs3(Box3, { width, justifyContent: "space-between", paddingX: 1, children: [
432
+ /* @__PURE__ */ jsx3(Text3, { wrap: "truncate-end", children: left2 }),
433
+ /* @__PURE__ */ jsx3(Text3, { wrap: "truncate-start", children: right2 })
434
+ ] });
435
+ }
436
+
437
+ // src/tui/components/Transcript.tsx
438
+ import { Box as Box4, Text as Text4 } from "ink";
439
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
440
+ function metaLine(meta) {
441
+ const parts = [];
442
+ if (meta.accountLabel ?? meta.accountId) {
443
+ parts.push([meta.provider, meta.accountLabel ?? meta.accountId].filter(Boolean).join(" \xB7 "));
444
+ }
445
+ if (meta.model) parts.push(meta.model);
446
+ if (meta.tokens !== void 0)
447
+ parts.push(`${meta.estimated ? "~" : ""}${formatTokens(meta.tokens)} tok`);
448
+ if (meta.latencyMs !== void 0) parts.push(`${(meta.latencyMs / 1e3).toFixed(1)}s`);
449
+ if (meta.failovers) parts.push(`${meta.failovers} failover${meta.failovers === 1 ? "" : "s"}`);
450
+ if (meta.cancelled) parts.push("cancelled");
451
+ return parts.join(" \xB7 ");
452
+ }
453
+ function Marker({ color, glyph }) {
454
+ return /* @__PURE__ */ jsx4(Box4, { width: 2, flexShrink: 0, children: /* @__PURE__ */ jsx4(Text4, { color, children: glyph }) });
455
+ }
456
+ function EntryView({ entry }) {
457
+ const theme = useTheme();
458
+ switch (entry.kind) {
459
+ case "banner":
460
+ return /* @__PURE__ */ jsx4(Box4, { marginBottom: 1, children: /* @__PURE__ */ jsx4(Text4, { children: entry.text }) });
461
+ case "user":
462
+ return /* @__PURE__ */ jsxs4(Box4, { marginBottom: 1, children: [
463
+ /* @__PURE__ */ jsx4(Marker, { color: theme.ink.accent, glyph: theme.glyph.prompt }),
464
+ /* @__PURE__ */ jsx4(Text4, { dimColor: theme.ink.dim, children: entry.text })
465
+ ] });
466
+ case "assistant":
467
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginBottom: 1, children: [
468
+ /* @__PURE__ */ jsxs4(Box4, { children: [
469
+ /* @__PURE__ */ jsx4(Marker, { color: theme.ink.accent, glyph: theme.glyph.dot }),
470
+ /* @__PURE__ */ jsx4(Text4, { children: entry.text || (entry.meta?.cancelled ? "(cancelled before any output)" : "") })
471
+ ] }),
472
+ entry.meta ? /* @__PURE__ */ jsx4(Box4, { paddingLeft: 2, children: /* @__PURE__ */ jsx4(Text4, { dimColor: theme.ink.dim, children: metaLine(entry.meta) }) }) : null
473
+ ] });
474
+ case "error":
475
+ return /* @__PURE__ */ jsxs4(Box4, { marginBottom: 1, children: [
476
+ /* @__PURE__ */ jsx4(Marker, { color: theme.ink.error, glyph: theme.glyph.fail }),
477
+ /* @__PURE__ */ jsx4(Text4, { color: theme.ink.error, children: entry.text })
478
+ ] });
479
+ default:
480
+ return /* @__PURE__ */ jsx4(Box4, { marginBottom: 1, paddingLeft: 2, children: /* @__PURE__ */ jsx4(Text4, { children: entry.text }) });
481
+ }
482
+ }
483
+ function StreamingView({ streaming, now }) {
484
+ const theme = useTheme();
485
+ const seconds = Math.max(0, (now - streaming.startedAt) / 1e3).toFixed(0);
486
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginBottom: 1, children: [
487
+ streaming.text ? /* @__PURE__ */ jsxs4(Box4, { children: [
488
+ /* @__PURE__ */ jsx4(Marker, { color: theme.ink.accent, glyph: theme.glyph.dot }),
489
+ /* @__PURE__ */ jsx4(Text4, { children: streaming.text })
490
+ ] }) : null,
491
+ /* @__PURE__ */ jsxs4(Box4, { paddingLeft: streaming.text ? 2 : 0, children: [
492
+ /* @__PURE__ */ jsx4(Spinner, {}),
493
+ /* @__PURE__ */ jsxs4(Text4, { dimColor: theme.ink.dim, children: [
494
+ " ",
495
+ streaming.text ? "streaming" : "thinking",
496
+ " ",
497
+ seconds,
498
+ "s \xB7 esc to cancel"
499
+ ] })
500
+ ] })
501
+ ] });
502
+ }
503
+
504
+ // src/tui/slash.ts
505
+ var SLASH_COMMANDS = [
506
+ { name: "model", args: "[id]", description: "show models or switch, e.g. /model davecode/auto" },
507
+ {
508
+ name: "connect",
509
+ description: "connect Claude Code, Codex or Antigravity, or sign in to one again"
510
+ },
511
+ { name: "route", description: "routes and their failover targets" },
512
+ { name: "accounts", description: "accounts with 5h quota usage" },
513
+ { name: "tasks", description: "the project task graph" },
514
+ { name: "status", description: "model, agents and whether they are signed in" },
515
+ { name: "login", description: "sign in to your DaveCode account in the browser" },
516
+ { name: "logout", description: "sign out of your DaveCode account" },
517
+ { name: "account", description: "the signed-in DaveCode account" },
518
+ { name: "clear", description: "clear the transcript and start a new conversation" },
519
+ { name: "help", description: "commands and keyboard shortcuts", aliases: ["?"] },
520
+ { name: "exit", description: "quit DaveCode", aliases: ["quit", "q"] }
521
+ ];
522
+ function parseSlash(input) {
523
+ const text = input.trim();
524
+ if (!text.startsWith("/") || text.startsWith("//")) return { kind: "none" };
525
+ const [head = "", ...rest] = text.slice(1).split(/\s+/);
526
+ const name = head.toLowerCase();
527
+ if (!name) return { kind: "unknown", name: "" };
528
+ const command = SLASH_COMMANDS.find((c) => c.name === name || c.aliases?.includes(name));
529
+ if (!command) return { kind: "unknown", name };
530
+ return { kind: "command", command, args: rest.join(" ").trim() };
531
+ }
532
+ function slashSuggestions(input) {
533
+ if (!input.startsWith("/") || /\s/.test(input)) return [];
534
+ const prefix = input.slice(1).toLowerCase();
535
+ return SLASH_COMMANDS.filter(
536
+ (c) => c.name.startsWith(prefix) || c.aliases?.some((a) => a.startsWith(prefix) && prefix)
537
+ );
538
+ }
539
+ var KEY_HINTS = [
540
+ ["Enter", "send"],
541
+ ["Shift+Enter, Ctrl+J or \\ Enter", "new line"],
542
+ ["\u2191 / \u2193", "move between lines, then through history"],
543
+ ["/", "command menu: \u2191 / \u2193 to choose, Enter to run, Tab to fill, Esc to close"],
544
+ ["Esc", "cancel the response in flight"],
545
+ ["Ctrl+U / Ctrl+K / Ctrl+W", "delete to line start / line end / previous word"],
546
+ ["Ctrl+C twice", "exit"]
547
+ ];
548
+
549
+ // src/tui/slash-actions.ts
550
+ function helpText(theme) {
551
+ const width = Math.max(...SLASH_COMMANDS.map((c) => c.name.length + (c.args?.length ?? 0))) + 4;
552
+ const commands = SLASH_COMMANDS.map(
553
+ (c) => `${theme.accent(padEnd(`/${c.name}${c.args ? ` ${c.args}` : ""}`, width))}${theme.dim(c.description)}`
554
+ );
555
+ const keyWidth = Math.max(...KEY_HINTS.map(([k]) => k.length)) + 2;
556
+ const keys = KEY_HINTS.map(([k, v]) => `${padEnd(k, keyWidth)}${theme.dim(v)}`);
557
+ return [theme.bold("Commands"), ...commands, "", theme.bold("Keys"), ...keys].join("\n");
558
+ }
559
+ async function modelsText(theme, backend, current) {
560
+ const models = await backend.models();
561
+ const ids = models.map((m) => m.id).sort((a, b) => {
562
+ const da = a.startsWith("davecode/") ? 0 : 1;
563
+ const db = b.startsWith("davecode/") ? 0 : 1;
564
+ return da - db || a.localeCompare(b);
565
+ });
566
+ if (ids.length === 0)
567
+ return theme.dim("No models yet. Add an account with `davecode accounts add`.");
568
+ const shown = ids.slice(0, 40);
569
+ const lines2 = shown.map(
570
+ (id) => id === current ? `${theme.accent(theme.glyph.dot)} ${theme.accent(id)}` : ` ${id}`
571
+ );
572
+ if (ids.length > shown.length) lines2.push(theme.dim(` \u2026 and ${ids.length - shown.length} more`));
573
+ lines2.push("", theme.dim("Switch with /model <id>"));
574
+ return lines2.join("\n");
575
+ }
576
+ async function routesText(theme, backend) {
577
+ const { defaultRoute, routes } = await backend.routes();
578
+ if (routes.length === 0) {
579
+ return [
580
+ `${theme.dim("No routes configured.")} davecode/${defaultRoute} uses every account with a default model.`,
581
+ theme.dim("Define routes under routing.routes in ~/.davecode/config.json.")
582
+ ].join("\n");
583
+ }
584
+ const lines2 = [];
585
+ for (const route of routes) {
586
+ const mark = route.name === defaultRoute ? theme.dim(" (default)") : "";
587
+ lines2.push(
588
+ `${theme.accent(`davecode/${route.name}`)}${mark}${route.description ? theme.dim(` ${route.description}`) : ""}`
589
+ );
590
+ route.targets.forEach((t, i) => {
591
+ lines2.push(
592
+ ` ${theme.dim(`${i + 1}.`)} ${t.provider}/${t.model}${t.accountId ? theme.dim(` @${t.accountId}`) : ""}`
593
+ );
594
+ });
595
+ }
596
+ return lines2.join("\n");
597
+ }
598
+ async function accountsText(theme, backend, columns) {
599
+ const [accounts, usage] = await Promise.all([backend.accounts(), backend.usage()]);
600
+ if (accounts.length === 0)
601
+ return theme.dim("No accounts yet. Add one with `davecode accounts add`.");
602
+ const byId = new Map(usage.map((u) => [u.accountId, u]));
603
+ return renderAccounts(
604
+ theme,
605
+ accounts.map((account) => ({ account, usage: byId.get(account.id) ?? emptyUsage(account.id) })),
606
+ columns
607
+ ).join("\n");
608
+ }
609
+ async function tasksText(theme, backend, columns) {
610
+ const { project, graph } = await backend.tasks();
611
+ if (!project)
612
+ return theme.dim("No project brain is being served. Start DaveCode inside a repository.");
613
+ if (graph.tasks.length === 0) {
614
+ return theme.dim(`${project.name}: no tasks yet. Add one with \`davecode tasks add\`.`);
615
+ }
616
+ return [
617
+ `${theme.bold(project.name)} ${summaryLine(theme, graph)}`,
618
+ "",
619
+ ...formatTaskTree(theme, graph, { width: columns })
620
+ ].join("\n");
621
+ }
622
+ async function statusText(theme, backend, model) {
623
+ const health = await backend.health();
624
+ if (!health) return theme.error("DaveCode is not answering. Restart it with davecode.");
625
+ const [accounts, runner] = await Promise.all([
626
+ backend.accounts(),
627
+ backend.runner().catch(() => void 0)
628
+ ]);
629
+ const state = (status) => status === "error" ? theme.error("needs sign-in \xB7 /connect") : status === "cooldown" ? theme.warn("cooling down") : status === "disabled" ? theme.dim("off") : theme.ok("ready");
630
+ const lines2 = [`${theme.dim("model ")} ${model}`];
631
+ if (accounts.length === 0) lines2.push(`${theme.dim("agents")} none yet \xB7 /connect`);
632
+ for (const [i, account] of accounts.entries()) {
633
+ lines2.push(
634
+ `${theme.dim(i === 0 ? "agents" : " ")} ${account.label} ${state(account.status)}`
635
+ );
636
+ }
637
+ lines2.push(`${theme.dim("runner")} ${runner ? runner.state : "unknown"}`);
638
+ return lines2.join("\n");
639
+ }
640
+
641
+ // src/tui/ChatApp.tsx
642
+ import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
643
+ var PLACEHOLDER = "Ask anything, or type / for commands";
644
+ var EXIT_WINDOW_MS = 2e3;
645
+ function errorText(err) {
646
+ return err instanceof Error ? err.message : String(err);
647
+ }
648
+ function Chat({
649
+ backend,
650
+ model,
651
+ version,
652
+ project,
653
+ account,
654
+ connect,
655
+ pollMs = 1e4,
656
+ now = Date.now
657
+ }) {
658
+ const theme = useTheme();
659
+ const { exit } = useApp();
660
+ const { write } = useStdout();
661
+ const { columns } = useWindowSize();
662
+ const width = Math.max(20, columns);
663
+ const widthRef = useRef(width);
664
+ widthRef.current = width;
665
+ const [state, dispatch] = useReducer(chatReducer, model, initialChatState);
666
+ const [editor, setEditor] = useState(emptyEditor);
667
+ const [historyIndex, setHistoryIndex] = useState();
668
+ const [notice, setNotice] = useState();
669
+ const [clock, setClock] = useState(now());
670
+ const [slashIndex, setSlashIndex] = useState(0);
671
+ const [picker, setPicker] = useState();
672
+ const abortRef = useRef(void 0);
673
+ const exitArmedAt = useRef(void 0);
674
+ const labels = useRef(/* @__PURE__ */ new Map());
675
+ const stateRef = useRef(state);
676
+ stateRef.current = state;
677
+ useEffect(() => {
678
+ dispatch({
679
+ type: "banner",
680
+ text: brandBanner(
681
+ theme,
682
+ [
683
+ `${theme.bold("DaveCode")} ${theme.dim(`v${version}`)}`,
684
+ ...project ? [theme.dim(project)] : []
685
+ ],
686
+ widthRef.current
687
+ ).join("\n")
688
+ });
689
+ let alive = true;
690
+ const check = async () => {
691
+ const health = await backend.health();
692
+ if (alive) dispatch({ type: "connection", connection: health ? "online" : "offline" });
693
+ };
694
+ void check();
695
+ backend.accounts().then((accounts) => {
696
+ for (const a of accounts) labels.current.set(a.id, a.label);
697
+ }).catch(() => {
698
+ });
699
+ const timer = setInterval(() => void check(), pollMs);
700
+ return () => {
701
+ alive = false;
702
+ clearInterval(timer);
703
+ abortRef.current?.abort();
704
+ };
705
+ }, [backend, pollMs, project, theme, version]);
706
+ useEffect(() => {
707
+ if (!state.streaming) return;
708
+ const timer = setInterval(() => setClock(now()), 250);
709
+ return () => clearInterval(timer);
710
+ }, [state.streaming, now]);
711
+ useEffect(() => {
712
+ if (!notice) return;
713
+ const timer = setTimeout(() => setNotice(void 0), EXIT_WINDOW_MS);
714
+ return () => clearTimeout(timer);
715
+ }, [notice]);
716
+ const refreshQuota = useCallback(
717
+ async (accountId) => {
718
+ try {
719
+ const usage = (await backend.usage()).find((u) => u.accountId === accountId);
720
+ const window = usage?.windows["5h"];
721
+ const limited = window && (window.tokenLimit !== void 0 || window.requestLimit !== void 0);
722
+ dispatch({
723
+ type: "quota",
724
+ accountId,
725
+ utilization: limited ? window.utilization : void 0
726
+ });
727
+ } catch {
728
+ }
729
+ },
730
+ [backend]
731
+ );
732
+ const send = useCallback(
733
+ async (text2) => {
734
+ dispatch({ type: "user", text: text2 });
735
+ const conversation = [
736
+ ...stateRef.current.conversation,
737
+ { role: "user", content: text2 }
738
+ ];
739
+ const controller = new AbortController();
740
+ abortRef.current = controller;
741
+ dispatch({ type: "stream-start", now: now() });
742
+ let usage;
743
+ let accountId;
744
+ try {
745
+ const { meta, chunks } = await backend.stream(
746
+ { model: stateRef.current.model, messages: conversation },
747
+ controller.signal
748
+ );
749
+ accountId = meta.accountId;
750
+ dispatch({
751
+ type: "stream-meta",
752
+ failovers: meta.failovers,
753
+ ...meta.accountId ? { accountId: meta.accountId } : {},
754
+ ...meta.provider ? { provider: meta.provider } : {},
755
+ ...meta.accountId && labels.current.has(meta.accountId) ? { accountLabel: labels.current.get(meta.accountId) } : {}
756
+ });
757
+ dispatch({ type: "connection", connection: "online" });
758
+ for await (const chunk of chunks) {
759
+ if (controller.signal.aborted) break;
760
+ const delta = chunk.choices[0]?.delta?.content;
761
+ if (typeof delta === "string" && delta) dispatch({ type: "stream-delta", text: delta });
762
+ if (chunk.usage) usage = chunk.usage;
763
+ }
764
+ dispatch({
765
+ type: "stream-end",
766
+ now: now(),
767
+ ...usage ? { usage } : {},
768
+ ...controller.signal.aborted ? { cancelled: true } : {}
769
+ });
770
+ } catch (err) {
771
+ if (controller.signal.aborted) {
772
+ dispatch({ type: "stream-end", now: now(), cancelled: true });
773
+ } else {
774
+ const status = err.status;
775
+ if (status === 0) dispatch({ type: "connection", connection: "offline" });
776
+ let text3 = errorText(err);
777
+ if (connect)
778
+ text3 = text3.replace(/Run: davecode accounts login \S+/, "Type /connect to sign in.");
779
+ dispatch({ type: "stream-end", now: now(), error: text3 });
780
+ }
781
+ } finally {
782
+ if (abortRef.current === controller) abortRef.current = void 0;
783
+ }
784
+ if (accountId) void refreshQuota(accountId);
785
+ },
786
+ [backend, connect, now, refreshQuota]
787
+ );
788
+ const runSlash = useCallback(
789
+ async (input) => {
790
+ const parsed = parseSlash(input);
791
+ if (parsed.kind === "unknown") {
792
+ dispatch({
793
+ type: "error",
794
+ text: `Unknown command /${parsed.name}. Type /help for the list.`
795
+ });
796
+ return;
797
+ }
798
+ if (parsed.kind !== "command") return;
799
+ const { command, args } = parsed;
800
+ try {
801
+ switch (command.name) {
802
+ case "help":
803
+ dispatch({ type: "info", text: helpText(theme) });
804
+ break;
805
+ case "exit":
806
+ abortRef.current?.abort();
807
+ exit();
808
+ break;
809
+ case "clear":
810
+ abortRef.current?.abort();
811
+ write("\x1B[2J\x1B[3J\x1B[H");
812
+ dispatch({ type: "clear" });
813
+ break;
814
+ case "model": {
815
+ const choose = (id) => {
816
+ dispatch({ type: "set-model", model: id });
817
+ dispatch({ type: "info", text: `Model set to ${theme.accent(id)}` });
818
+ };
819
+ if (args) {
820
+ choose(args);
821
+ break;
822
+ }
823
+ const ids = (await backend.models()).map((m) => m.id).sort(
824
+ (a, b) => Number(!a.startsWith("davecode/")) - Number(!b.startsWith("davecode/")) || a.localeCompare(b)
825
+ );
826
+ if (ids.length === 0) {
827
+ dispatch({
828
+ type: "info",
829
+ text: await modelsText(theme, backend, stateRef.current.model)
830
+ });
831
+ break;
832
+ }
833
+ const current = stateRef.current.model;
834
+ setPicker({
835
+ title: "Choose a model",
836
+ options: ids.map((id) => ({
837
+ value: id,
838
+ label: id,
839
+ ...id === current ? { hint: "(current)" } : {}
840
+ })),
841
+ index: Math.max(0, ids.indexOf(current)),
842
+ pick: choose
843
+ });
844
+ break;
845
+ }
846
+ case "connect": {
847
+ if (!connect) {
848
+ dispatch({ type: "info", text: "Connect agents with davecode accounts add." });
849
+ break;
850
+ }
851
+ setPicker({
852
+ title: "Connect an agent",
853
+ options: await connect.agents(),
854
+ index: 0,
855
+ pick: (provider) => {
856
+ abortRef.current?.abort();
857
+ connect.choose(provider);
858
+ exit();
859
+ }
860
+ });
861
+ break;
862
+ }
863
+ case "route":
864
+ dispatch({ type: "info", text: await routesText(theme, backend) });
865
+ break;
866
+ case "accounts": {
867
+ const accounts = await backend.accounts();
868
+ for (const a of accounts) labels.current.set(a.id, a.label);
869
+ dispatch({ type: "info", text: await accountsText(theme, backend, width - 4) });
870
+ break;
871
+ }
872
+ case "tasks":
873
+ dispatch({ type: "info", text: await tasksText(theme, backend, width - 4) });
874
+ break;
875
+ case "status":
876
+ dispatch({
877
+ type: "info",
878
+ text: await statusText(theme, backend, stateRef.current.model)
879
+ });
880
+ break;
881
+ case "login":
882
+ case "logout":
883
+ case "account": {
884
+ if (!account) {
885
+ dispatch({
886
+ type: "info",
887
+ text: "DaveCode accounts are not available in this build."
888
+ });
889
+ break;
890
+ }
891
+ const text2 = command.name === "login" ? await account.login((note) => dispatch({ type: "info", text: theme.dim(note) })) : command.name === "logout" ? await account.logout() : await account.status();
892
+ dispatch({ type: "info", text: text2 });
893
+ break;
894
+ }
895
+ }
896
+ } catch (err) {
897
+ dispatch({ type: "error", text: `/${command.name} failed: ${errorText(err)}` });
898
+ }
899
+ },
900
+ [account, backend, connect, exit, theme, width, write]
901
+ );
902
+ const submit = useCallback(() => {
903
+ const text2 = textOf(editor).trim();
904
+ if (!text2) return;
905
+ if (parseSlash(text2).kind !== "none") {
906
+ setEditor(emptyEditor);
907
+ setHistoryIndex(void 0);
908
+ void runSlash(text2);
909
+ return;
910
+ }
911
+ if (stateRef.current.streaming) {
912
+ setNotice("Wait for the reply, or press Esc to cancel it");
913
+ return;
914
+ }
915
+ setEditor(emptyEditor);
916
+ setHistoryIndex(void 0);
917
+ void send(text2);
918
+ }, [editor, runSlash, send]);
919
+ const recall = (direction) => {
920
+ const { history } = stateRef.current;
921
+ if (history.length === 0) return;
922
+ const current = historyIndex ?? history.length;
923
+ const next = Math.min(history.length, Math.max(0, current + direction));
924
+ setHistoryIndex(next === history.length ? void 0 : next);
925
+ setEditor(next === history.length ? emptyEditor : fromText(history[next]));
926
+ };
927
+ useInput((input, key) => {
928
+ if (key.ctrl && input === "c") {
929
+ const armed = exitArmedAt.current !== void 0 && now() - exitArmedAt.current < EXIT_WINDOW_MS;
930
+ if (armed) {
931
+ abortRef.current?.abort();
932
+ exit();
933
+ return;
934
+ }
935
+ exitArmedAt.current = now();
936
+ if (stateRef.current.streaming) abortRef.current?.abort();
937
+ else if (editor.chars.length > 0) setEditor(emptyEditor);
938
+ setNotice("Press Ctrl+C again to exit");
939
+ return;
940
+ }
941
+ exitArmedAt.current = void 0;
942
+ if (picker) {
943
+ const count = picker.options.length;
944
+ const move = (index) => setPicker({ ...picker, index });
945
+ if (key.escape) return setPicker(void 0);
946
+ if (key.upArrow) return move((picker.index - 1 + count) % count);
947
+ if (key.downArrow) return move((picker.index + 1) % count);
948
+ const digit = count <= 9 && /^[1-9]$/.test(input) ? Number(input) - 1 : void 0;
949
+ if (key.return || digit !== void 0 && digit < count) {
950
+ const option = picker.options[digit ?? picker.index];
951
+ setPicker(void 0);
952
+ if (option) picker.pick(option.value);
953
+ }
954
+ return;
955
+ }
956
+ const menu = slashSuggestions(textOf(editor));
957
+ if (menu.length > 0) {
958
+ const selected = menu[Math.min(slashIndex, menu.length - 1)];
959
+ if (key.upArrow) return setSlashIndex((i) => (i - 1 + menu.length) % menu.length);
960
+ if (key.downArrow) return setSlashIndex((i) => (i + 1) % menu.length);
961
+ if (key.escape) return setEditor(emptyEditor);
962
+ if (key.tab) {
963
+ setEditor(fromText(`/${selected.name}${selected.args ? " " : ""}`));
964
+ return;
965
+ }
966
+ if (key.return && !key.shift && !key.meta) {
967
+ setEditor(emptyEditor);
968
+ setHistoryIndex(void 0);
969
+ void runSlash(`/${selected.name}`);
970
+ return;
971
+ }
972
+ }
973
+ if (key.escape) {
974
+ if (stateRef.current.streaming) abortRef.current?.abort();
975
+ return;
976
+ }
977
+ if (input === "\n" && !key.return || key.ctrl && input === "j") {
978
+ setEditor((e) => insert(e, "\n"));
979
+ return;
980
+ }
981
+ if (key.return) {
982
+ if (key.shift || key.meta) setEditor((e) => insert(e, "\n"));
983
+ else if (continuesLine(editor)) setEditor((e) => newlineAfterBackslash(e));
984
+ else submit();
985
+ return;
986
+ }
987
+ if (key.tab) return;
988
+ if (key.upArrow) {
989
+ const moved = up(editor);
990
+ if (moved) setEditor(moved);
991
+ else recall(-1);
992
+ return;
993
+ }
994
+ if (key.downArrow) {
995
+ const moved = down(editor);
996
+ if (moved) setEditor(moved);
997
+ else if (historyIndex !== void 0) recall(1);
998
+ return;
999
+ }
1000
+ if (key.leftArrow) return setEditor(left);
1001
+ if (key.rightArrow) return setEditor(right);
1002
+ if (key.home || key.ctrl && input === "a") return setEditor(lineStart);
1003
+ if (key.end || key.ctrl && input === "e") return setEditor(lineEnd);
1004
+ if (key.backspace) return setEditor(backspace);
1005
+ if (key.delete) return setEditor(deleteForward);
1006
+ if (key.ctrl && input === "u") return setEditor(killToLineStart);
1007
+ if (key.ctrl && input === "k") return setEditor(killToLineEnd);
1008
+ if (key.ctrl && input === "w") return setEditor(deleteWordBack);
1009
+ if (key.ctrl && input === "l") {
1010
+ write("\x1B[2J\x1B[H");
1011
+ return;
1012
+ }
1013
+ if (key.ctrl || key.meta || !input) return;
1014
+ setEditor((e) => insert(e, input));
1015
+ });
1016
+ usePaste((text2) => {
1017
+ setEditor((e) => insert(e, text2));
1018
+ });
1019
+ const text = textOf(editor);
1020
+ const suggestions = slashSuggestions(text);
1021
+ useEffect(() => setSlashIndex(0), [text]);
1022
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", width, children: [
1023
+ /* @__PURE__ */ jsx5(Static, { items: state.entries, children: (entry) => /* @__PURE__ */ jsx5(EntryView, { entry }, entry.id) }, state.epoch),
1024
+ state.streaming ? /* @__PURE__ */ jsx5(StreamingView, { streaming: state.streaming, now: clock }) : null,
1025
+ picker ? /* @__PURE__ */ jsx5(Picker, { title: picker.title, options: picker.options, index: picker.index }) : /* @__PURE__ */ jsxs5(Fragment, { children: [
1026
+ /* @__PURE__ */ jsx5(InputBox, { editor, width, placeholder: PLACEHOLDER }),
1027
+ /* @__PURE__ */ jsx5(
1028
+ SlashHints,
1029
+ {
1030
+ commands: suggestions,
1031
+ width,
1032
+ selected: Math.min(slashIndex, Math.max(0, suggestions.length - 1))
1033
+ }
1034
+ )
1035
+ ] }),
1036
+ !picker && suggestions.length === 0 ? /* @__PURE__ */ jsx5(StatusLine, { state, width, info: notice ? { notice } : {} }) : null
1037
+ ] });
1038
+ }
1039
+ function ChatApp(props) {
1040
+ return /* @__PURE__ */ jsx5(ThemeProvider, { theme: props.theme, children: /* @__PURE__ */ jsx5(Chat, { ...props }) });
1041
+ }
1042
+ export {
1043
+ ChatApp
1044
+ };
1045
+ //# sourceMappingURL=ChatApp-BO7QMBWD.js.map