@uiid/bertrand 0.10.1 → 0.11.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.
- package/README.md +11 -0
- package/dist/bertrand.js +126 -2
- package/dist/run-screen.js +460 -741
- package/package.json +1 -1
package/dist/run-screen.js
CHANGED
|
@@ -19,8 +19,8 @@ import { writeFileSync } from "fs";
|
|
|
19
19
|
import { render } from "@orchetron/storm";
|
|
20
20
|
|
|
21
21
|
// src/tui/screens/launch/index.tsx
|
|
22
|
-
import {
|
|
23
|
-
import { Box as
|
|
22
|
+
import { useCallback as useCallback2 } from "react";
|
|
23
|
+
import { Box as Box5, useTui as useTui2 } from "@orchetron/storm";
|
|
24
24
|
|
|
25
25
|
// src/tui/components/app-details.tsx
|
|
26
26
|
import { Box, Text } from "@orchetron/storm";
|
|
@@ -69,14 +69,230 @@ function Logo() {
|
|
|
69
69
|
}, i, false, undefined, this))
|
|
70
70
|
}, undefined, false, undefined, this);
|
|
71
71
|
}
|
|
72
|
-
// src/tui/components/
|
|
73
|
-
import {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
// src/tui/components/picker.tsx
|
|
73
|
+
import { useMemo, useState } from "react";
|
|
74
|
+
import { Box as Box3, Text as Text3, TextInput, useInput } from "@orchetron/storm";
|
|
75
|
+
import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
|
|
76
|
+
var NEW_KEY = "__new__";
|
|
77
|
+
function Picker(props) {
|
|
78
|
+
const {
|
|
79
|
+
items,
|
|
80
|
+
isFocused,
|
|
81
|
+
placeholder,
|
|
82
|
+
allowCreate = true,
|
|
83
|
+
emptyHint,
|
|
84
|
+
maxVisible = 8
|
|
85
|
+
} = props;
|
|
86
|
+
const [filter, setFilter] = useState("");
|
|
87
|
+
const [cursor, setCursor] = useState(0);
|
|
88
|
+
const filtered = useMemo(() => {
|
|
89
|
+
const q = filter.trim().toLowerCase();
|
|
90
|
+
if (!q)
|
|
91
|
+
return items;
|
|
92
|
+
return items.filter((it) => it.label.toLowerCase().includes(q));
|
|
93
|
+
}, [filter, items]);
|
|
94
|
+
const exactMatch = useMemo(() => filtered.some((it) => it.label.toLowerCase() === filter.trim().toLowerCase()), [filter, filtered]);
|
|
95
|
+
const showCreate = allowCreate && filter.trim().length > 0 && !exactMatch;
|
|
96
|
+
const visibleRows = useMemo(() => showCreate ? [...filtered, { value: NEW_KEY }] : filtered, [filtered, showCreate]);
|
|
97
|
+
if (cursor >= visibleRows.length) {
|
|
98
|
+
setTimeout(() => setCursor(Math.max(0, visibleRows.length - 1)), 0);
|
|
99
|
+
}
|
|
100
|
+
useInput((e) => {
|
|
101
|
+
if (!isFocused)
|
|
102
|
+
return;
|
|
103
|
+
if (e.key === "escape" && filter.length > 0) {
|
|
104
|
+
setFilter("");
|
|
105
|
+
setCursor(0);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (visibleRows.length === 0)
|
|
109
|
+
return;
|
|
110
|
+
if (e.key === "up") {
|
|
111
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
112
|
+
} else if (e.key === "down") {
|
|
113
|
+
setCursor((c) => Math.min(visibleRows.length - 1, c + 1));
|
|
114
|
+
} else if (e.key === "tab" && props.mode === "multi") {
|
|
115
|
+
props.onDone();
|
|
116
|
+
}
|
|
117
|
+
}, { isActive: isFocused });
|
|
118
|
+
const submitCursor = () => {
|
|
119
|
+
const row = visibleRows[cursor];
|
|
120
|
+
const value = row && row.value === NEW_KEY ? filter.trim() : row?.value ?? "";
|
|
121
|
+
if (!value)
|
|
122
|
+
return;
|
|
123
|
+
if (props.mode === "single") {
|
|
124
|
+
props.onSubmit(value);
|
|
125
|
+
setFilter("");
|
|
126
|
+
setCursor(0);
|
|
127
|
+
} else {
|
|
128
|
+
props.onToggle(value);
|
|
129
|
+
setFilter("");
|
|
130
|
+
setCursor(0);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
const handleSubmit = (text) => {
|
|
134
|
+
if (visibleRows.length === 0) {
|
|
135
|
+
if (allowCreate && text.trim() && props.mode === "single") {
|
|
136
|
+
props.onSubmit(text.trim());
|
|
137
|
+
setFilter("");
|
|
138
|
+
}
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
submitCursor();
|
|
142
|
+
};
|
|
143
|
+
const visibleStart = Math.max(0, Math.min(cursor - Math.floor(maxVisible / 2), visibleRows.length - maxVisible));
|
|
144
|
+
const visibleEnd = Math.min(visibleRows.length, visibleStart + maxVisible);
|
|
145
|
+
const slice = visibleRows.slice(visibleStart, visibleEnd);
|
|
146
|
+
const selectedSet = props.mode === "multi" ? new Set(props.selected) : new Set;
|
|
147
|
+
return /* @__PURE__ */ jsxDEV3(Box3, {
|
|
148
|
+
flexDirection: "column",
|
|
149
|
+
gap: 0,
|
|
150
|
+
width: "100%",
|
|
151
|
+
children: [
|
|
152
|
+
props.mode === "multi" && props.selected.length > 0 && /* @__PURE__ */ jsxDEV3(Box3, {
|
|
153
|
+
flexDirection: "row",
|
|
154
|
+
gap: 1,
|
|
155
|
+
children: [
|
|
156
|
+
/* @__PURE__ */ jsxDEV3(Text3, {
|
|
157
|
+
dim: true,
|
|
158
|
+
children: "Selected:"
|
|
159
|
+
}, undefined, false, undefined, this),
|
|
160
|
+
props.selected.map((v) => {
|
|
161
|
+
const item = items.find((i) => i.value === v);
|
|
162
|
+
return /* @__PURE__ */ jsxDEV3(Text3, {
|
|
163
|
+
color: item?.color ?? "cyan",
|
|
164
|
+
children: item?.label ?? v
|
|
165
|
+
}, v, false, undefined, this);
|
|
166
|
+
})
|
|
167
|
+
]
|
|
168
|
+
}, undefined, true, undefined, this),
|
|
169
|
+
/* @__PURE__ */ jsxDEV3(Box3, {
|
|
170
|
+
borderStyle: "round",
|
|
171
|
+
borderColor: isFocused ? "green" : undefined,
|
|
172
|
+
borderDimColor: !isFocused,
|
|
173
|
+
paddingX: 1,
|
|
174
|
+
children: /* @__PURE__ */ jsxDEV3(TextInput, {
|
|
175
|
+
value: filter,
|
|
176
|
+
onChange: setFilter,
|
|
177
|
+
onSubmit: handleSubmit,
|
|
178
|
+
placeholder,
|
|
179
|
+
color: "green",
|
|
180
|
+
placeholderColor: "gray",
|
|
181
|
+
isFocused
|
|
182
|
+
}, undefined, false, undefined, this)
|
|
183
|
+
}, undefined, false, undefined, this),
|
|
184
|
+
/* @__PURE__ */ jsxDEV3(Box3, {
|
|
185
|
+
flexDirection: "column",
|
|
186
|
+
borderStyle: "round",
|
|
187
|
+
borderDimColor: true,
|
|
188
|
+
paddingX: 1,
|
|
189
|
+
children: [
|
|
190
|
+
visibleStart > 0 && /* @__PURE__ */ jsxDEV3(Text3, {
|
|
191
|
+
dim: true,
|
|
192
|
+
children: [
|
|
193
|
+
"\u25B2 ",
|
|
194
|
+
visibleStart,
|
|
195
|
+
" more above"
|
|
196
|
+
]
|
|
197
|
+
}, undefined, true, undefined, this),
|
|
198
|
+
slice.length === 0 && emptyHint && /* @__PURE__ */ jsxDEV3(Text3, {
|
|
199
|
+
dim: true,
|
|
200
|
+
children: emptyHint
|
|
201
|
+
}, undefined, false, undefined, this),
|
|
202
|
+
slice.map((row, i) => {
|
|
203
|
+
const idx = visibleStart + i;
|
|
204
|
+
const isCursor = idx === cursor && isFocused;
|
|
205
|
+
const isNew = row.value === NEW_KEY;
|
|
206
|
+
const showDivider = isNew && filtered.length > 0 && i > 0;
|
|
207
|
+
if (isNew) {
|
|
208
|
+
return /* @__PURE__ */ jsxDEV3(Box3, {
|
|
209
|
+
flexDirection: "column",
|
|
210
|
+
children: [
|
|
211
|
+
showDivider && /* @__PURE__ */ jsxDEV3(Text3, {
|
|
212
|
+
dim: true,
|
|
213
|
+
children: "\u2500".repeat(Math.min(20, filter.length + 12))
|
|
214
|
+
}, undefined, false, undefined, this),
|
|
215
|
+
/* @__PURE__ */ jsxDEV3(Box3, {
|
|
216
|
+
flexDirection: "row",
|
|
217
|
+
gap: 1,
|
|
218
|
+
backgroundColor: isCursor ? "green" : undefined,
|
|
219
|
+
children: /* @__PURE__ */ jsxDEV3(Text3, {
|
|
220
|
+
color: isCursor ? "black" : "cyan",
|
|
221
|
+
bold: true,
|
|
222
|
+
children: [
|
|
223
|
+
"\u271A create \u201C",
|
|
224
|
+
filter.trim(),
|
|
225
|
+
"\u201D"
|
|
226
|
+
]
|
|
227
|
+
}, undefined, true, undefined, this)
|
|
228
|
+
}, undefined, false, undefined, this)
|
|
229
|
+
]
|
|
230
|
+
}, "__new__", true, undefined, this);
|
|
231
|
+
}
|
|
232
|
+
const item = row;
|
|
233
|
+
const isSelected = selectedSet.has(item.value);
|
|
234
|
+
const marker = props.mode === "multi" ? isSelected ? "\u2713 " : " " : "";
|
|
235
|
+
return /* @__PURE__ */ jsxDEV3(Box3, {
|
|
236
|
+
flexDirection: "row",
|
|
237
|
+
justifyContent: "space-between",
|
|
238
|
+
gap: 1,
|
|
239
|
+
backgroundColor: isCursor ? "green" : undefined,
|
|
240
|
+
children: [
|
|
241
|
+
/* @__PURE__ */ jsxDEV3(Text3, {
|
|
242
|
+
color: isCursor ? "black" : item.color ?? undefined,
|
|
243
|
+
bold: isCursor,
|
|
244
|
+
children: [
|
|
245
|
+
marker,
|
|
246
|
+
item.label
|
|
247
|
+
]
|
|
248
|
+
}, undefined, true, undefined, this),
|
|
249
|
+
item.meta && /* @__PURE__ */ jsxDEV3(Box3, {
|
|
250
|
+
paddingX: 1,
|
|
251
|
+
backgroundColor: isCursor ? "black" : "#3a3a3a",
|
|
252
|
+
children: /* @__PURE__ */ jsxDEV3(Text3, {
|
|
253
|
+
color: isCursor ? "green" : "white",
|
|
254
|
+
bold: true,
|
|
255
|
+
children: item.meta
|
|
256
|
+
}, undefined, false, undefined, this)
|
|
257
|
+
}, undefined, false, undefined, this)
|
|
258
|
+
]
|
|
259
|
+
}, item.value, true, undefined, this);
|
|
260
|
+
}),
|
|
261
|
+
visibleEnd < visibleRows.length && /* @__PURE__ */ jsxDEV3(Text3, {
|
|
262
|
+
dim: true,
|
|
263
|
+
children: [
|
|
264
|
+
"\u25BC ",
|
|
265
|
+
visibleRows.length - visibleEnd,
|
|
266
|
+
" more below"
|
|
267
|
+
]
|
|
268
|
+
}, undefined, true, undefined, this)
|
|
269
|
+
]
|
|
270
|
+
}, undefined, true, undefined, this)
|
|
271
|
+
]
|
|
272
|
+
}, undefined, true, undefined, this);
|
|
273
|
+
}
|
|
274
|
+
// src/tui/components/status-dot.tsx
|
|
275
|
+
import { Text as Text4 } from "@orchetron/storm";
|
|
276
|
+
import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
|
|
277
|
+
var STATUS_COLORS = {
|
|
278
|
+
active: "orange",
|
|
279
|
+
waiting: "red",
|
|
280
|
+
paused: "gold",
|
|
281
|
+
archived: "purple"
|
|
282
|
+
};
|
|
283
|
+
function StatusDot({ status }) {
|
|
284
|
+
const color = STATUS_COLORS[status] ?? "#6B7280";
|
|
285
|
+
return /* @__PURE__ */ jsxDEV4(Text4, {
|
|
286
|
+
color,
|
|
287
|
+
children: "\u25CF"
|
|
288
|
+
}, undefined, false, undefined, this);
|
|
289
|
+
}
|
|
290
|
+
// src/tui/screens/launch/create-screen.tsx
|
|
291
|
+
import { useState as useState2, useMemo as useMemo2, useCallback, useEffect, useRef } from "react";
|
|
292
|
+
import { Box as Box4, Text as Text5, TextInput as TextInput2, useInput as useInput2, useTui } from "@orchetron/storm";
|
|
77
293
|
|
|
78
|
-
// src/db/queries/
|
|
79
|
-
import { eq,
|
|
294
|
+
// src/db/queries/groups.ts
|
|
295
|
+
import { eq, like, or, isNull } from "drizzle-orm";
|
|
80
296
|
|
|
81
297
|
// src/db/client.ts
|
|
82
298
|
import { Database } from "bun:sqlite";
|
|
@@ -225,17 +441,20 @@ function getDb() {
|
|
|
225
441
|
|
|
226
442
|
// src/lib/id.ts
|
|
227
443
|
import { nanoid } from "nanoid";
|
|
228
|
-
|
|
229
|
-
|
|
444
|
+
|
|
445
|
+
// src/db/queries/groups.ts
|
|
446
|
+
function getAllGroups() {
|
|
447
|
+
return getDb().select().from(groups).all();
|
|
230
448
|
}
|
|
231
449
|
|
|
232
450
|
// src/db/queries/sessions.ts
|
|
451
|
+
import { eq as eq2, and, inArray, sql as sql2 } from "drizzle-orm";
|
|
233
452
|
function getSession(id) {
|
|
234
|
-
return getDb().select().from(sessions).where(
|
|
453
|
+
return getDb().select().from(sessions).where(eq2(sessions.id, id)).get();
|
|
235
454
|
}
|
|
236
455
|
function getAllSessions(opts) {
|
|
237
456
|
const db = getDb();
|
|
238
|
-
const query = db.select({ session: sessions, groupPath: groups.path }).from(sessions).innerJoin(groups,
|
|
457
|
+
const query = db.select({ session: sessions, groupPath: groups.path }).from(sessions).innerJoin(groups, eq2(sessions.groupId, groups.id));
|
|
239
458
|
if (opts?.excludeArchived) {
|
|
240
459
|
return query.where(inArray(sessions.status, [
|
|
241
460
|
"active",
|
|
@@ -245,739 +464,198 @@ function getAllSessions(opts) {
|
|
|
245
464
|
}
|
|
246
465
|
return query.all();
|
|
247
466
|
}
|
|
248
|
-
function updateSessionStatus(id, status) {
|
|
249
|
-
return getDb().update(sessions).set({ status, updatedAt: sql2`(datetime('now'))` }).where(eq(sessions.id, id)).returning().get();
|
|
250
|
-
}
|
|
251
|
-
function renameSession(id, slug, name) {
|
|
252
|
-
return getDb().update(sessions).set({ slug, name: name ?? slug, updatedAt: sql2`(datetime('now'))` }).where(eq(sessions.id, id)).returning().get();
|
|
253
|
-
}
|
|
254
|
-
function moveSession(id, groupId) {
|
|
255
|
-
return getDb().update(sessions).set({ groupId, updatedAt: sql2`(datetime('now'))` }).where(eq(sessions.id, id)).returning().get();
|
|
256
|
-
}
|
|
257
|
-
function deleteSession(id) {
|
|
258
|
-
return getDb().delete(sessions).where(eq(sessions.id, id)).run();
|
|
259
|
-
}
|
|
260
467
|
|
|
261
|
-
// src/tui/
|
|
262
|
-
function useLaunchSessions() {
|
|
263
|
-
const [cursor, setCursor] = useState(0);
|
|
264
|
-
const [showArchived, setShowArchived] = useState(false);
|
|
265
|
-
const [refreshKey, setRefreshKey] = useState(0);
|
|
266
|
-
const refresh = useCallback(() => setRefreshKey((k) => k + 1), []);
|
|
267
|
-
const sessionRows = getAllSessions(showArchived ? undefined : { excludeArchived: true });
|
|
268
|
-
const selected = sessionRows[cursor];
|
|
269
|
-
const toggleArchived = useCallback(() => {
|
|
270
|
-
setShowArchived((s) => !s);
|
|
271
|
-
setCursor(0);
|
|
272
|
-
}, []);
|
|
273
|
-
return {
|
|
274
|
-
sessionRows,
|
|
275
|
-
selected,
|
|
276
|
-
cursor,
|
|
277
|
-
setCursor,
|
|
278
|
-
showArchived,
|
|
279
|
-
toggleArchived,
|
|
280
|
-
refresh
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// src/tui/components/no-sessions.tsx
|
|
285
|
-
import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
|
|
286
|
-
// src/tui/components/session-row.tsx
|
|
287
|
-
import { Box as Box4, Text as Text5 } from "@orchetron/storm";
|
|
288
|
-
|
|
289
|
-
// src/lib/format.ts
|
|
290
|
-
var SECOND = 1000;
|
|
291
|
-
var MINUTE = 60 * SECOND;
|
|
292
|
-
var HOUR = 60 * MINUTE;
|
|
293
|
-
var DAY = 24 * HOUR;
|
|
294
|
-
function formatDuration(ms) {
|
|
295
|
-
if (ms < MINUTE)
|
|
296
|
-
return `${Math.round(ms / SECOND)}s`;
|
|
297
|
-
const days = Math.floor(ms / DAY);
|
|
298
|
-
const hours = Math.floor(ms % DAY / HOUR);
|
|
299
|
-
const minutes = Math.floor(ms % HOUR / MINUTE);
|
|
300
|
-
if (days > 0)
|
|
301
|
-
return hours > 0 ? `${days}d ${hours}h` : `${days}d`;
|
|
302
|
-
if (hours > 0)
|
|
303
|
-
return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
|
|
304
|
-
return `${minutes}m`;
|
|
305
|
-
}
|
|
306
|
-
function formatAgo(isoOrDate) {
|
|
307
|
-
const date = typeof isoOrDate === "string" ? new Date(isoOrDate) : isoOrDate;
|
|
308
|
-
const ms = Date.now() - date.getTime();
|
|
309
|
-
if (ms < MINUTE)
|
|
310
|
-
return "just now";
|
|
311
|
-
if (ms < HOUR)
|
|
312
|
-
return `${Math.floor(ms / MINUTE)}m ago`;
|
|
313
|
-
if (ms < DAY)
|
|
314
|
-
return `${Math.floor(ms / HOUR)}h ago`;
|
|
315
|
-
if (ms < 2 * DAY)
|
|
316
|
-
return "yesterday";
|
|
317
|
-
if (ms < 7 * DAY)
|
|
318
|
-
return `${Math.floor(ms / DAY)}d ago`;
|
|
319
|
-
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// src/tui/components/status-dot.tsx
|
|
323
|
-
import { Text as Text4 } from "@orchetron/storm";
|
|
324
|
-
import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
|
|
325
|
-
var STATUS_COLORS = {
|
|
326
|
-
active: "orange",
|
|
327
|
-
waiting: "red",
|
|
328
|
-
paused: "gold",
|
|
329
|
-
archived: "purple"
|
|
330
|
-
};
|
|
331
|
-
function StatusDot({ status }) {
|
|
332
|
-
const color = STATUS_COLORS[status] ?? "#6B7280";
|
|
333
|
-
return /* @__PURE__ */ jsxDEV4(Text4, {
|
|
334
|
-
color,
|
|
335
|
-
children: "\u25CF"
|
|
336
|
-
}, undefined, false, undefined, this);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// src/tui/components/session-row.tsx
|
|
468
|
+
// src/tui/screens/launch/create-screen.tsx
|
|
340
469
|
import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
|
|
341
|
-
function
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
selected
|
|
346
|
-
}) {
|
|
347
|
-
return /* @__PURE__ */ jsxDEV5(Box4, {
|
|
348
|
-
flexDirection: "row",
|
|
349
|
-
gap: 1,
|
|
350
|
-
children: [
|
|
351
|
-
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
352
|
-
color: "green",
|
|
353
|
-
children: selected ? "\u276F " : " "
|
|
354
|
-
}, undefined, false, undefined, this),
|
|
355
|
-
/* @__PURE__ */ jsxDEV5(StatusDot, {
|
|
356
|
-
status
|
|
357
|
-
}, undefined, false, undefined, this),
|
|
358
|
-
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
359
|
-
bold: selected,
|
|
360
|
-
color: selected ? "green" : undefined,
|
|
361
|
-
children: name
|
|
362
|
-
}, undefined, false, undefined, this),
|
|
363
|
-
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
364
|
-
color: selected ? "green" : undefined,
|
|
365
|
-
dim: true,
|
|
366
|
-
children: formatAgo(updatedAt)
|
|
367
|
-
}, undefined, false, undefined, this)
|
|
368
|
-
]
|
|
369
|
-
}, undefined, true, undefined, this);
|
|
370
|
-
}
|
|
371
|
-
// src/tui/hooks/use-launch-actions.ts
|
|
372
|
-
import { useCallback as useCallback2 } from "react";
|
|
373
|
-
|
|
374
|
-
// src/db/queries/conversations.ts
|
|
375
|
-
import { eq as eq2, and as and2, desc, sql as sql3 } from "drizzle-orm";
|
|
376
|
-
function getConversationsBySession(sessionId) {
|
|
377
|
-
return getDb().select().from(conversations).where(and2(eq2(conversations.sessionId, sessionId), eq2(conversations.discarded, false))).orderBy(desc(conversations.startedAt)).all();
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// src/db/queries/groups.ts
|
|
381
|
-
import { eq as eq3, like, or, isNull } from "drizzle-orm";
|
|
382
|
-
function createGroup(opts) {
|
|
383
|
-
const db = getDb();
|
|
384
|
-
const id = createId();
|
|
385
|
-
let path = opts.slug;
|
|
386
|
-
let depth = 0;
|
|
387
|
-
if (opts.parentId) {
|
|
388
|
-
const parent = db.select().from(groups).where(eq3(groups.id, opts.parentId)).get();
|
|
389
|
-
if (!parent)
|
|
390
|
-
throw new Error(`Parent group ${opts.parentId} not found`);
|
|
391
|
-
path = `${parent.path}/${opts.slug}`;
|
|
392
|
-
depth = parent.depth + 1;
|
|
393
|
-
}
|
|
394
|
-
return db.insert(groups).values({ id, slug: opts.slug, name: opts.name, parentId: opts.parentId, path, depth }).returning().get();
|
|
395
|
-
}
|
|
396
|
-
function getGroupByPath(path) {
|
|
397
|
-
return getDb().select().from(groups).where(eq3(groups.path, path)).get();
|
|
398
|
-
}
|
|
399
|
-
function getOrCreateGroupPath(path) {
|
|
400
|
-
const existing = getGroupByPath(path);
|
|
401
|
-
if (existing)
|
|
402
|
-
return existing.id;
|
|
403
|
-
const segments = path.split("/");
|
|
404
|
-
let parentId;
|
|
405
|
-
for (let i = 0;i < segments.length; i++) {
|
|
406
|
-
const partialPath = segments.slice(0, i + 1).join("/");
|
|
407
|
-
const group = getGroupByPath(partialPath);
|
|
408
|
-
if (group) {
|
|
409
|
-
parentId = group.id;
|
|
410
|
-
} else {
|
|
411
|
-
const slug = segments[i];
|
|
412
|
-
const created = createGroup({ slug, name: slug, parentId });
|
|
413
|
-
parentId = created.id;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
return parentId;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// src/lib/parse-session-name.ts
|
|
420
|
-
function parseSessionName(input) {
|
|
421
|
-
const trimmed = input.trim().replace(/^\/+|\/+$/g, "");
|
|
422
|
-
if (!trimmed) {
|
|
423
|
-
throw new Error("Session name cannot be empty");
|
|
424
|
-
}
|
|
425
|
-
const segments = trimmed.split("/").filter(Boolean);
|
|
426
|
-
if (segments.length < 2) {
|
|
427
|
-
throw new Error(`Session name must include at least one group: "group/session" (got "${trimmed}")`);
|
|
428
|
-
}
|
|
429
|
-
for (const segment of segments) {
|
|
430
|
-
if (!/^[a-z0-9][a-z0-9._-]*$/i.test(segment)) {
|
|
431
|
-
throw new Error(`Invalid segment "${segment}": must start with alphanumeric and contain only letters, digits, dots, underscores, or dashes`);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
const slug = segments[segments.length - 1];
|
|
435
|
-
const groupPath = segments.slice(0, -1).join("/");
|
|
436
|
-
return { groupPath, slug };
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// src/tui/hooks/use-launch-actions.ts
|
|
440
|
-
function useLaunchActions({
|
|
441
|
-
selected,
|
|
442
|
-
select,
|
|
443
|
-
refresh,
|
|
444
|
-
setMode,
|
|
445
|
-
setCursor,
|
|
446
|
-
setNewName,
|
|
447
|
-
setEditValue,
|
|
448
|
-
setError
|
|
470
|
+
function CreateScreen({
|
|
471
|
+
isFocused,
|
|
472
|
+
onSubmit,
|
|
473
|
+
onQuit
|
|
449
474
|
}) {
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
475
|
+
const { clear } = useTui();
|
|
476
|
+
const [step, setStep] = useState2("group");
|
|
477
|
+
const [groupPath, setGroupPath] = useState2(null);
|
|
478
|
+
const [slug, setSlug] = useState2("");
|
|
479
|
+
const [error, setError] = useState2(null);
|
|
480
|
+
const stepRef = useRef(step);
|
|
481
|
+
stepRef.current = step;
|
|
482
|
+
useEffect(() => {
|
|
483
|
+
clear();
|
|
484
|
+
}, [step, clear]);
|
|
485
|
+
const groupItems = useMemo2(() => {
|
|
486
|
+
const activeSessions = getAllSessions({ excludeArchived: true });
|
|
487
|
+
const counts = new Map;
|
|
488
|
+
for (const s of activeSessions) {
|
|
489
|
+
counts.set(s.groupPath, (counts.get(s.groupPath) ?? 0) + 1);
|
|
462
490
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
if (!selected)
|
|
473
|
-
return;
|
|
474
|
-
deleteSession(selected.session.id);
|
|
475
|
-
setCursor((c) => Math.max(0, c - 1));
|
|
476
|
-
setMode("browse");
|
|
477
|
-
refresh();
|
|
478
|
-
}, [selected, refresh, setCursor, setMode]);
|
|
479
|
-
const handleStartRename = useCallback2(() => {
|
|
480
|
-
if (!selected)
|
|
481
|
-
return;
|
|
482
|
-
setMode("rename");
|
|
483
|
-
setEditValue(selected.session.slug);
|
|
484
|
-
setError(null);
|
|
485
|
-
}, [selected, setMode, setEditValue, setError]);
|
|
486
|
-
const handleStartMove = useCallback2(() => {
|
|
487
|
-
if (!selected)
|
|
488
|
-
return;
|
|
489
|
-
setMode("move");
|
|
490
|
-
setEditValue(selected.groupPath);
|
|
491
|
-
setError(null);
|
|
492
|
-
}, [selected, setMode, setEditValue, setError]);
|
|
493
|
-
const handleStartCreate = useCallback2(() => {
|
|
494
|
-
setMode("create");
|
|
495
|
-
setNewName("");
|
|
496
|
-
setError(null);
|
|
497
|
-
}, [setMode, setNewName, setError]);
|
|
498
|
-
const cancelToBrowse = useCallback2(() => {
|
|
499
|
-
setMode("browse");
|
|
491
|
+
return getAllGroups().filter((g) => counts.has(g.path)).sort((a, b) => a.path.localeCompare(b.path)).map((g) => ({
|
|
492
|
+
value: g.path,
|
|
493
|
+
label: g.path,
|
|
494
|
+
color: g.color,
|
|
495
|
+
meta: `${counts.get(g.path)}`
|
|
496
|
+
}));
|
|
497
|
+
}, []);
|
|
498
|
+
const handleGroupPicked = useCallback((value) => {
|
|
499
|
+
setGroupPath(value);
|
|
500
500
|
setError(null);
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
501
|
+
setStep("slug");
|
|
502
|
+
}, []);
|
|
503
|
+
const handleSlugSubmit = useCallback((value) => {
|
|
504
|
+
if (stepRef.current !== "slug")
|
|
504
505
|
return;
|
|
505
|
-
try {
|
|
506
|
-
const { groupPath, slug } = parseSessionName(value);
|
|
507
|
-
select({ type: "create", groupPath, slug });
|
|
508
|
-
} catch (e) {
|
|
509
|
-
setError(e instanceof Error ? e.message : String(e));
|
|
510
|
-
}
|
|
511
|
-
}, [select, setError]);
|
|
512
|
-
const handleRenameSubmit = useCallback2((value) => {
|
|
513
506
|
const trimmed = value.trim();
|
|
514
|
-
if (!trimmed
|
|
507
|
+
if (!trimmed) {
|
|
508
|
+
setError("Name cannot be empty.");
|
|
515
509
|
return;
|
|
510
|
+
}
|
|
516
511
|
if (!/^[a-z0-9][a-z0-9._-]*$/i.test(trimmed)) {
|
|
517
|
-
setError("
|
|
512
|
+
setError("Name must start alphanumeric; letters, digits, dots, underscores, dashes only.");
|
|
518
513
|
return;
|
|
519
514
|
}
|
|
520
|
-
|
|
521
|
-
setMode("browse");
|
|
522
|
-
refresh();
|
|
523
|
-
}, [selected, setError, setMode, refresh]);
|
|
524
|
-
const handleMoveSubmit = useCallback2((value) => {
|
|
525
|
-
const trimmed = value.trim().replace(/^\/+|\/+$/g, "");
|
|
526
|
-
if (!trimmed || !selected)
|
|
515
|
+
if (!groupPath)
|
|
527
516
|
return;
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
refresh();
|
|
533
|
-
} catch (e) {
|
|
534
|
-
setError(e instanceof Error ? e.message : String(e));
|
|
535
|
-
}
|
|
536
|
-
}, [selected, setMode, refresh, setError]);
|
|
537
|
-
return {
|
|
538
|
-
handleResume,
|
|
539
|
-
handleArchiveToggle,
|
|
540
|
-
handleConfirmDelete,
|
|
541
|
-
handleStartRename,
|
|
542
|
-
handleStartMove,
|
|
543
|
-
handleStartCreate,
|
|
544
|
-
cancelToBrowse,
|
|
545
|
-
handleCreateSubmit,
|
|
546
|
-
handleRenameSubmit,
|
|
547
|
-
handleMoveSubmit
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
|
-
// src/tui/hooks/use-launch-hotkeys.ts
|
|
551
|
-
import { useCallback as useCallback3, useRef, useState as useState2 } from "react";
|
|
552
|
-
import { useHotkey, useInput } from "@orchetron/storm";
|
|
553
|
-
function useLaunchHotkeys({
|
|
554
|
-
mode,
|
|
555
|
-
selected,
|
|
556
|
-
sessionCount,
|
|
557
|
-
quit,
|
|
558
|
-
setCursor,
|
|
559
|
-
toggleArchived,
|
|
560
|
-
handleResume,
|
|
561
|
-
handleStartCreate,
|
|
562
|
-
handleArchiveToggle,
|
|
563
|
-
handleStartRename,
|
|
564
|
-
handleStartMove,
|
|
565
|
-
handleConfirmDelete,
|
|
566
|
-
cancelToBrowse,
|
|
567
|
-
setMode
|
|
568
|
-
}) {
|
|
569
|
-
useInput((e) => {
|
|
570
|
-
if (e.key === "c" && e.ctrl) {
|
|
571
|
-
e.consumed = true;
|
|
572
|
-
quit();
|
|
517
|
+
onSubmit({ groupPath, slug: trimmed });
|
|
518
|
+
}, [groupPath, onSubmit]);
|
|
519
|
+
useInput2((e) => {
|
|
520
|
+
if (!isFocused)
|
|
573
521
|
return;
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
setCursor((c) => Math.max(0, c - 1));
|
|
577
|
-
} else if (e.key === "down" || e.key === "j") {
|
|
578
|
-
setCursor((c) => Math.min(sessionCount - 1, c + 1));
|
|
579
|
-
} else if (e.key === "return") {
|
|
580
|
-
handleResume();
|
|
581
|
-
} else if (e.key === "n") {
|
|
582
|
-
handleStartCreate();
|
|
583
|
-
} else if (e.key === "a") {
|
|
584
|
-
handleArchiveToggle();
|
|
585
|
-
} else if (e.key === "d" && selected) {
|
|
586
|
-
setMode("confirm-delete");
|
|
587
|
-
} else if (e.key === "r") {
|
|
588
|
-
handleStartRename();
|
|
589
|
-
} else if (e.key === "m") {
|
|
590
|
-
handleStartMove();
|
|
591
|
-
} else if (e.key === "tab") {
|
|
592
|
-
toggleArchived();
|
|
593
|
-
} else if (e.key === "q") {
|
|
594
|
-
quit();
|
|
595
|
-
} else {
|
|
522
|
+
if (e.key === "c" && e.ctrl) {
|
|
523
|
+
onQuit();
|
|
596
524
|
return;
|
|
597
525
|
}
|
|
598
|
-
e.
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
{ label: "resume", description: "return" },
|
|
603
|
-
{ label: "new", description: "n" },
|
|
604
|
-
{ label: "archive", description: "a" },
|
|
605
|
-
{ label: "delete", description: "d" },
|
|
606
|
-
{ label: "rename", description: "r" },
|
|
607
|
-
{ label: "move", description: "m" },
|
|
608
|
-
{ label: "quit", description: "q" }
|
|
609
|
-
];
|
|
610
|
-
const escPendingRef = useRef(false);
|
|
611
|
-
const [escPending, setEscPending] = useState2(false);
|
|
612
|
-
const clearEscPending = useCallback3(() => {
|
|
613
|
-
if (escPendingRef.current) {
|
|
614
|
-
escPendingRef.current = false;
|
|
615
|
-
setEscPending(false);
|
|
526
|
+
if (e.key === "escape" && step === "slug") {
|
|
527
|
+
setStep("group");
|
|
528
|
+
setSlug("");
|
|
529
|
+
setError(null);
|
|
616
530
|
}
|
|
617
|
-
},
|
|
618
|
-
|
|
619
|
-
hotkeys: [
|
|
620
|
-
{
|
|
621
|
-
key: "escape",
|
|
622
|
-
label: "cancel",
|
|
623
|
-
action: () => {
|
|
624
|
-
if (escPendingRef.current) {
|
|
625
|
-
escPendingRef.current = false;
|
|
626
|
-
setEscPending(false);
|
|
627
|
-
cancelToBrowse();
|
|
628
|
-
} else {
|
|
629
|
-
escPendingRef.current = true;
|
|
630
|
-
setEscPending(true);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
],
|
|
635
|
-
isActive: mode === "create" || mode === "rename" || mode === "move"
|
|
636
|
-
});
|
|
637
|
-
const { bindings: deleteBindings } = useHotkey({
|
|
638
|
-
hotkeys: [
|
|
639
|
-
{ key: "y", label: "confirm", action: handleConfirmDelete },
|
|
640
|
-
{ key: "escape", label: "cancel", action: () => setMode("browse") }
|
|
641
|
-
],
|
|
642
|
-
isActive: mode === "confirm-delete"
|
|
643
|
-
});
|
|
644
|
-
return {
|
|
645
|
-
browseBindings,
|
|
646
|
-
inputBindings,
|
|
647
|
-
deleteBindings,
|
|
648
|
-
escPending,
|
|
649
|
-
clearEscPending
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
// src/tui/screens/launch/launch.constants.ts
|
|
653
|
-
var KEY_DISPLAY = {
|
|
654
|
-
up: "\u2191",
|
|
655
|
-
down: "\u2193",
|
|
656
|
-
return: "enter",
|
|
657
|
-
escape: "esc"
|
|
658
|
-
};
|
|
659
|
-
|
|
660
|
-
// src/tui/screens/launch/launch.utils.ts
|
|
661
|
-
function formatBindings(bindings) {
|
|
662
|
-
return bindings.filter((b) => b.label).map((b) => `${KEY_DISPLAY[b.description] ?? b.description} ${b.label}`).join(" \xB7 ");
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
// src/tui/screens/launch/create-session.tsx
|
|
666
|
-
import { useState as useState3 } from "react";
|
|
667
|
-
import { Box as Box5, Text as Text6, TextInput } from "@orchetron/storm";
|
|
668
|
-
import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
|
|
669
|
-
function Create({ isFocused, setError, handleCreateSubmit, error }) {
|
|
670
|
-
const [newName, setNewName] = useState3("");
|
|
671
|
-
const handleChange = (v) => {
|
|
672
|
-
setNewName(v);
|
|
673
|
-
setError(null);
|
|
674
|
-
};
|
|
675
|
-
return /* @__PURE__ */ jsxDEV6(Box5, {
|
|
676
|
-
flexDirection: "column",
|
|
677
|
-
children: [
|
|
678
|
-
/* @__PURE__ */ jsxDEV6(Text6, {
|
|
679
|
-
children: "Create a session:"
|
|
680
|
-
}, undefined, false, undefined, this),
|
|
681
|
-
/* @__PURE__ */ jsxDEV6(TextInput, {
|
|
682
|
-
value: newName,
|
|
683
|
-
onChange: handleChange,
|
|
684
|
-
onSubmit: handleCreateSubmit,
|
|
685
|
-
placeholder: "group/session-name",
|
|
686
|
-
placeholderColor: "green",
|
|
687
|
-
color: "green",
|
|
688
|
-
isFocused
|
|
689
|
-
}, undefined, false, undefined, this),
|
|
690
|
-
error && /* @__PURE__ */ jsxDEV6(Text6, {
|
|
691
|
-
color: "red",
|
|
692
|
-
children: error
|
|
693
|
-
}, undefined, false, undefined, this)
|
|
694
|
-
]
|
|
695
|
-
}, undefined, true, undefined, this);
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
// src/tui/screens/launch/rename.tsx
|
|
699
|
-
import { Box as Box6, Text as Text7, TextInput as TextInput2 } from "@orchetron/storm";
|
|
700
|
-
import { jsxDEV as jsxDEV7, Fragment } from "react/jsx-dev-runtime";
|
|
701
|
-
function Rename({
|
|
702
|
-
editValue,
|
|
703
|
-
setEditValue,
|
|
704
|
-
setError,
|
|
705
|
-
handleRenameSubmit,
|
|
706
|
-
inputBindings,
|
|
707
|
-
error,
|
|
708
|
-
escPending,
|
|
709
|
-
clearEscPending,
|
|
710
|
-
placeholder
|
|
711
|
-
}) {
|
|
712
|
-
return /* @__PURE__ */ jsxDEV7(Box6, {
|
|
531
|
+
}, { isActive: isFocused });
|
|
532
|
+
return /* @__PURE__ */ jsxDEV5(Box4, {
|
|
713
533
|
flexDirection: "column",
|
|
714
|
-
|
|
534
|
+
gap: 1,
|
|
715
535
|
children: [
|
|
716
|
-
/* @__PURE__ */
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
children: "Rename session"
|
|
720
|
-
}, undefined, false, undefined, this),
|
|
721
|
-
/* @__PURE__ */ jsxDEV7(Box6, {
|
|
722
|
-
height: 1
|
|
723
|
-
}, undefined, false, undefined, this),
|
|
724
|
-
/* @__PURE__ */ jsxDEV7(Text7, {
|
|
725
|
-
children: "New name: "
|
|
726
|
-
}, undefined, false, undefined, this),
|
|
727
|
-
/* @__PURE__ */ jsxDEV7(TextInput2, {
|
|
728
|
-
value: editValue,
|
|
729
|
-
onChange: (v) => {
|
|
730
|
-
setEditValue(v);
|
|
731
|
-
setError(null);
|
|
732
|
-
clearEscPending();
|
|
733
|
-
},
|
|
734
|
-
onSubmit: handleRenameSubmit,
|
|
735
|
-
placeholder
|
|
736
|
-
}, undefined, false, undefined, this),
|
|
737
|
-
error && /* @__PURE__ */ jsxDEV7(Fragment, {
|
|
536
|
+
/* @__PURE__ */ jsxDEV5(Box4, {
|
|
537
|
+
flexDirection: "row",
|
|
538
|
+
gap: 1,
|
|
738
539
|
children: [
|
|
739
|
-
/* @__PURE__ */
|
|
740
|
-
|
|
540
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
541
|
+
bold: true,
|
|
542
|
+
children: "New session"
|
|
543
|
+
}, undefined, false, undefined, this),
|
|
544
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
545
|
+
dim: true,
|
|
546
|
+
children: "\xB7"
|
|
741
547
|
}, undefined, false, undefined, this),
|
|
742
|
-
/* @__PURE__ */
|
|
743
|
-
|
|
744
|
-
children:
|
|
548
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
549
|
+
dim: true,
|
|
550
|
+
children: stepLabel(step)
|
|
745
551
|
}, undefined, false, undefined, this)
|
|
746
552
|
]
|
|
747
553
|
}, undefined, true, undefined, this),
|
|
748
|
-
/* @__PURE__ */
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
escPending ? /* @__PURE__ */ jsxDEV7(Text7, {
|
|
752
|
-
color: "yellow",
|
|
753
|
-
children: "Press esc again to cancel"
|
|
754
|
-
}, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV7(Text7, {
|
|
755
|
-
dim: true,
|
|
756
|
-
children: [
|
|
757
|
-
"enter save \xB7 ",
|
|
758
|
-
formatBindings(inputBindings)
|
|
759
|
-
]
|
|
760
|
-
}, undefined, true, undefined, this)
|
|
761
|
-
]
|
|
762
|
-
}, undefined, true, undefined, this);
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
// src/tui/screens/launch/move.tsx
|
|
766
|
-
import { Box as Box7, Text as Text8, TextInput as TextInput3 } from "@orchetron/storm";
|
|
767
|
-
import { jsxDEV as jsxDEV8, Fragment as Fragment2 } from "react/jsx-dev-runtime";
|
|
768
|
-
function Move({
|
|
769
|
-
editValue,
|
|
770
|
-
setEditValue,
|
|
771
|
-
setError,
|
|
772
|
-
handleMoveSubmit,
|
|
773
|
-
inputBindings,
|
|
774
|
-
error,
|
|
775
|
-
escPending,
|
|
776
|
-
clearEscPending,
|
|
777
|
-
placeholder = "category/session-name"
|
|
778
|
-
}) {
|
|
779
|
-
return /* @__PURE__ */ jsxDEV8(Box7, {
|
|
780
|
-
flexDirection: "column",
|
|
781
|
-
padding: 1,
|
|
782
|
-
children: [
|
|
783
|
-
/* @__PURE__ */ jsxDEV8(Text8, {
|
|
784
|
-
bold: true,
|
|
785
|
-
color: "#82AAFF",
|
|
786
|
-
children: "Move session"
|
|
787
|
-
}, undefined, false, undefined, this),
|
|
788
|
-
/* @__PURE__ */ jsxDEV8(Box7, {
|
|
789
|
-
height: 1
|
|
790
|
-
}, undefined, false, undefined, this),
|
|
791
|
-
/* @__PURE__ */ jsxDEV8(Text8, {
|
|
792
|
-
children: "New group path: "
|
|
793
|
-
}, undefined, false, undefined, this),
|
|
794
|
-
/* @__PURE__ */ jsxDEV8(TextInput3, {
|
|
795
|
-
value: editValue,
|
|
796
|
-
onChange: (v) => {
|
|
797
|
-
setEditValue(v);
|
|
798
|
-
setError(null);
|
|
799
|
-
clearEscPending();
|
|
800
|
-
},
|
|
801
|
-
onSubmit: handleMoveSubmit,
|
|
802
|
-
placeholder
|
|
803
|
-
}, undefined, false, undefined, this),
|
|
804
|
-
error && /* @__PURE__ */ jsxDEV8(Fragment2, {
|
|
554
|
+
groupPath && step === "slug" && /* @__PURE__ */ jsxDEV5(Box4, {
|
|
555
|
+
flexDirection: "row",
|
|
556
|
+
gap: 1,
|
|
805
557
|
children: [
|
|
806
|
-
/* @__PURE__ */
|
|
807
|
-
|
|
558
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
559
|
+
dim: true,
|
|
560
|
+
children: "Group:"
|
|
808
561
|
}, undefined, false, undefined, this),
|
|
809
|
-
/* @__PURE__ */
|
|
810
|
-
color: "
|
|
811
|
-
children:
|
|
562
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
563
|
+
color: "green",
|
|
564
|
+
children: groupPath
|
|
812
565
|
}, undefined, false, undefined, this)
|
|
813
566
|
]
|
|
814
567
|
}, undefined, true, undefined, this),
|
|
815
|
-
/* @__PURE__ */
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
}, undefined, true, undefined, this)
|
|
828
|
-
]
|
|
829
|
-
}, undefined, true, undefined, this);
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
// src/tui/screens/launch/confirm-delete.tsx
|
|
833
|
-
import { Box as Box8, Text as Text9 } from "@orchetron/storm";
|
|
834
|
-
import { jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime";
|
|
835
|
-
function ConfirmDelete({ sessionName, deleteBindings }) {
|
|
836
|
-
return /* @__PURE__ */ jsxDEV9(Box8, {
|
|
837
|
-
flexDirection: "column",
|
|
838
|
-
padding: 1,
|
|
839
|
-
children: [
|
|
840
|
-
/* @__PURE__ */ jsxDEV9(Text9, {
|
|
841
|
-
bold: true,
|
|
842
|
-
color: "red",
|
|
843
|
-
children: "Delete session?"
|
|
844
|
-
}, undefined, false, undefined, this),
|
|
845
|
-
/* @__PURE__ */ jsxDEV9(Box8, {
|
|
846
|
-
height: 1
|
|
568
|
+
/* @__PURE__ */ jsxDEV5(Box4, {
|
|
569
|
+
flexDirection: "column",
|
|
570
|
+
height: step === "group" ? undefined : 0,
|
|
571
|
+
overflow: "hidden",
|
|
572
|
+
children: /* @__PURE__ */ jsxDEV5(Picker, {
|
|
573
|
+
mode: "single",
|
|
574
|
+
items: groupItems,
|
|
575
|
+
isFocused: isFocused && step === "group",
|
|
576
|
+
placeholder: "Pick or type a new group path\u2026",
|
|
577
|
+
onSubmit: handleGroupPicked,
|
|
578
|
+
emptyHint: "No active groups. Type a name to create one."
|
|
579
|
+
}, undefined, false, undefined, this)
|
|
847
580
|
}, undefined, false, undefined, this),
|
|
848
|
-
/* @__PURE__ */
|
|
581
|
+
/* @__PURE__ */ jsxDEV5(Box4, {
|
|
582
|
+
flexDirection: "column",
|
|
583
|
+
gap: 0,
|
|
584
|
+
height: step === "slug" ? undefined : 0,
|
|
585
|
+
overflow: "hidden",
|
|
849
586
|
children: [
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
bold: true,
|
|
854
|
-
children: sessionName
|
|
587
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
588
|
+
dim: true,
|
|
589
|
+
children: "Name"
|
|
855
590
|
}, undefined, false, undefined, this),
|
|
856
|
-
|
|
857
|
-
|
|
591
|
+
/* @__PURE__ */ jsxDEV5(Box4, {
|
|
592
|
+
borderStyle: "round",
|
|
593
|
+
borderColor: isFocused ? "green" : undefined,
|
|
594
|
+
borderDimColor: !isFocused,
|
|
595
|
+
paddingX: 1,
|
|
596
|
+
children: /* @__PURE__ */ jsxDEV5(TextInput2, {
|
|
597
|
+
value: slug,
|
|
598
|
+
onChange: (v) => {
|
|
599
|
+
setSlug(v);
|
|
600
|
+
setError(null);
|
|
601
|
+
},
|
|
602
|
+
onSubmit: handleSlugSubmit,
|
|
603
|
+
placeholder: "fix-auth-bug",
|
|
604
|
+
color: "green",
|
|
605
|
+
placeholderColor: "gray",
|
|
606
|
+
isFocused: isFocused && step === "slug"
|
|
607
|
+
}, undefined, false, undefined, this)
|
|
608
|
+
}, undefined, false, undefined, this)
|
|
858
609
|
]
|
|
859
610
|
}, undefined, true, undefined, this),
|
|
860
|
-
/* @__PURE__ */
|
|
861
|
-
|
|
611
|
+
error && /* @__PURE__ */ jsxDEV5(Text5, {
|
|
612
|
+
color: "red",
|
|
613
|
+
children: error
|
|
862
614
|
}, undefined, false, undefined, this),
|
|
863
|
-
/* @__PURE__ */
|
|
615
|
+
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
864
616
|
dim: true,
|
|
865
|
-
children:
|
|
617
|
+
children: footer(step)
|
|
866
618
|
}, undefined, false, undefined, this)
|
|
867
619
|
]
|
|
868
620
|
}, undefined, true, undefined, this);
|
|
869
621
|
}
|
|
622
|
+
function stepLabel(step) {
|
|
623
|
+
return step === "group" ? "1/2 Group" : "2/2 Name";
|
|
624
|
+
}
|
|
625
|
+
function footer(step) {
|
|
626
|
+
if (step === "group") {
|
|
627
|
+
return "\u2191\u2193 navigate \xB7 enter pick \xB7 ctrl+c quit";
|
|
628
|
+
}
|
|
629
|
+
return "enter create \xB7 esc back \xB7 ctrl+c quit";
|
|
630
|
+
}
|
|
870
631
|
|
|
871
632
|
// src/tui/screens/launch/index.tsx
|
|
872
|
-
import { jsxDEV as
|
|
633
|
+
import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
|
|
873
634
|
function Launch({ onSelect }) {
|
|
874
|
-
const { exit } =
|
|
875
|
-
const
|
|
876
|
-
const [newName, setNewName] = useState4("");
|
|
877
|
-
const [editValue, setEditValue] = useState4("");
|
|
878
|
-
const [error, setError] = useState4(null);
|
|
879
|
-
const { sessionRows, selected, cursor, setCursor, toggleArchived, refresh } = useLaunchSessions();
|
|
880
|
-
const select = useCallback4((selection) => {
|
|
635
|
+
const { exit } = useTui2();
|
|
636
|
+
const select = useCallback2((selection) => {
|
|
881
637
|
onSelect(selection);
|
|
882
638
|
exit();
|
|
883
639
|
}, [onSelect, exit]);
|
|
884
|
-
|
|
885
|
-
selected,
|
|
886
|
-
select,
|
|
887
|
-
refresh,
|
|
888
|
-
setMode,
|
|
889
|
-
setCursor,
|
|
890
|
-
setNewName,
|
|
891
|
-
setEditValue,
|
|
892
|
-
setError
|
|
893
|
-
});
|
|
894
|
-
const {
|
|
895
|
-
browseBindings,
|
|
896
|
-
inputBindings,
|
|
897
|
-
deleteBindings,
|
|
898
|
-
escPending,
|
|
899
|
-
clearEscPending
|
|
900
|
-
} = useLaunchHotkeys({
|
|
901
|
-
mode,
|
|
902
|
-
selected,
|
|
903
|
-
sessionCount: sessionRows.length,
|
|
904
|
-
quit: () => select({ type: "quit" }),
|
|
905
|
-
setCursor,
|
|
906
|
-
toggleArchived,
|
|
907
|
-
setMode,
|
|
908
|
-
...actions
|
|
909
|
-
});
|
|
910
|
-
if (mode === "confirm-delete" && selected) {
|
|
911
|
-
return /* @__PURE__ */ jsxDEV10(ConfirmDelete, {
|
|
912
|
-
sessionName: `${selected.groupPath}/${selected.session.slug}`,
|
|
913
|
-
deleteBindings
|
|
914
|
-
}, undefined, false, undefined, this);
|
|
915
|
-
}
|
|
916
|
-
if (mode === "rename" && selected) {
|
|
917
|
-
return /* @__PURE__ */ jsxDEV10(Rename, {
|
|
918
|
-
editValue,
|
|
919
|
-
setEditValue,
|
|
920
|
-
setError,
|
|
921
|
-
handleRenameSubmit: actions.handleRenameSubmit,
|
|
922
|
-
inputBindings,
|
|
923
|
-
error,
|
|
924
|
-
escPending,
|
|
925
|
-
clearEscPending,
|
|
926
|
-
placeholder: selected.session.slug
|
|
927
|
-
}, undefined, false, undefined, this);
|
|
928
|
-
}
|
|
929
|
-
if (mode === "move" && selected) {
|
|
930
|
-
return /* @__PURE__ */ jsxDEV10(Move, {
|
|
931
|
-
editValue,
|
|
932
|
-
setEditValue,
|
|
933
|
-
setError,
|
|
934
|
-
handleMoveSubmit: actions.handleMoveSubmit,
|
|
935
|
-
inputBindings,
|
|
936
|
-
error,
|
|
937
|
-
escPending,
|
|
938
|
-
clearEscPending,
|
|
939
|
-
placeholder: selected.groupPath
|
|
940
|
-
}, undefined, false, undefined, this);
|
|
941
|
-
}
|
|
942
|
-
const hasSessions = sessionRows.length > 0;
|
|
943
|
-
return /* @__PURE__ */ jsxDEV10(Box9, {
|
|
640
|
+
return /* @__PURE__ */ jsxDEV6(Box5, {
|
|
944
641
|
flexDirection: "column",
|
|
945
642
|
paddingY: 1,
|
|
946
643
|
gap: 1,
|
|
947
644
|
children: [
|
|
948
|
-
/* @__PURE__ */
|
|
949
|
-
"data-slot": "logo",
|
|
645
|
+
/* @__PURE__ */ jsxDEV6(Box5, {
|
|
950
646
|
marginX: 1,
|
|
951
|
-
children: /* @__PURE__ */
|
|
647
|
+
children: /* @__PURE__ */ jsxDEV6(Logo, {}, undefined, false, undefined, this)
|
|
952
648
|
}, undefined, false, undefined, this),
|
|
953
|
-
/* @__PURE__ */
|
|
954
|
-
"data-slot": "app-menu",
|
|
649
|
+
/* @__PURE__ */ jsxDEV6(Box5, {
|
|
955
650
|
flexDirection: "column",
|
|
956
651
|
marginX: 2,
|
|
957
652
|
gap: 1,
|
|
958
653
|
children: [
|
|
959
|
-
/* @__PURE__ */
|
|
960
|
-
/* @__PURE__ */
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
/* @__PURE__ */ jsxDEV10(Create, {
|
|
965
|
-
isFocused: mode === "create",
|
|
966
|
-
setError,
|
|
967
|
-
handleCreateSubmit: actions.handleCreateSubmit,
|
|
968
|
-
error
|
|
969
|
-
}, undefined, false, undefined, this),
|
|
970
|
-
hasSessions && sessionRows.map((row, i) => /* @__PURE__ */ jsxDEV10(SessionRow, {
|
|
971
|
-
name: `${row.groupPath}/${row.session.slug}`,
|
|
972
|
-
status: row.session.status,
|
|
973
|
-
updatedAt: row.session.updatedAt,
|
|
974
|
-
selected: i === cursor
|
|
975
|
-
}, row.session.id, false, undefined, this))
|
|
976
|
-
]
|
|
977
|
-
}, undefined, true, undefined, this),
|
|
978
|
-
/* @__PURE__ */ jsxDEV10(Text10, {
|
|
979
|
-
dim: true,
|
|
980
|
-
children: formatBindings(browseBindings)
|
|
654
|
+
/* @__PURE__ */ jsxDEV6(AppDetails, {}, undefined, false, undefined, this),
|
|
655
|
+
/* @__PURE__ */ jsxDEV6(CreateScreen, {
|
|
656
|
+
isFocused: true,
|
|
657
|
+
onSubmit: (payload) => select({ type: "create", ...payload }),
|
|
658
|
+
onQuit: () => select({ type: "quit" })
|
|
981
659
|
}, undefined, false, undefined, this)
|
|
982
660
|
]
|
|
983
661
|
}, undefined, true, undefined, this)
|
|
@@ -986,9 +664,50 @@ function Launch({ onSelect }) {
|
|
|
986
664
|
}
|
|
987
665
|
|
|
988
666
|
// src/tui/screens/Exit.tsx
|
|
989
|
-
import { useState as
|
|
990
|
-
import { Box as
|
|
991
|
-
|
|
667
|
+
import { useState as useState3 } from "react";
|
|
668
|
+
import { Box as Box6, Text as Text6, useInput as useInput3, useTui as useTui3 } from "@orchetron/storm";
|
|
669
|
+
|
|
670
|
+
// src/db/queries/conversations.ts
|
|
671
|
+
import { eq as eq3, and as and2, desc, sql as sql3 } from "drizzle-orm";
|
|
672
|
+
function getConversationsBySession(sessionId) {
|
|
673
|
+
return getDb().select().from(conversations).where(and2(eq3(conversations.sessionId, sessionId), eq3(conversations.discarded, false))).orderBy(desc(conversations.startedAt)).all();
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// src/lib/format.ts
|
|
677
|
+
var SECOND = 1000;
|
|
678
|
+
var MINUTE = 60 * SECOND;
|
|
679
|
+
var HOUR = 60 * MINUTE;
|
|
680
|
+
var DAY = 24 * HOUR;
|
|
681
|
+
function formatDuration(ms) {
|
|
682
|
+
if (ms < MINUTE)
|
|
683
|
+
return `${Math.round(ms / SECOND)}s`;
|
|
684
|
+
const days = Math.floor(ms / DAY);
|
|
685
|
+
const hours = Math.floor(ms % DAY / HOUR);
|
|
686
|
+
const minutes = Math.floor(ms % HOUR / MINUTE);
|
|
687
|
+
if (days > 0)
|
|
688
|
+
return hours > 0 ? `${days}d ${hours}h` : `${days}d`;
|
|
689
|
+
if (hours > 0)
|
|
690
|
+
return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
|
|
691
|
+
return `${minutes}m`;
|
|
692
|
+
}
|
|
693
|
+
function formatAgo(isoOrDate) {
|
|
694
|
+
const date = typeof isoOrDate === "string" ? new Date(isoOrDate) : isoOrDate;
|
|
695
|
+
const ms = Date.now() - date.getTime();
|
|
696
|
+
if (ms < MINUTE)
|
|
697
|
+
return "just now";
|
|
698
|
+
if (ms < HOUR)
|
|
699
|
+
return `${Math.floor(ms / MINUTE)}m ago`;
|
|
700
|
+
if (ms < DAY)
|
|
701
|
+
return `${Math.floor(ms / HOUR)}h ago`;
|
|
702
|
+
if (ms < 2 * DAY)
|
|
703
|
+
return "yesterday";
|
|
704
|
+
if (ms < 7 * DAY)
|
|
705
|
+
return `${Math.floor(ms / DAY)}d ago`;
|
|
706
|
+
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// src/tui/screens/Exit.tsx
|
|
710
|
+
import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
|
|
992
711
|
var OPTIONS = [
|
|
993
712
|
{ action: "save", label: "Save", hint: "Keep session paused for later" },
|
|
994
713
|
{
|
|
@@ -1008,11 +727,11 @@ var OPTIONS = [
|
|
|
1008
727
|
}
|
|
1009
728
|
];
|
|
1010
729
|
function Exit({ sessionId, onAction }) {
|
|
1011
|
-
const { exit } =
|
|
1012
|
-
const [cursor, setCursor] =
|
|
730
|
+
const { exit } = useTui3();
|
|
731
|
+
const [cursor, setCursor] = useState3(0);
|
|
1013
732
|
const session = getSession(sessionId);
|
|
1014
733
|
const conversations2 = session ? getConversationsBySession(session.id) : [];
|
|
1015
|
-
|
|
734
|
+
useInput3((e) => {
|
|
1016
735
|
if (e.key === "c" && e.ctrl)
|
|
1017
736
|
exit();
|
|
1018
737
|
if (e.key === "up" || e.key === "k") {
|
|
@@ -1029,37 +748,37 @@ function Exit({ sessionId, onAction }) {
|
|
|
1029
748
|
}
|
|
1030
749
|
});
|
|
1031
750
|
if (!session) {
|
|
1032
|
-
return /* @__PURE__ */
|
|
751
|
+
return /* @__PURE__ */ jsxDEV7(Text6, {
|
|
1033
752
|
color: "red",
|
|
1034
753
|
children: "Session not found"
|
|
1035
754
|
}, undefined, false, undefined, this);
|
|
1036
755
|
}
|
|
1037
756
|
const duration = session.endedAt && session.startedAt ? formatDuration(new Date(session.endedAt).getTime() - new Date(session.startedAt).getTime()) : null;
|
|
1038
|
-
return /* @__PURE__ */
|
|
757
|
+
return /* @__PURE__ */ jsxDEV7(Box6, {
|
|
1039
758
|
flexDirection: "column",
|
|
1040
759
|
padding: 1,
|
|
1041
760
|
gap: 1,
|
|
1042
761
|
children: [
|
|
1043
|
-
/* @__PURE__ */
|
|
762
|
+
/* @__PURE__ */ jsxDEV7(Text6, {
|
|
1044
763
|
bold: true,
|
|
1045
764
|
color: "#82AAFF",
|
|
1046
765
|
children: "Session ended"
|
|
1047
766
|
}, undefined, false, undefined, this),
|
|
1048
|
-
/* @__PURE__ */
|
|
767
|
+
/* @__PURE__ */ jsxDEV7(Box6, {
|
|
1049
768
|
flexDirection: "column",
|
|
1050
769
|
children: [
|
|
1051
|
-
/* @__PURE__ */
|
|
770
|
+
/* @__PURE__ */ jsxDEV7(Box6, {
|
|
1052
771
|
flexDirection: "row",
|
|
1053
772
|
gap: 1,
|
|
1054
773
|
children: [
|
|
1055
|
-
/* @__PURE__ */
|
|
774
|
+
/* @__PURE__ */ jsxDEV7(StatusDot, {
|
|
1056
775
|
status: session.status
|
|
1057
776
|
}, undefined, false, undefined, this),
|
|
1058
|
-
/* @__PURE__ */
|
|
777
|
+
/* @__PURE__ */ jsxDEV7(Text6, {
|
|
1059
778
|
bold: true,
|
|
1060
779
|
children: session.name
|
|
1061
780
|
}, undefined, false, undefined, this),
|
|
1062
|
-
duration && /* @__PURE__ */
|
|
781
|
+
duration && /* @__PURE__ */ jsxDEV7(Text6, {
|
|
1063
782
|
dim: true,
|
|
1064
783
|
children: [
|
|
1065
784
|
"(",
|
|
@@ -1069,7 +788,7 @@ function Exit({ sessionId, onAction }) {
|
|
|
1069
788
|
}, undefined, true, undefined, this)
|
|
1070
789
|
]
|
|
1071
790
|
}, undefined, true, undefined, this),
|
|
1072
|
-
/* @__PURE__ */
|
|
791
|
+
/* @__PURE__ */ jsxDEV7(Text6, {
|
|
1073
792
|
dim: true,
|
|
1074
793
|
children: [
|
|
1075
794
|
conversations2.length,
|
|
@@ -1079,28 +798,28 @@ function Exit({ sessionId, onAction }) {
|
|
|
1079
798
|
}, undefined, true, undefined, this)
|
|
1080
799
|
]
|
|
1081
800
|
}, undefined, true, undefined, this),
|
|
1082
|
-
/* @__PURE__ */
|
|
801
|
+
/* @__PURE__ */ jsxDEV7(Box6, {
|
|
1083
802
|
flexDirection: "column",
|
|
1084
|
-
children: OPTIONS.map((opt, i) => /* @__PURE__ */
|
|
803
|
+
children: OPTIONS.map((opt, i) => /* @__PURE__ */ jsxDEV7(Box6, {
|
|
1085
804
|
flexDirection: "row",
|
|
1086
805
|
gap: 1,
|
|
1087
806
|
children: [
|
|
1088
|
-
/* @__PURE__ */
|
|
807
|
+
/* @__PURE__ */ jsxDEV7(Text6, {
|
|
1089
808
|
children: i === cursor ? "\u276F" : " "
|
|
1090
809
|
}, undefined, false, undefined, this),
|
|
1091
|
-
/* @__PURE__ */
|
|
810
|
+
/* @__PURE__ */ jsxDEV7(Text6, {
|
|
1092
811
|
bold: i === cursor,
|
|
1093
812
|
children: opt.label
|
|
1094
813
|
}, undefined, false, undefined, this),
|
|
1095
|
-
/* @__PURE__ */
|
|
814
|
+
/* @__PURE__ */ jsxDEV7(Text6, {
|
|
1096
815
|
dim: true,
|
|
1097
816
|
children: opt.hint
|
|
1098
817
|
}, undefined, false, undefined, this)
|
|
1099
818
|
]
|
|
1100
819
|
}, opt.action, true, undefined, this))
|
|
1101
820
|
}, undefined, false, undefined, this),
|
|
1102
|
-
/* @__PURE__ */
|
|
1103
|
-
children: /* @__PURE__ */
|
|
821
|
+
/* @__PURE__ */ jsxDEV7(Box6, {
|
|
822
|
+
children: /* @__PURE__ */ jsxDEV7(Text6, {
|
|
1104
823
|
dim: true,
|
|
1105
824
|
children: "\u2191\u2193 navigate \xB7 enter select \xB7 q save & quit"
|
|
1106
825
|
}, undefined, false, undefined, this)
|
|
@@ -1110,12 +829,12 @@ function Exit({ sessionId, onAction }) {
|
|
|
1110
829
|
}
|
|
1111
830
|
|
|
1112
831
|
// src/tui/screens/Resume.tsx
|
|
1113
|
-
import { useState as
|
|
1114
|
-
import { Box as
|
|
1115
|
-
import { jsxDEV as
|
|
832
|
+
import { useState as useState4 } from "react";
|
|
833
|
+
import { Box as Box7, Text as Text7, useInput as useInput4, useTui as useTui4 } from "@orchetron/storm";
|
|
834
|
+
import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
|
|
1116
835
|
function Resume({ sessionId, onSelect }) {
|
|
1117
|
-
const { exit } =
|
|
1118
|
-
const [cursor, setCursor] =
|
|
836
|
+
const { exit } = useTui4();
|
|
837
|
+
const [cursor, setCursor] = useState4(0);
|
|
1119
838
|
const session = getSession(sessionId);
|
|
1120
839
|
const conversations2 = getConversationsBySession(sessionId);
|
|
1121
840
|
const totalOptions = conversations2.length + 1;
|
|
@@ -1123,7 +842,7 @@ function Resume({ sessionId, onSelect }) {
|
|
|
1123
842
|
onSelect(selection);
|
|
1124
843
|
exit();
|
|
1125
844
|
};
|
|
1126
|
-
|
|
845
|
+
useInput4((e) => {
|
|
1127
846
|
if (e.key === "c" && e.ctrl)
|
|
1128
847
|
select({ type: "back" });
|
|
1129
848
|
if (e.key === "up" || e.key === "k") {
|
|
@@ -1142,17 +861,17 @@ function Resume({ sessionId, onSelect }) {
|
|
|
1142
861
|
}
|
|
1143
862
|
});
|
|
1144
863
|
if (!session) {
|
|
1145
|
-
return /* @__PURE__ */
|
|
864
|
+
return /* @__PURE__ */ jsxDEV8(Text7, {
|
|
1146
865
|
color: "red",
|
|
1147
866
|
children: "Session not found"
|
|
1148
867
|
}, undefined, false, undefined, this);
|
|
1149
868
|
}
|
|
1150
|
-
return /* @__PURE__ */
|
|
869
|
+
return /* @__PURE__ */ jsxDEV8(Box7, {
|
|
1151
870
|
flexDirection: "column",
|
|
1152
871
|
padding: 1,
|
|
1153
872
|
gap: 1,
|
|
1154
873
|
children: [
|
|
1155
|
-
/* @__PURE__ */
|
|
874
|
+
/* @__PURE__ */ jsxDEV8(Text7, {
|
|
1156
875
|
bold: true,
|
|
1157
876
|
color: "#82AAFF",
|
|
1158
877
|
children: [
|
|
@@ -1160,17 +879,17 @@ function Resume({ sessionId, onSelect }) {
|
|
|
1160
879
|
session.name
|
|
1161
880
|
]
|
|
1162
881
|
}, undefined, true, undefined, this),
|
|
1163
|
-
/* @__PURE__ */
|
|
882
|
+
/* @__PURE__ */ jsxDEV8(Box7, {
|
|
1164
883
|
flexDirection: "column",
|
|
1165
884
|
children: [
|
|
1166
|
-
/* @__PURE__ */
|
|
885
|
+
/* @__PURE__ */ jsxDEV8(Box7, {
|
|
1167
886
|
flexDirection: "row",
|
|
1168
887
|
gap: 1,
|
|
1169
888
|
children: [
|
|
1170
|
-
/* @__PURE__ */
|
|
889
|
+
/* @__PURE__ */ jsxDEV8(Text7, {
|
|
1171
890
|
children: cursor === 0 ? "\u276F" : " "
|
|
1172
891
|
}, undefined, false, undefined, this),
|
|
1173
|
-
/* @__PURE__ */
|
|
892
|
+
/* @__PURE__ */ jsxDEV8(Text7, {
|
|
1174
893
|
bold: cursor === 0,
|
|
1175
894
|
color: "#34D399",
|
|
1176
895
|
children: "+ New conversation"
|
|
@@ -1182,42 +901,42 @@ function Resume({ sessionId, onSelect }) {
|
|
|
1182
901
|
const isSelected = cursor === idx;
|
|
1183
902
|
const duration = conv.endedAt ? formatDuration(new Date(conv.endedAt).getTime() - new Date(conv.startedAt).getTime()) : "active";
|
|
1184
903
|
const ago = formatAgo(conv.startedAt);
|
|
1185
|
-
return /* @__PURE__ */
|
|
904
|
+
return /* @__PURE__ */ jsxDEV8(Box7, {
|
|
1186
905
|
flexDirection: "row",
|
|
1187
906
|
gap: 1,
|
|
1188
907
|
children: [
|
|
1189
|
-
/* @__PURE__ */
|
|
908
|
+
/* @__PURE__ */ jsxDEV8(Text7, {
|
|
1190
909
|
children: isSelected ? "\u276F" : " "
|
|
1191
910
|
}, undefined, false, undefined, this),
|
|
1192
|
-
/* @__PURE__ */
|
|
911
|
+
/* @__PURE__ */ jsxDEV8(Text7, {
|
|
1193
912
|
bold: isSelected,
|
|
1194
913
|
dim: conv.discarded,
|
|
1195
914
|
children: conv.id.slice(0, 8)
|
|
1196
915
|
}, undefined, false, undefined, this),
|
|
1197
|
-
/* @__PURE__ */
|
|
916
|
+
/* @__PURE__ */ jsxDEV8(Text7, {
|
|
1198
917
|
dim: true,
|
|
1199
918
|
children: ago
|
|
1200
919
|
}, undefined, false, undefined, this),
|
|
1201
|
-
/* @__PURE__ */
|
|
920
|
+
/* @__PURE__ */ jsxDEV8(Text7, {
|
|
1202
921
|
dim: true,
|
|
1203
922
|
children: "\xB7"
|
|
1204
923
|
}, undefined, false, undefined, this),
|
|
1205
|
-
/* @__PURE__ */
|
|
924
|
+
/* @__PURE__ */ jsxDEV8(Text7, {
|
|
1206
925
|
dim: true,
|
|
1207
926
|
children: [
|
|
1208
927
|
conv.eventCount,
|
|
1209
928
|
" events"
|
|
1210
929
|
]
|
|
1211
930
|
}, undefined, true, undefined, this),
|
|
1212
|
-
/* @__PURE__ */
|
|
931
|
+
/* @__PURE__ */ jsxDEV8(Text7, {
|
|
1213
932
|
dim: true,
|
|
1214
933
|
children: "\xB7"
|
|
1215
934
|
}, undefined, false, undefined, this),
|
|
1216
|
-
/* @__PURE__ */
|
|
935
|
+
/* @__PURE__ */ jsxDEV8(Text7, {
|
|
1217
936
|
dim: true,
|
|
1218
937
|
children: duration
|
|
1219
938
|
}, undefined, false, undefined, this),
|
|
1220
|
-
conv.discarded && /* @__PURE__ */
|
|
939
|
+
conv.discarded && /* @__PURE__ */ jsxDEV8(Text7, {
|
|
1221
940
|
color: "red",
|
|
1222
941
|
dim: true,
|
|
1223
942
|
children: "(discarded)"
|
|
@@ -1227,8 +946,8 @@ function Resume({ sessionId, onSelect }) {
|
|
|
1227
946
|
})
|
|
1228
947
|
]
|
|
1229
948
|
}, undefined, true, undefined, this),
|
|
1230
|
-
/* @__PURE__ */
|
|
1231
|
-
children: /* @__PURE__ */
|
|
949
|
+
/* @__PURE__ */ jsxDEV8(Box7, {
|
|
950
|
+
children: /* @__PURE__ */ jsxDEV8(Text7, {
|
|
1232
951
|
dim: true,
|
|
1233
952
|
children: "\u2191\u2193 navigate \xB7 enter select \xB7 q back"
|
|
1234
953
|
}, undefined, false, undefined, this)
|
|
@@ -1238,7 +957,7 @@ function Resume({ sessionId, onSelect }) {
|
|
|
1238
957
|
}
|
|
1239
958
|
|
|
1240
959
|
// src/tui/run-screen.tsx
|
|
1241
|
-
import { jsxDEV as
|
|
960
|
+
import { jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime";
|
|
1242
961
|
var [, , screen, outputPath, ...args] = process.argv;
|
|
1243
962
|
if (!screen || !outputPath) {
|
|
1244
963
|
console.error("Usage: run-screen <screen> <outputPath> [args...]");
|
|
@@ -1248,7 +967,7 @@ var result;
|
|
|
1248
967
|
switch (screen) {
|
|
1249
968
|
case "launch": {
|
|
1250
969
|
let selection = { type: "quit" };
|
|
1251
|
-
const app = render(/* @__PURE__ */
|
|
970
|
+
const app = render(/* @__PURE__ */ jsxDEV9(Launch, {
|
|
1252
971
|
onSelect: (s) => {
|
|
1253
972
|
selection = s;
|
|
1254
973
|
}
|
|
@@ -1265,7 +984,7 @@ switch (screen) {
|
|
|
1265
984
|
process.exit(1);
|
|
1266
985
|
}
|
|
1267
986
|
let action = "save";
|
|
1268
|
-
const app = render(/* @__PURE__ */
|
|
987
|
+
const app = render(/* @__PURE__ */ jsxDEV9(Exit, {
|
|
1269
988
|
sessionId,
|
|
1270
989
|
onAction: (a) => {
|
|
1271
990
|
action = a;
|
|
@@ -1283,7 +1002,7 @@ switch (screen) {
|
|
|
1283
1002
|
process.exit(1);
|
|
1284
1003
|
}
|
|
1285
1004
|
let selection = { type: "back" };
|
|
1286
|
-
const app = render(/* @__PURE__ */
|
|
1005
|
+
const app = render(/* @__PURE__ */ jsxDEV9(Resume, {
|
|
1287
1006
|
sessionId,
|
|
1288
1007
|
onSelect: (s) => {
|
|
1289
1008
|
selection = s;
|