recappi 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1220 -220
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -18,22 +18,20 @@ function Header({ active }) {
|
|
|
18
18
|
"Recappi",
|
|
19
19
|
" "
|
|
20
20
|
] }),
|
|
21
|
-
/* @__PURE__ */ jsx(Tab, { num: "1", label: "Overview", active: active === "overview"
|
|
22
|
-
/* @__PURE__ */ jsx(Tab, { num: "2", label: "Jobs", active: active === "jobs"
|
|
23
|
-
/* @__PURE__ */ jsx(Tab, { num: "3", label: "Recordings", active: active === "recordings", enabled: false })
|
|
21
|
+
/* @__PURE__ */ jsx(Tab, { num: "1", label: "Overview", active: active === "overview" }),
|
|
22
|
+
/* @__PURE__ */ jsx(Tab, { num: "2", label: "Jobs", active: active === "jobs" })
|
|
24
23
|
] });
|
|
25
24
|
}
|
|
26
25
|
function Tab({
|
|
27
26
|
num,
|
|
28
27
|
label,
|
|
29
|
-
active
|
|
30
|
-
enabled
|
|
28
|
+
active
|
|
31
29
|
}) {
|
|
32
|
-
const text = ` ${num} ${label}
|
|
30
|
+
const text = ` ${num} ${label} `;
|
|
33
31
|
if (active) {
|
|
34
|
-
return /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: text });
|
|
32
|
+
return /* @__PURE__ */ jsx(Text, { bold: true, inverse: true, color: "cyan", children: text });
|
|
35
33
|
}
|
|
36
|
-
return /* @__PURE__ */ jsx(Text, {
|
|
34
|
+
return /* @__PURE__ */ jsx(Text, { children: text });
|
|
37
35
|
}
|
|
38
36
|
function Footer({ keys }) {
|
|
39
37
|
return /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: keys }) });
|
|
@@ -80,6 +78,23 @@ function statusStyle(status) {
|
|
|
80
78
|
return { label: status, color: "white" };
|
|
81
79
|
}
|
|
82
80
|
}
|
|
81
|
+
function spinnerChar(frame) {
|
|
82
|
+
return SPINNER_FRAMES[frame % SPINNER_FRAMES.length];
|
|
83
|
+
}
|
|
84
|
+
function dateBucket(epochMs, nowMs) {
|
|
85
|
+
if (!epochMs) return "Earlier";
|
|
86
|
+
const startOfDay = (ms) => {
|
|
87
|
+
const d = new Date(ms);
|
|
88
|
+
d.setHours(0, 0, 0, 0);
|
|
89
|
+
return d.getTime();
|
|
90
|
+
};
|
|
91
|
+
const days = Math.floor((startOfDay(nowMs) - startOfDay(epochMs)) / 864e5);
|
|
92
|
+
if (days <= 0) return "Today";
|
|
93
|
+
if (days === 1) return "Yesterday";
|
|
94
|
+
if (days < 7) return "Previous 7 days";
|
|
95
|
+
if (days < 30) return "Previous 30 days";
|
|
96
|
+
return "Earlier";
|
|
97
|
+
}
|
|
83
98
|
function statusGlyph(status, spinnerFrame) {
|
|
84
99
|
switch (status) {
|
|
85
100
|
case "running":
|
|
@@ -114,6 +129,45 @@ function padCell(text, width) {
|
|
|
114
129
|
if (text.length > width) return `${text.slice(0, Math.max(0, width - 1))}\u2026`;
|
|
115
130
|
return text.padEnd(width);
|
|
116
131
|
}
|
|
132
|
+
function charWidth(code) {
|
|
133
|
+
if (code >= 4352 && code <= 4447 || // Hangul Jamo
|
|
134
|
+
code === 9001 || code === 9002 || code >= 11904 && code <= 12350 || // CJK radicals … Kangxi
|
|
135
|
+
code >= 12353 && code <= 13311 || // Hiragana … CJK symbols
|
|
136
|
+
code >= 13312 && code <= 19903 || // CJK ext A
|
|
137
|
+
code >= 19968 && code <= 40959 || // CJK unified
|
|
138
|
+
code >= 40960 && code <= 42191 || // Yi
|
|
139
|
+
code >= 44032 && code <= 55203 || // Hangul syllables
|
|
140
|
+
code >= 63744 && code <= 64255 || // CJK compat
|
|
141
|
+
code >= 65072 && code <= 65103 || // CJK compat forms
|
|
142
|
+
code >= 65280 && code <= 65376 || // Fullwidth forms
|
|
143
|
+
code >= 65504 && code <= 65510 || code >= 127744 && code <= 129791 || // emoji
|
|
144
|
+
code >= 131072 && code <= 262141) {
|
|
145
|
+
return 2;
|
|
146
|
+
}
|
|
147
|
+
return 1;
|
|
148
|
+
}
|
|
149
|
+
function displayWidth(text) {
|
|
150
|
+
let width = 0;
|
|
151
|
+
for (const ch of text) width += charWidth(ch.codePointAt(0) ?? 0);
|
|
152
|
+
return width;
|
|
153
|
+
}
|
|
154
|
+
function padDisplay(text, width) {
|
|
155
|
+
const w = displayWidth(text);
|
|
156
|
+
if (w === width) return text;
|
|
157
|
+
if (w < width) return text + " ".repeat(width - w);
|
|
158
|
+
let out = "";
|
|
159
|
+
let acc = 0;
|
|
160
|
+
for (const ch of text) {
|
|
161
|
+
const cw = charWidth(ch.codePointAt(0) ?? 0);
|
|
162
|
+
if (acc + cw > width - 1) break;
|
|
163
|
+
out += ch;
|
|
164
|
+
acc += cw;
|
|
165
|
+
}
|
|
166
|
+
out += "\u2026";
|
|
167
|
+
acc += 1;
|
|
168
|
+
if (acc < width) out += " ".repeat(width - acc);
|
|
169
|
+
return out;
|
|
170
|
+
}
|
|
117
171
|
function countJobs(items) {
|
|
118
172
|
const counts = {
|
|
119
173
|
total: items.length,
|
|
@@ -151,6 +205,60 @@ function resolveJobLinks(item, origin) {
|
|
|
151
205
|
}
|
|
152
206
|
return {};
|
|
153
207
|
}
|
|
208
|
+
function resolveRecordingLinks(recordingId, origin) {
|
|
209
|
+
if (!recordingId) return {};
|
|
210
|
+
return { webUrl: `${origin}/recordings/${recordingId}` };
|
|
211
|
+
}
|
|
212
|
+
function recordingStatusStyle(status) {
|
|
213
|
+
switch (status) {
|
|
214
|
+
case "ready":
|
|
215
|
+
return { label: "Ready", color: "green", glyph: "\u2713" };
|
|
216
|
+
case "uploading":
|
|
217
|
+
return { label: "Uploading", color: "cyan", glyph: "\u2191" };
|
|
218
|
+
case "failed":
|
|
219
|
+
return { label: "Failed", color: "red", glyph: "\u2717" };
|
|
220
|
+
case "aborted":
|
|
221
|
+
return { label: "Aborted", color: "gray", glyph: "\u2022" };
|
|
222
|
+
default:
|
|
223
|
+
return { label: status, color: "white", glyph: "\u2022" };
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function listWindow(selected, total, size) {
|
|
227
|
+
if (size <= 0 || total <= 0) return { start: 0, end: 0 };
|
|
228
|
+
if (total <= size) return { start: 0, end: total };
|
|
229
|
+
let start = selected - Math.floor(size / 2);
|
|
230
|
+
start = Math.max(0, Math.min(start, total - size));
|
|
231
|
+
return { start, end: start + size };
|
|
232
|
+
}
|
|
233
|
+
function groupedListWindow(buckets, selected, budget) {
|
|
234
|
+
const total = buckets.length;
|
|
235
|
+
if (budget <= 0 || total <= 0) return { start: 0, end: 0 };
|
|
236
|
+
const cost = (start, end) => {
|
|
237
|
+
if (end <= start) return 0;
|
|
238
|
+
let boundaries = 0;
|
|
239
|
+
for (let i = start + 1; i < end; i++) {
|
|
240
|
+
if (buckets[i] !== buckets[i - 1]) boundaries += 1;
|
|
241
|
+
}
|
|
242
|
+
return end - start + 1 + boundaries * 2;
|
|
243
|
+
};
|
|
244
|
+
for (let n = Math.min(total, budget); n >= 1; n--) {
|
|
245
|
+
const win = listWindow(selected, total, n);
|
|
246
|
+
if (cost(win.start, win.end) <= budget) return win;
|
|
247
|
+
}
|
|
248
|
+
return listWindow(selected, total, 1);
|
|
249
|
+
}
|
|
250
|
+
function formatBytes2(bytes) {
|
|
251
|
+
if (bytes == null || !Number.isFinite(bytes) || bytes < 0) return "";
|
|
252
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
253
|
+
let value = bytes;
|
|
254
|
+
let unit = 0;
|
|
255
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
256
|
+
value /= 1024;
|
|
257
|
+
unit += 1;
|
|
258
|
+
}
|
|
259
|
+
const rounded = value < 10 && unit > 0 ? value.toFixed(1) : String(Math.round(value));
|
|
260
|
+
return `${rounded}${units[unit]}`;
|
|
261
|
+
}
|
|
154
262
|
var SPINNER_FRAMES;
|
|
155
263
|
var init_format = __esm({
|
|
156
264
|
"src/tui/format.ts"() {
|
|
@@ -212,83 +320,231 @@ var init_JobsView = __esm({
|
|
|
212
320
|
}
|
|
213
321
|
});
|
|
214
322
|
|
|
215
|
-
// src/tui/
|
|
323
|
+
// src/tui/RecordingRow.tsx
|
|
216
324
|
import { Box as Box4, Text as Text4 } from "ink";
|
|
217
325
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
218
|
-
function
|
|
219
|
-
|
|
326
|
+
function recordingTitle2(item) {
|
|
327
|
+
const named = (item.title || item.summaryTitle || "").trim();
|
|
328
|
+
if (named && !UUID_RE.test(named)) return named;
|
|
329
|
+
return "Untitled";
|
|
330
|
+
}
|
|
331
|
+
function recordingProcessingState(item, jobStatus, spinnerFrame) {
|
|
332
|
+
if (item.status === "uploading") return { glyph: "\u2191", color: "cyan" };
|
|
333
|
+
if (item.status === "failed" || jobStatus === "failed") return { glyph: "\u2717", color: "red" };
|
|
334
|
+
if (jobStatus === "running") return { glyph: spinnerChar(spinnerFrame), color: "cyan" };
|
|
335
|
+
if (jobStatus === "queued") return { glyph: "\u25CB", color: "yellow" };
|
|
336
|
+
if (item.status === "aborted") return { glyph: "\u2022", color: "gray" };
|
|
337
|
+
if (item.activeTranscriptId) return { glyph: "\u2713", color: "green" };
|
|
338
|
+
return { glyph: "\xB7", color: "gray" };
|
|
339
|
+
}
|
|
340
|
+
function recordingLayout(columns) {
|
|
341
|
+
const usable = Math.max(20, columns - 2);
|
|
342
|
+
const showWhen = usable >= 54;
|
|
343
|
+
const title = Math.max(
|
|
344
|
+
10,
|
|
345
|
+
usable - MARKER_W - GLYPH_W - LENGTH_W - (showWhen ? WHEN_W : 0)
|
|
346
|
+
);
|
|
347
|
+
return { title, showWhen };
|
|
220
348
|
}
|
|
221
|
-
function
|
|
349
|
+
function RecordingRow({
|
|
350
|
+
item,
|
|
351
|
+
selected,
|
|
352
|
+
nowMs,
|
|
353
|
+
columns,
|
|
354
|
+
jobStatus,
|
|
355
|
+
spinnerFrame = 0
|
|
356
|
+
}) {
|
|
357
|
+
const { title, showWhen } = recordingLayout(columns);
|
|
358
|
+
const { glyph, color } = recordingProcessingState(item, jobStatus, spinnerFrame);
|
|
359
|
+
const duration3 = item.durationMs ? formatClockMs(item.durationMs) : "\u2014";
|
|
360
|
+
return /* @__PURE__ */ jsxs3(Box4, { children: [
|
|
361
|
+
/* @__PURE__ */ jsx4(Text4, { color: "cyan", children: selected ? "\u25B8 " : " " }),
|
|
362
|
+
/* @__PURE__ */ jsx4(Text4, { color, children: `${glyph} ` }),
|
|
363
|
+
/* @__PURE__ */ jsx4(Text4, { bold: selected, children: padDisplay(recordingTitle2(item), title) }),
|
|
364
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: padDisplay(duration3, LENGTH_W) }),
|
|
365
|
+
showWhen ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: formatAge(item.createdAt, nowMs) }) : null
|
|
366
|
+
] });
|
|
367
|
+
}
|
|
368
|
+
function RecordingHeader({ columns }) {
|
|
369
|
+
const { title, showWhen } = recordingLayout(columns);
|
|
370
|
+
return /* @__PURE__ */ jsxs3(Box4, { children: [
|
|
371
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: padDisplay("", MARKER_W + GLYPH_W) }),
|
|
372
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: padDisplay("TITLE", title) }),
|
|
373
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: padDisplay("LENGTH", LENGTH_W) }),
|
|
374
|
+
showWhen ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "WHEN" }) : null
|
|
375
|
+
] });
|
|
376
|
+
}
|
|
377
|
+
var UUID_RE, MARKER_W, GLYPH_W, LENGTH_W, WHEN_W;
|
|
378
|
+
var init_RecordingRow = __esm({
|
|
379
|
+
"src/tui/RecordingRow.tsx"() {
|
|
380
|
+
"use strict";
|
|
381
|
+
init_format();
|
|
382
|
+
UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
383
|
+
MARKER_W = 2;
|
|
384
|
+
GLYPH_W = 2;
|
|
385
|
+
LENGTH_W = 9;
|
|
386
|
+
WHEN_W = 9;
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// src/tui/RecordingsView.tsx
|
|
391
|
+
import React from "react";
|
|
392
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
393
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
394
|
+
function RecordingsView({
|
|
222
395
|
items,
|
|
223
396
|
selectedIndex,
|
|
224
|
-
|
|
397
|
+
nowMs,
|
|
398
|
+
columns,
|
|
399
|
+
jobStatusByRecording,
|
|
400
|
+
spinnerFrame = 0
|
|
401
|
+
}) {
|
|
402
|
+
if (items.length === 0) {
|
|
403
|
+
return /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "No recordings yet \u2014 run: recappi upload <file>" }) });
|
|
404
|
+
}
|
|
405
|
+
return /* @__PURE__ */ jsxs4(Box5, { marginTop: 1, flexDirection: "column", children: [
|
|
406
|
+
/* @__PURE__ */ jsx5(RecordingHeader, { columns }),
|
|
407
|
+
items.map((item, index) => {
|
|
408
|
+
const bucket = dateBucket(item.createdAt, nowMs);
|
|
409
|
+
const showHeader = index === 0 || bucket !== dateBucket(items[index - 1].createdAt, nowMs);
|
|
410
|
+
return /* @__PURE__ */ jsxs4(React.Fragment, { children: [
|
|
411
|
+
showHeader ? /* @__PURE__ */ jsx5(Box5, { marginTop: index === 0 ? 0 : 1, children: /* @__PURE__ */ jsx5(Text5, { bold: true, color: "blue", children: bucket }) }) : null,
|
|
412
|
+
/* @__PURE__ */ jsx5(
|
|
413
|
+
RecordingRow,
|
|
414
|
+
{
|
|
415
|
+
item,
|
|
416
|
+
selected: index === selectedIndex,
|
|
417
|
+
nowMs,
|
|
418
|
+
columns,
|
|
419
|
+
jobStatus: jobStatusByRecording?.get(item.recordingId),
|
|
420
|
+
spinnerFrame
|
|
421
|
+
}
|
|
422
|
+
)
|
|
423
|
+
] }, item.recordingId);
|
|
424
|
+
})
|
|
425
|
+
] });
|
|
426
|
+
}
|
|
427
|
+
var init_RecordingsView = __esm({
|
|
428
|
+
"src/tui/RecordingsView.tsx"() {
|
|
429
|
+
"use strict";
|
|
430
|
+
init_RecordingRow();
|
|
431
|
+
init_format();
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
// src/tui/RecordingPeek.tsx
|
|
436
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
437
|
+
import { Fragment, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
438
|
+
function RecordingPeek({
|
|
439
|
+
item,
|
|
440
|
+
summary,
|
|
441
|
+
nowMs,
|
|
442
|
+
width
|
|
443
|
+
}) {
|
|
444
|
+
return /* @__PURE__ */ jsx6(Box6, { width, borderStyle: "round", borderColor: "gray", paddingX: 1, flexDirection: "column", children: !item ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "No selection" }) : /* @__PURE__ */ jsx6(PeekBody, { item, summary, nowMs }) });
|
|
445
|
+
}
|
|
446
|
+
function PeekBody({
|
|
447
|
+
item,
|
|
448
|
+
summary,
|
|
225
449
|
nowMs
|
|
226
450
|
}) {
|
|
227
|
-
const
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
451
|
+
const style = recordingStatusStyle(item.status);
|
|
452
|
+
const meta3 = [
|
|
453
|
+
item.durationMs ? formatClockMs(item.durationMs) : null,
|
|
454
|
+
formatBytes2(item.sizeBytes) || null,
|
|
455
|
+
item.contentType || null
|
|
456
|
+
].filter(Boolean).join(" \xB7 ");
|
|
457
|
+
return /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
458
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, color: "magenta", wrap: "truncate-end", children: recordingTitle2(item) }),
|
|
459
|
+
/* @__PURE__ */ jsx6(Text6, { color: style.color, children: `${style.glyph} ${style.label}` }),
|
|
460
|
+
meta3 ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: meta3 }) : null,
|
|
461
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: formatAge(item.createdAt, nowMs) }),
|
|
462
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx6(SummarySection, { item, summary }) }),
|
|
463
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\u23CE open \xB7 t transcript \xB7 o web" }) })
|
|
464
|
+
] });
|
|
465
|
+
}
|
|
466
|
+
function SummarySection({
|
|
467
|
+
item,
|
|
468
|
+
summary
|
|
469
|
+
}) {
|
|
470
|
+
if (!item.activeTranscriptId) return /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "No transcript yet" });
|
|
471
|
+
if (summary === "loading" || summary === void 0) return /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Loading summary\u2026" });
|
|
472
|
+
if (summary === "error") return /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "(summary unavailable)" });
|
|
473
|
+
if (summary.status !== "succeeded" || !summary.tldr) {
|
|
474
|
+
return /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: `Summary ${summary.status}` });
|
|
475
|
+
}
|
|
476
|
+
const points = (summary.keyPoints ?? []).slice(0, 3);
|
|
477
|
+
return /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
478
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "Summary" }),
|
|
479
|
+
/* @__PURE__ */ jsx6(Text6, { children: summary.tldr }),
|
|
480
|
+
points.length > 0 ? /* @__PURE__ */ jsx6(Box6, { marginTop: 1, flexDirection: "column", children: points.map((point, i) => /* @__PURE__ */ jsx6(Text6, { dimColor: true, wrap: "truncate-end", children: `\u2022 ${point}` }, i)) }) : null
|
|
481
|
+
] });
|
|
482
|
+
}
|
|
483
|
+
var init_RecordingPeek = __esm({
|
|
484
|
+
"src/tui/RecordingPeek.tsx"() {
|
|
485
|
+
"use strict";
|
|
486
|
+
init_format();
|
|
487
|
+
init_RecordingRow();
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
// src/tui/OverviewView.tsx
|
|
492
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
493
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
494
|
+
function OverviewView({
|
|
495
|
+
recordings,
|
|
496
|
+
jobs,
|
|
497
|
+
stats,
|
|
498
|
+
selectedIndex,
|
|
499
|
+
spinnerFrame,
|
|
500
|
+
nowMs,
|
|
501
|
+
columns,
|
|
502
|
+
jobStatusByRecording,
|
|
503
|
+
peekItem,
|
|
504
|
+
peekSummary,
|
|
505
|
+
showPeek = false,
|
|
506
|
+
peekWidth = 0
|
|
507
|
+
}) {
|
|
508
|
+
const jobCounts = countJobs(jobs);
|
|
509
|
+
const running = stats?.jobs.running ?? jobCounts.running;
|
|
510
|
+
const queued = stats?.jobs.queued ?? jobCounts.queued;
|
|
511
|
+
return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
|
|
512
|
+
/* @__PURE__ */ jsxs6(Box7, { children: [
|
|
513
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Recordings " }),
|
|
514
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, children: stats?.recordings.total ?? recordings.length }),
|
|
515
|
+
stats?.recordings.ready != null ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: ` \xB7 ${stats.recordings.ready} ready` }) : null,
|
|
516
|
+
stats?.recordings.totalDurationMs != null ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: ` \xB7 ${formatClockMs(stats.recordings.totalDurationMs)} transcribed` }) : null,
|
|
517
|
+
running > 0 ? /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: ` \xB7 ${running} transcribing` }) : null,
|
|
518
|
+
queued > 0 ? /* @__PURE__ */ jsx7(Text7, { color: "yellow", children: ` \xB7 ${queued} queued` }) : null
|
|
252
519
|
] }),
|
|
253
|
-
/* @__PURE__ */
|
|
254
|
-
/* @__PURE__ */
|
|
255
|
-
|
|
256
|
-
JobRow,
|
|
520
|
+
/* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", alignItems: "flex-start", children: [
|
|
521
|
+
/* @__PURE__ */ jsx7(Box7, { flexGrow: 1, flexDirection: "column", children: /* @__PURE__ */ jsx7(
|
|
522
|
+
RecordingsView,
|
|
257
523
|
{
|
|
258
|
-
|
|
259
|
-
|
|
524
|
+
items: recordings,
|
|
525
|
+
selectedIndex,
|
|
526
|
+
nowMs,
|
|
527
|
+
columns,
|
|
528
|
+
jobStatusByRecording,
|
|
260
529
|
spinnerFrame
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
))
|
|
264
|
-
] })
|
|
265
|
-
recent.length > 0 ? /* @__PURE__ */ jsxs3(Box4, { marginTop: 1, flexDirection: "column", children: [
|
|
266
|
-
/* @__PURE__ */ jsx4(Text4, { bold: true, children: "Recent" }),
|
|
267
|
-
recent.map((item) => {
|
|
268
|
-
const style = statusStyle(item.status);
|
|
269
|
-
const title = item.recording?.title ?? item.recordingId;
|
|
270
|
-
const age = formatAge(item.finishedAt ?? item.enqueuedAt, nowMs);
|
|
271
|
-
return /* @__PURE__ */ jsxs3(Box4, { children: [
|
|
272
|
-
/* @__PURE__ */ jsx4(Text4, { children: " " }),
|
|
273
|
-
/* @__PURE__ */ jsx4(Text4, { color: style.color, children: `${statusGlyph(item.status, 0)} ` }),
|
|
274
|
-
/* @__PURE__ */ jsx4(Text4, { children: title }),
|
|
275
|
-
age ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: ` ${age}` }) : null
|
|
276
|
-
] }, item.jobId);
|
|
277
|
-
})
|
|
278
|
-
] }) : null
|
|
530
|
+
}
|
|
531
|
+
) }),
|
|
532
|
+
showPeek ? /* @__PURE__ */ jsx7(Box7, { marginLeft: 1, marginTop: 1, children: /* @__PURE__ */ jsx7(RecordingPeek, { item: peekItem, summary: peekSummary, nowMs, width: peekWidth }) }) : null
|
|
533
|
+
] })
|
|
279
534
|
] });
|
|
280
535
|
}
|
|
281
536
|
var init_OverviewView = __esm({
|
|
282
537
|
"src/tui/OverviewView.tsx"() {
|
|
283
538
|
"use strict";
|
|
284
|
-
|
|
539
|
+
init_RecordingsView();
|
|
540
|
+
init_RecordingPeek();
|
|
285
541
|
init_format();
|
|
286
542
|
}
|
|
287
543
|
});
|
|
288
544
|
|
|
289
545
|
// src/tui/JobDetailView.tsx
|
|
290
|
-
import { Box as
|
|
291
|
-
import { jsx as
|
|
546
|
+
import { Box as Box8, Text as Text8 } from "ink";
|
|
547
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
292
548
|
function JobDetailView({
|
|
293
549
|
item,
|
|
294
550
|
origin,
|
|
@@ -298,13 +554,13 @@ function JobDetailView({
|
|
|
298
554
|
const style = statusStyle(item.status);
|
|
299
555
|
const links = resolveJobLinks(item, origin);
|
|
300
556
|
const title = item.recording?.title ?? item.recordingId;
|
|
301
|
-
return /* @__PURE__ */
|
|
302
|
-
/* @__PURE__ */
|
|
557
|
+
return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", paddingX: 1, children: [
|
|
558
|
+
/* @__PURE__ */ jsxs7(Text8, { dimColor: true, children: [
|
|
303
559
|
"\u2039 Jobs / ",
|
|
304
560
|
title
|
|
305
561
|
] }),
|
|
306
|
-
/* @__PURE__ */
|
|
307
|
-
|
|
562
|
+
/* @__PURE__ */ jsxs7(
|
|
563
|
+
Box8,
|
|
308
564
|
{
|
|
309
565
|
marginTop: 1,
|
|
310
566
|
borderStyle: "round",
|
|
@@ -312,17 +568,17 @@ function JobDetailView({
|
|
|
312
568
|
paddingX: 1,
|
|
313
569
|
flexDirection: "column",
|
|
314
570
|
children: [
|
|
315
|
-
/* @__PURE__ */
|
|
571
|
+
/* @__PURE__ */ jsxs7(Text8, { color: style.color, bold: true, children: [
|
|
316
572
|
style.label,
|
|
317
|
-
item.provider ? /* @__PURE__ */
|
|
573
|
+
item.provider ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: ` ${item.provider}` }) : null
|
|
318
574
|
] }),
|
|
319
|
-
/* @__PURE__ */
|
|
575
|
+
/* @__PURE__ */ jsx8(StatusLine, { item, spinnerFrame, nowMs })
|
|
320
576
|
]
|
|
321
577
|
}
|
|
322
578
|
),
|
|
323
|
-
/* @__PURE__ */
|
|
324
|
-
/* @__PURE__ */
|
|
325
|
-
/* @__PURE__ */
|
|
579
|
+
/* @__PURE__ */ jsxs7(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
580
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, children: "Timeline" }),
|
|
581
|
+
/* @__PURE__ */ jsx8(
|
|
326
582
|
TimelineRow,
|
|
327
583
|
{
|
|
328
584
|
label: "Enqueued",
|
|
@@ -331,7 +587,7 @@ function JobDetailView({
|
|
|
331
587
|
nowMs
|
|
332
588
|
}
|
|
333
589
|
),
|
|
334
|
-
/* @__PURE__ */
|
|
590
|
+
/* @__PURE__ */ jsx8(
|
|
335
591
|
TimelineRow,
|
|
336
592
|
{
|
|
337
593
|
label: "Started",
|
|
@@ -340,7 +596,7 @@ function JobDetailView({
|
|
|
340
596
|
nowMs
|
|
341
597
|
}
|
|
342
598
|
),
|
|
343
|
-
/* @__PURE__ */
|
|
599
|
+
/* @__PURE__ */ jsx8(
|
|
344
600
|
TimelineRow,
|
|
345
601
|
{
|
|
346
602
|
label: item.status === "failed" ? "Failed" : item.status === "running" ? "Transcribing" : "Finished",
|
|
@@ -352,21 +608,21 @@ function JobDetailView({
|
|
|
352
608
|
}
|
|
353
609
|
)
|
|
354
610
|
] }),
|
|
355
|
-
/* @__PURE__ */
|
|
356
|
-
/* @__PURE__ */
|
|
611
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs7(Text8, { children: [
|
|
612
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Recording " }),
|
|
357
613
|
title,
|
|
358
|
-
item.recording?.durationMs ? /* @__PURE__ */
|
|
614
|
+
item.recording?.durationMs ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: ` \xB7 ${formatClockMs(item.recording.durationMs)}` }) : null
|
|
359
615
|
] }) }),
|
|
360
|
-
/* @__PURE__ */
|
|
361
|
-
/* @__PURE__ */
|
|
362
|
-
/* @__PURE__ */
|
|
363
|
-
/* @__PURE__ */
|
|
364
|
-
/* @__PURE__ */
|
|
365
|
-
/* @__PURE__ */
|
|
366
|
-
/* @__PURE__ */
|
|
367
|
-
/* @__PURE__ */
|
|
616
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsxs7(Text8, { children: [
|
|
617
|
+
/* @__PURE__ */ jsx8(Text8, { color: links.webUrl ? "cyan" : "gray", children: "o open" }),
|
|
618
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: " \xB7 " }),
|
|
619
|
+
/* @__PURE__ */ jsx8(Text8, { color: links.webUrl ? "cyan" : "gray", children: "w web" }),
|
|
620
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: " \xB7 " }),
|
|
621
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "m mac app (soon)" }),
|
|
622
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: " \xB7 " }),
|
|
623
|
+
/* @__PURE__ */ jsx8(Text8, { color: links.webUrl ? "cyan" : "gray", children: "c copy" })
|
|
368
624
|
] }) }),
|
|
369
|
-
/* @__PURE__ */
|
|
625
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs7(Text8, { dimColor: true, children: [
|
|
370
626
|
"esc back \xB7 t transcript",
|
|
371
627
|
item.transcriptId ? "" : " (when ready)",
|
|
372
628
|
" \xB7 q quit"
|
|
@@ -383,24 +639,24 @@ function StatusLine({
|
|
|
383
639
|
const elapsed = item.startedAt ? ` \xB7 ${formatClockMs(nowMs - item.startedAt)} elapsed` : "";
|
|
384
640
|
if (fraction != null) {
|
|
385
641
|
const pct = Math.round(fraction * 100);
|
|
386
|
-
return /* @__PURE__ */
|
|
642
|
+
return /* @__PURE__ */ jsxs7(Text8, { children: [
|
|
387
643
|
`${progressBar(fraction)} ${pct}% ${formatClockMs(item.processedDurationMs)} / ${formatClockMs(
|
|
388
644
|
item.recording?.durationMs
|
|
389
645
|
)}`,
|
|
390
|
-
/* @__PURE__ */
|
|
646
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: elapsed })
|
|
391
647
|
] });
|
|
392
648
|
}
|
|
393
649
|
const spinner = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"][spinnerFrame % 10];
|
|
394
|
-
return /* @__PURE__ */
|
|
650
|
+
return /* @__PURE__ */ jsxs7(Text8, { children: [
|
|
395
651
|
`${spinner} transcribing\u2026`,
|
|
396
|
-
/* @__PURE__ */
|
|
652
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: elapsed })
|
|
397
653
|
] });
|
|
398
654
|
}
|
|
399
655
|
if (item.status === "succeeded")
|
|
400
|
-
return /* @__PURE__ */
|
|
401
|
-
if (item.status === "queued") return /* @__PURE__ */
|
|
402
|
-
if (item.status === "failed") return /* @__PURE__ */
|
|
403
|
-
return /* @__PURE__ */
|
|
656
|
+
return /* @__PURE__ */ jsx8(Text8, { children: item.transcriptId ? "transcript ready" : "done" });
|
|
657
|
+
if (item.status === "queued") return /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "waiting to start\u2026" });
|
|
658
|
+
if (item.status === "failed") return /* @__PURE__ */ jsx8(Text8, { color: "red", children: "transcription failed" });
|
|
659
|
+
return /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: item.status });
|
|
404
660
|
}
|
|
405
661
|
function TimelineRow({
|
|
406
662
|
label,
|
|
@@ -413,10 +669,10 @@ function TimelineRow({
|
|
|
413
669
|
const glyph = failed ? "\u2717" : done ? "\u2713" : running ? "\u280B" : "\u25CB";
|
|
414
670
|
const color = failed ? "red" : done ? "green" : running ? "cyan" : "gray";
|
|
415
671
|
const age = at ? formatAge(at, nowMs) : running ? "now" : "";
|
|
416
|
-
return /* @__PURE__ */
|
|
417
|
-
/* @__PURE__ */
|
|
418
|
-
/* @__PURE__ */
|
|
419
|
-
age ? /* @__PURE__ */
|
|
672
|
+
return /* @__PURE__ */ jsxs7(Box8, { children: [
|
|
673
|
+
/* @__PURE__ */ jsx8(Text8, { color, children: ` ${glyph} ` }),
|
|
674
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: !done && !running, children: label }),
|
|
675
|
+
age ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: ` ${age}` }) : null
|
|
420
676
|
] });
|
|
421
677
|
}
|
|
422
678
|
var init_JobDetailView = __esm({
|
|
@@ -426,46 +682,165 @@ var init_JobDetailView = __esm({
|
|
|
426
682
|
}
|
|
427
683
|
});
|
|
428
684
|
|
|
685
|
+
// src/tui/RecordingDetailView.tsx
|
|
686
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
687
|
+
import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
688
|
+
function RecordingDetailView({
|
|
689
|
+
item,
|
|
690
|
+
nowMs,
|
|
691
|
+
transcript,
|
|
692
|
+
audio
|
|
693
|
+
}) {
|
|
694
|
+
const style = recordingStatusStyle(item.status);
|
|
695
|
+
const links = resolveRecordingLinks(item.recordingId, item.origin);
|
|
696
|
+
const title = recordingTitle2(item);
|
|
697
|
+
const meta3 = [
|
|
698
|
+
item.durationMs ? formatClockMs(item.durationMs) : void 0,
|
|
699
|
+
formatBytes2(item.sizeBytes) || void 0,
|
|
700
|
+
item.contentType || void 0
|
|
701
|
+
].filter(Boolean).join(" \xB7 ");
|
|
702
|
+
const summary = typeof transcript === "object" ? transcript.summary : void 0;
|
|
703
|
+
const segments = typeof transcript === "object" ? transcript.segments : void 0;
|
|
704
|
+
return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", paddingX: 1, children: [
|
|
705
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "\u2039 Recordings" }),
|
|
706
|
+
/* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { bold: true, color: "magenta", children: title }) }),
|
|
707
|
+
/* @__PURE__ */ jsxs8(Text9, { children: [
|
|
708
|
+
/* @__PURE__ */ jsx9(Text9, { color: style.color, children: `${style.glyph} ${style.label}` }),
|
|
709
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: ` ${formatAge(item.createdAt, nowMs) || "\u2014"}` })
|
|
710
|
+
] }),
|
|
711
|
+
meta3 ? /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: meta3 }) : null,
|
|
712
|
+
/* @__PURE__ */ jsx9(AudioActionRow, { item, audio }),
|
|
713
|
+
/* @__PURE__ */ jsx9(SummaryCard, { summary, transcript, hasTranscript: !!item.activeTranscriptId }),
|
|
714
|
+
/* @__PURE__ */ jsx9(TranscriptPreview, { segments, transcript, hasTranscript: !!item.activeTranscriptId }),
|
|
715
|
+
/* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text9, { dimColor: true, children: [
|
|
716
|
+
`esc back \xB7 o open \xB7 d download \xB7 f finder`,
|
|
717
|
+
item.activeTranscriptId ? " \xB7 t transcript" : "",
|
|
718
|
+
links.webUrl ? " \xB7 w web" : "",
|
|
719
|
+
" \xB7 q quit"
|
|
720
|
+
] }) })
|
|
721
|
+
] });
|
|
722
|
+
}
|
|
723
|
+
function AudioActionRow({
|
|
724
|
+
item,
|
|
725
|
+
audio
|
|
726
|
+
}) {
|
|
727
|
+
const ready = item.status === "ready";
|
|
728
|
+
const status = audio?.status ?? "idle";
|
|
729
|
+
let line;
|
|
730
|
+
if (!ready) {
|
|
731
|
+
line = /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Audio available once the recording is ready" });
|
|
732
|
+
} else if (status === "downloading") {
|
|
733
|
+
line = /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: "Downloading audio\u2026" });
|
|
734
|
+
} else if (status === "opening") {
|
|
735
|
+
line = /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: "Opening\u2026" });
|
|
736
|
+
} else if (status === "error") {
|
|
737
|
+
line = /* @__PURE__ */ jsx9(Text9, { color: "red", children: audio?.error ? `Audio failed: ${audio.error}` : "Audio failed" });
|
|
738
|
+
} else if (status === "ready" && audio?.localPath) {
|
|
739
|
+
line = /* @__PURE__ */ jsxs8(Text9, { children: [
|
|
740
|
+
/* @__PURE__ */ jsx9(Text9, { color: "green", children: "\u2713 Downloaded " }),
|
|
741
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, wrap: "truncate-middle", children: audio.localPath })
|
|
742
|
+
] });
|
|
743
|
+
} else {
|
|
744
|
+
line = /* @__PURE__ */ jsxs8(Text9, { children: [
|
|
745
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", children: "o" }),
|
|
746
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " open in player \xB7 " }),
|
|
747
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", children: "d" }),
|
|
748
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " download \xB7 " }),
|
|
749
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", children: "f" }),
|
|
750
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " reveal in Finder" })
|
|
751
|
+
] });
|
|
752
|
+
}
|
|
753
|
+
return /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: [
|
|
754
|
+
/* @__PURE__ */ jsx9(Text9, { color: ready ? "cyan" : "gray", children: "\u266A " }),
|
|
755
|
+
line
|
|
756
|
+
] });
|
|
757
|
+
}
|
|
758
|
+
function SummaryCard({
|
|
759
|
+
summary,
|
|
760
|
+
transcript,
|
|
761
|
+
hasTranscript
|
|
762
|
+
}) {
|
|
763
|
+
if (!hasTranscript) return null;
|
|
764
|
+
return /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
|
|
765
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: "Summary" }),
|
|
766
|
+
transcript === "loading" || transcript === void 0 ? /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Loading\u2026" }) : transcript === "error" ? /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "(summary unavailable)" }) : summary && summary.status === "succeeded" && summary.tldr ? /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
767
|
+
/* @__PURE__ */ jsx9(Text9, { children: summary.tldr }),
|
|
768
|
+
(summary.keyPoints ?? []).slice(0, 5).map((point, i) => /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: `\u2022 ${point}` }, i))
|
|
769
|
+
] }) : /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: `Summary ${summary?.status ?? "unavailable"}` })
|
|
770
|
+
] });
|
|
771
|
+
}
|
|
772
|
+
function TranscriptPreview({
|
|
773
|
+
segments,
|
|
774
|
+
transcript,
|
|
775
|
+
hasTranscript
|
|
776
|
+
}) {
|
|
777
|
+
if (!hasTranscript) {
|
|
778
|
+
return /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Transcript not available yet" }) });
|
|
779
|
+
}
|
|
780
|
+
if (transcript === "loading" || transcript === void 0 || transcript === "error") {
|
|
781
|
+
return null;
|
|
782
|
+
}
|
|
783
|
+
const shown = (segments ?? []).slice(0, PREVIEW_SEGMENTS);
|
|
784
|
+
return /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
|
|
785
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: "Transcript" }),
|
|
786
|
+
shown.length === 0 ? /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "(no segments)" }) : shown.map((seg, i) => /* @__PURE__ */ jsxs8(Text9, { wrap: "truncate-end", children: [
|
|
787
|
+
/* @__PURE__ */ jsx9(Text9, { color: "blue", children: `[${formatClockMs(seg.startMs)}] ` }),
|
|
788
|
+
seg.speaker ? /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: `${seg.speaker} ` }) : null,
|
|
789
|
+
/* @__PURE__ */ jsx9(Text9, { children: seg.text })
|
|
790
|
+
] }, i)),
|
|
791
|
+
(segments?.length ?? 0) > PREVIEW_SEGMENTS ? /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: ` \u2026${(segments?.length ?? 0) - PREVIEW_SEGMENTS} more \u2014 press t for full transcript` }) : null
|
|
792
|
+
] });
|
|
793
|
+
}
|
|
794
|
+
var PREVIEW_SEGMENTS;
|
|
795
|
+
var init_RecordingDetailView = __esm({
|
|
796
|
+
"src/tui/RecordingDetailView.tsx"() {
|
|
797
|
+
"use strict";
|
|
798
|
+
init_format();
|
|
799
|
+
init_RecordingRow();
|
|
800
|
+
PREVIEW_SEGMENTS = 6;
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
|
|
429
804
|
// src/tui/TranscriptView.tsx
|
|
430
|
-
import { Box as
|
|
431
|
-
import { jsx as
|
|
805
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
806
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
432
807
|
function TranscriptView({ loading, data, error: error51 }) {
|
|
433
808
|
if (loading) {
|
|
434
|
-
return /* @__PURE__ */
|
|
809
|
+
return /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "Loading transcript\u2026" }) });
|
|
435
810
|
}
|
|
436
811
|
if (error51) {
|
|
437
|
-
return /* @__PURE__ */
|
|
438
|
-
/* @__PURE__ */
|
|
812
|
+
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", paddingX: 1, children: [
|
|
813
|
+
/* @__PURE__ */ jsxs9(Text10, { color: "red", children: [
|
|
439
814
|
"! ",
|
|
440
815
|
error51
|
|
441
816
|
] }),
|
|
442
|
-
/* @__PURE__ */
|
|
817
|
+
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "q / esc / \u2190 back" })
|
|
443
818
|
] });
|
|
444
819
|
}
|
|
445
820
|
if (!data) {
|
|
446
|
-
return /* @__PURE__ */
|
|
821
|
+
return /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "No transcript." }) });
|
|
447
822
|
}
|
|
448
823
|
const summary = data.summary;
|
|
449
824
|
const showSummary = summary?.status === "succeeded";
|
|
450
|
-
return /* @__PURE__ */
|
|
451
|
-
/* @__PURE__ */
|
|
452
|
-
/* @__PURE__ */
|
|
453
|
-
/* @__PURE__ */
|
|
825
|
+
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", paddingX: 1, children: [
|
|
826
|
+
/* @__PURE__ */ jsx10(Text10, { bold: true, color: "magenta", children: summary?.title ?? "Transcript" }),
|
|
827
|
+
/* @__PURE__ */ jsx10(Box10, { marginTop: 1, flexDirection: "column", children: data.segments.length === 0 ? /* @__PURE__ */ jsx10(Text10, { children: data.text }) : data.segments.slice(0, 200).map((segment, index) => /* @__PURE__ */ jsxs9(Text10, { children: [
|
|
828
|
+
/* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
|
|
454
829
|
"[",
|
|
455
830
|
formatClockMs(segment.startMs),
|
|
456
831
|
"] "
|
|
457
832
|
] }),
|
|
458
|
-
segment.speaker ? /* @__PURE__ */
|
|
833
|
+
segment.speaker ? /* @__PURE__ */ jsxs9(Text10, { color: "cyan", children: [
|
|
459
834
|
segment.speaker,
|
|
460
835
|
": "
|
|
461
836
|
] }) : null,
|
|
462
837
|
segment.text
|
|
463
838
|
] }, index)) }),
|
|
464
|
-
showSummary && summary?.tldr ? /* @__PURE__ */
|
|
465
|
-
/* @__PURE__ */
|
|
466
|
-
/* @__PURE__ */
|
|
839
|
+
showSummary && summary?.tldr ? /* @__PURE__ */ jsxs9(Box10, { marginTop: 1, flexDirection: "column", children: [
|
|
840
|
+
/* @__PURE__ */ jsx10(Text10, { bold: true, children: "Summary" }),
|
|
841
|
+
/* @__PURE__ */ jsx10(Text10, { children: summary.tldr })
|
|
467
842
|
] }) : null,
|
|
468
|
-
/* @__PURE__ */
|
|
843
|
+
/* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "q / esc / \u2190 back" }) })
|
|
469
844
|
] });
|
|
470
845
|
}
|
|
471
846
|
var init_TranscriptView = __esm({
|
|
@@ -475,13 +850,27 @@ var init_TranscriptView = __esm({
|
|
|
475
850
|
}
|
|
476
851
|
});
|
|
477
852
|
|
|
853
|
+
// src/tui/terminal.ts
|
|
854
|
+
import { useWindowSize } from "ink";
|
|
855
|
+
function useTerminalSize() {
|
|
856
|
+
return useWindowSize();
|
|
857
|
+
}
|
|
858
|
+
var init_terminal = __esm({
|
|
859
|
+
"src/tui/terminal.ts"() {
|
|
860
|
+
"use strict";
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
|
|
478
864
|
// src/tui/AppShell.tsx
|
|
479
865
|
import { useCallback, useEffect, useState } from "react";
|
|
480
|
-
import { Box as
|
|
481
|
-
import { jsx as
|
|
866
|
+
import { Box as Box11, Text as Text11, useApp, useInput } from "ink";
|
|
867
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
482
868
|
function AppShell({
|
|
483
869
|
fetchJobs,
|
|
484
870
|
fetchTranscript,
|
|
871
|
+
fetchRecordings,
|
|
872
|
+
fetchDashboardStats,
|
|
873
|
+
recordingAudio,
|
|
485
874
|
initialView = "overview",
|
|
486
875
|
openUrl: openUrl2,
|
|
487
876
|
copyText: copyText2,
|
|
@@ -490,41 +879,126 @@ function AppShell({
|
|
|
490
879
|
spinnerMs = 80
|
|
491
880
|
}) {
|
|
492
881
|
const { exit } = useApp();
|
|
493
|
-
const
|
|
882
|
+
const size = useTerminalSize();
|
|
883
|
+
const [jobs, setJobs] = useState([]);
|
|
884
|
+
const [recordings, setRecordings] = useState([]);
|
|
885
|
+
const [recordingsNextCursor, setRecordingsNextCursor] = useState(null);
|
|
886
|
+
const [recordingsTotalCount, setRecordingsTotalCount] = useState(void 0);
|
|
887
|
+
const [stats, setStats] = useState(void 0);
|
|
494
888
|
const [origin, setOrigin] = useState("");
|
|
495
|
-
const [stack, setStack] = useState([
|
|
496
|
-
{ kind: initialView === "jobs" ? "jobs" : "overview" }
|
|
497
|
-
]);
|
|
889
|
+
const [stack, setStack] = useState([{ kind: initialView }]);
|
|
498
890
|
const [selected, setSelected] = useState(0);
|
|
499
891
|
const [spinnerFrame, setSpinnerFrame] = useState(0);
|
|
892
|
+
const [loadingMoreRecordings, setLoadingMoreRecordings] = useState(false);
|
|
500
893
|
const [loadError, setLoadError] = useState(void 0);
|
|
501
894
|
const [notice, setNotice] = useState(void 0);
|
|
895
|
+
const [summaryCache, setSummaryCache] = useState(() => /* @__PURE__ */ new Map());
|
|
896
|
+
const [transcriptCache, setTranscriptCache] = useState(
|
|
897
|
+
() => /* @__PURE__ */ new Map()
|
|
898
|
+
);
|
|
899
|
+
const [audioCache, setAudioCache] = useState(() => /* @__PURE__ */ new Map());
|
|
502
900
|
const screen = stack[stack.length - 1];
|
|
503
|
-
const
|
|
901
|
+
const selectedRecording = screen.kind === "overview" ? recordings[selected] : void 0;
|
|
902
|
+
const peekTranscriptId = selectedRecording?.activeTranscriptId ?? void 0;
|
|
903
|
+
useEffect(() => {
|
|
904
|
+
if (!peekTranscriptId || summaryCache.has(peekTranscriptId)) return;
|
|
905
|
+
let cancelled = false;
|
|
906
|
+
const timer = setTimeout(() => {
|
|
907
|
+
setSummaryCache((m) => new Map(m).set(peekTranscriptId, "loading"));
|
|
908
|
+
fetchTranscript(peekTranscriptId).then((tr) => {
|
|
909
|
+
if (!cancelled) setSummaryCache((m) => new Map(m).set(peekTranscriptId, tr.summary));
|
|
910
|
+
}).catch(() => {
|
|
911
|
+
if (!cancelled) setSummaryCache((m) => new Map(m).set(peekTranscriptId, "error"));
|
|
912
|
+
});
|
|
913
|
+
}, 200);
|
|
914
|
+
return () => {
|
|
915
|
+
cancelled = true;
|
|
916
|
+
clearTimeout(timer);
|
|
917
|
+
};
|
|
918
|
+
}, [peekTranscriptId, fetchTranscript]);
|
|
919
|
+
const peekSummary = peekTranscriptId ? summaryCache.get(peekTranscriptId) : void 0;
|
|
920
|
+
const refresh = useCallback(async ({ resetRecordings = false } = {}) => {
|
|
921
|
+
const [jobsR, recR, statsR] = await Promise.allSettled([
|
|
922
|
+
fetchJobs(),
|
|
923
|
+
resetRecordings && fetchRecordings ? fetchRecordings({ limit: RECORDINGS_PAGE_SIZE }) : Promise.resolve(void 0),
|
|
924
|
+
fetchDashboardStats ? fetchDashboardStats() : Promise.resolve(void 0)
|
|
925
|
+
]);
|
|
926
|
+
if (jobsR.status === "fulfilled") {
|
|
927
|
+
setJobs(jobsR.value.items);
|
|
928
|
+
setOrigin(jobsR.value.origin);
|
|
929
|
+
setLoadError(void 0);
|
|
930
|
+
} else {
|
|
931
|
+
setLoadError(jobsR.reason instanceof Error ? jobsR.reason.message : String(jobsR.reason));
|
|
932
|
+
}
|
|
933
|
+
if (recR.status === "fulfilled" && recR.value) {
|
|
934
|
+
setRecordings(recR.value.items);
|
|
935
|
+
setRecordingsNextCursor(recR.value.nextCursor ?? null);
|
|
936
|
+
setRecordingsTotalCount(recR.value.totalCount);
|
|
937
|
+
}
|
|
938
|
+
if (statsR.status === "fulfilled" && statsR.value) setStats(statsR.value);
|
|
939
|
+
}, [fetchJobs, fetchRecordings, fetchDashboardStats]);
|
|
940
|
+
const loadMoreRecordings = useCallback(async () => {
|
|
941
|
+
if (!fetchRecordings || !recordingsNextCursor || loadingMoreRecordings) return;
|
|
942
|
+
setLoadingMoreRecordings(true);
|
|
504
943
|
try {
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
944
|
+
const page = await fetchRecordings({
|
|
945
|
+
limit: RECORDINGS_PAGE_SIZE,
|
|
946
|
+
cursor: recordingsNextCursor
|
|
947
|
+
});
|
|
948
|
+
setRecordings((prev) => {
|
|
949
|
+
const seen = new Set(prev.map((item) => item.recordingId));
|
|
950
|
+
const merged = [...prev];
|
|
951
|
+
for (const item of page.items) {
|
|
952
|
+
if (!seen.has(item.recordingId)) merged.push(item);
|
|
953
|
+
}
|
|
954
|
+
return merged;
|
|
955
|
+
});
|
|
956
|
+
setRecordingsNextCursor(page.nextCursor ?? null);
|
|
957
|
+
setRecordingsTotalCount(page.totalCount);
|
|
508
958
|
setLoadError(void 0);
|
|
509
959
|
} catch (error51) {
|
|
510
960
|
setLoadError(error51 instanceof Error ? error51.message : String(error51));
|
|
961
|
+
} finally {
|
|
962
|
+
setLoadingMoreRecordings(false);
|
|
511
963
|
}
|
|
512
|
-
}, [
|
|
964
|
+
}, [fetchRecordings, loadingMoreRecordings, recordingsNextCursor]);
|
|
513
965
|
useEffect(() => {
|
|
514
|
-
void refresh();
|
|
966
|
+
void refresh({ resetRecordings: true });
|
|
515
967
|
const id = setInterval(() => void refresh(), pollMs);
|
|
516
968
|
return () => clearInterval(id);
|
|
517
969
|
}, [refresh, pollMs]);
|
|
518
|
-
const hasRunning =
|
|
970
|
+
const hasRunning = jobs.some((item) => item.status === "running");
|
|
519
971
|
useEffect(() => {
|
|
520
972
|
if (!hasRunning) return;
|
|
521
973
|
const id = setInterval(() => setSpinnerFrame((f) => f + 1), spinnerMs);
|
|
522
974
|
return () => clearInterval(id);
|
|
523
975
|
}, [hasRunning, spinnerMs]);
|
|
524
|
-
const
|
|
976
|
+
const jobRank = (s) => s === "running" ? 4 : s === "queued" ? 3 : s === "failed" ? 2 : s === "succeeded" ? 1 : 0;
|
|
977
|
+
const jobStatusByRecording = /* @__PURE__ */ new Map();
|
|
978
|
+
for (const job of jobs) {
|
|
979
|
+
const prev = jobStatusByRecording.get(job.recordingId);
|
|
980
|
+
if (!prev || jobRank(job.status) > jobRank(prev)) {
|
|
981
|
+
jobStatusByRecording.set(job.recordingId, job.status);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
const listLength = screen.kind === "jobs" ? jobs.length : recordings.length;
|
|
985
|
+
useEffect(() => {
|
|
986
|
+
setSelected((i) => Math.max(0, Math.min(i, Math.max(0, listLength - 1))));
|
|
987
|
+
}, [listLength]);
|
|
988
|
+
const visibleRecordingRows = Math.max(3, size.rows - 6);
|
|
525
989
|
useEffect(() => {
|
|
526
|
-
|
|
527
|
-
|
|
990
|
+
if (screen.kind !== "overview" || !recordingsNextCursor) return;
|
|
991
|
+
const nearLoadedEnd = recordings.length - selected <= RECORDINGS_PREFETCH_REMAINING;
|
|
992
|
+
const underfilledViewport = recordings.length < visibleRecordingRows;
|
|
993
|
+
if (nearLoadedEnd || underfilledViewport) void loadMoreRecordings();
|
|
994
|
+
}, [
|
|
995
|
+
loadMoreRecordings,
|
|
996
|
+
recordings.length,
|
|
997
|
+
recordingsNextCursor,
|
|
998
|
+
screen.kind,
|
|
999
|
+
selected,
|
|
1000
|
+
visibleRecordingRows
|
|
1001
|
+
]);
|
|
528
1002
|
const openTranscript = useCallback(
|
|
529
1003
|
async (transcriptId) => {
|
|
530
1004
|
setStack((st) => [...st, { kind: "transcript", loading: true }]);
|
|
@@ -544,6 +1018,47 @@ function AppShell({
|
|
|
544
1018
|
},
|
|
545
1019
|
[fetchTranscript]
|
|
546
1020
|
);
|
|
1021
|
+
const detailTranscriptId = screen.kind === "recordingDetail" ? recordings.find((r) => r.recordingId === screen.recordingId)?.activeTranscriptId : void 0;
|
|
1022
|
+
useEffect(() => {
|
|
1023
|
+
if (!detailTranscriptId || transcriptCache.has(detailTranscriptId)) return;
|
|
1024
|
+
let cancelled = false;
|
|
1025
|
+
setTranscriptCache((m) => new Map(m).set(detailTranscriptId, "loading"));
|
|
1026
|
+
fetchTranscript(detailTranscriptId).then((tr) => {
|
|
1027
|
+
if (!cancelled) setTranscriptCache((m) => new Map(m).set(detailTranscriptId, tr));
|
|
1028
|
+
}).catch(() => {
|
|
1029
|
+
if (!cancelled) setTranscriptCache((m) => new Map(m).set(detailTranscriptId, "error"));
|
|
1030
|
+
});
|
|
1031
|
+
return () => {
|
|
1032
|
+
cancelled = true;
|
|
1033
|
+
};
|
|
1034
|
+
}, [detailTranscriptId, fetchTranscript]);
|
|
1035
|
+
const setAudio = (recordingId, action) => setAudioCache((m) => new Map(m).set(recordingId, action));
|
|
1036
|
+
const runAudio = useCallback(
|
|
1037
|
+
async (recordingId, mode) => {
|
|
1038
|
+
if (!recordingAudio) {
|
|
1039
|
+
setNotice("Audio actions are not available");
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
setAudio(recordingId, { status: "downloading" });
|
|
1043
|
+
try {
|
|
1044
|
+
const localPath = await recordingAudio.downloadRecordingAudio(recordingId);
|
|
1045
|
+
if (mode === "open") {
|
|
1046
|
+
setAudio(recordingId, { status: "opening", localPath });
|
|
1047
|
+
await recordingAudio.openPath(localPath);
|
|
1048
|
+
} else if (mode === "finder") {
|
|
1049
|
+
setAudio(recordingId, { status: "opening", localPath });
|
|
1050
|
+
await recordingAudio.revealInFinder(localPath);
|
|
1051
|
+
}
|
|
1052
|
+
setAudio(recordingId, { status: "ready", localPath });
|
|
1053
|
+
} catch (error51) {
|
|
1054
|
+
setAudio(recordingId, {
|
|
1055
|
+
status: "error",
|
|
1056
|
+
error: error51 instanceof Error ? error51.message : String(error51)
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
},
|
|
1060
|
+
[recordingAudio]
|
|
1061
|
+
);
|
|
547
1062
|
const goTab = (tab2) => {
|
|
548
1063
|
setStack([{ kind: tab2 }]);
|
|
549
1064
|
setSelected(0);
|
|
@@ -552,33 +1067,32 @@ function AppShell({
|
|
|
552
1067
|
const back = () => setStack((st) => st.length > 1 ? st.slice(0, -1) : st);
|
|
553
1068
|
useInput((input, key) => {
|
|
554
1069
|
setNotice(void 0);
|
|
555
|
-
if (input === "q")
|
|
556
|
-
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
if (key.escape || key.leftArrow) {
|
|
560
|
-
back();
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
1070
|
+
if (input === "q") return exit();
|
|
1071
|
+
if (key.escape || key.leftArrow) return back();
|
|
563
1072
|
if (input === "1") return goTab("overview");
|
|
564
1073
|
if (input === "2") return goTab("jobs");
|
|
565
|
-
if (input === "r") {
|
|
566
|
-
|
|
1074
|
+
if (input === "r") return void refresh({ resetRecordings: true });
|
|
1075
|
+
if (screen.kind === "overview") {
|
|
1076
|
+
if (key.upArrow || input === "k") setSelected((i) => Math.max(0, i - 1));
|
|
1077
|
+
if (key.downArrow || input === "j") setSelected((i) => Math.min(recordings.length - 1, i + 1));
|
|
1078
|
+
const rec = recordings[selected];
|
|
1079
|
+
if (key.return && rec)
|
|
1080
|
+
setStack((st) => [...st, { kind: "recordingDetail", recordingId: rec.recordingId }]);
|
|
1081
|
+
if (input === "t" && rec?.activeTranscriptId) void openTranscript(rec.activeTranscriptId);
|
|
567
1082
|
return;
|
|
568
1083
|
}
|
|
569
|
-
if (screen.kind === "
|
|
1084
|
+
if (screen.kind === "jobs") {
|
|
570
1085
|
if (key.upArrow || input === "k") setSelected((i) => Math.max(0, i - 1));
|
|
571
|
-
if (key.downArrow || input === "j")
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
if (
|
|
575
|
-
if (input === "t" && item?.transcriptId) void openTranscript(item.transcriptId);
|
|
1086
|
+
if (key.downArrow || input === "j") setSelected((i) => Math.min(jobs.length - 1, i + 1));
|
|
1087
|
+
const job = jobs[selected];
|
|
1088
|
+
if (key.return && job) setStack((st) => [...st, { kind: "jobDetail", jobId: job.jobId }]);
|
|
1089
|
+
if (input === "t" && job?.transcriptId) void openTranscript(job.transcriptId);
|
|
576
1090
|
return;
|
|
577
1091
|
}
|
|
578
1092
|
if (screen.kind === "jobDetail") {
|
|
579
|
-
const
|
|
580
|
-
const links =
|
|
581
|
-
if (input === "t" &&
|
|
1093
|
+
const job = jobs.find((j) => j.jobId === screen.jobId);
|
|
1094
|
+
const links = job ? resolveJobLinks(job, origin) : {};
|
|
1095
|
+
if (input === "t" && job?.transcriptId) void openTranscript(job.transcriptId);
|
|
582
1096
|
else if ((input === "o" || input === "w") && links.webUrl) openUrl2?.(links.webUrl);
|
|
583
1097
|
else if (input === "m") setNotice("Mac app deeplink not available yet");
|
|
584
1098
|
else if (input === "c" && links.webUrl) {
|
|
@@ -587,47 +1101,119 @@ function AppShell({
|
|
|
587
1101
|
}
|
|
588
1102
|
return;
|
|
589
1103
|
}
|
|
1104
|
+
if (screen.kind === "recordingDetail") {
|
|
1105
|
+
const rec = recordings.find((r) => r.recordingId === screen.recordingId);
|
|
1106
|
+
const links = rec ? resolveRecordingLinks(rec.recordingId, rec.origin) : {};
|
|
1107
|
+
if (input === "t" && rec?.activeTranscriptId) void openTranscript(rec.activeTranscriptId);
|
|
1108
|
+
else if (input === "o" && rec) void runAudio(rec.recordingId, "open");
|
|
1109
|
+
else if (input === "d" && rec) void runAudio(rec.recordingId, "download");
|
|
1110
|
+
else if (input === "f" && rec) void runAudio(rec.recordingId, "finder");
|
|
1111
|
+
else if (input === "w" && links.webUrl) openUrl2?.(links.webUrl);
|
|
1112
|
+
else if (input === "c" && links.webUrl) {
|
|
1113
|
+
copyText2?.(links.webUrl);
|
|
1114
|
+
setNotice("Link copied");
|
|
1115
|
+
}
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
590
1118
|
});
|
|
591
1119
|
if (screen.kind === "transcript") {
|
|
592
|
-
return /* @__PURE__ */
|
|
1120
|
+
return /* @__PURE__ */ jsx11(TranscriptView, { loading: screen.loading, data: screen.data, error: screen.error });
|
|
593
1121
|
}
|
|
594
1122
|
if (screen.kind === "jobDetail") {
|
|
595
|
-
const
|
|
596
|
-
if (!
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
1123
|
+
const job = jobs.find((j) => j.jobId === screen.jobId);
|
|
1124
|
+
if (!job) return /* @__PURE__ */ jsx11(Missing, { label: "Job" });
|
|
1125
|
+
return /* @__PURE__ */ jsx11(Detail, { notice, children: /* @__PURE__ */ jsx11(JobDetailView, { item: job, origin, spinnerFrame, nowMs: now() }) });
|
|
1126
|
+
}
|
|
1127
|
+
if (screen.kind === "recordingDetail") {
|
|
1128
|
+
const rec = recordings.find((r) => r.recordingId === screen.recordingId);
|
|
1129
|
+
if (!rec) return /* @__PURE__ */ jsx11(Missing, { label: "Recording" });
|
|
1130
|
+
const detailTranscript = rec.activeTranscriptId ? transcriptCache.get(rec.activeTranscriptId) : void 0;
|
|
1131
|
+
return /* @__PURE__ */ jsx11(Detail, { notice, children: /* @__PURE__ */ jsx11(
|
|
1132
|
+
RecordingDetailView,
|
|
1133
|
+
{
|
|
1134
|
+
item: rec,
|
|
1135
|
+
nowMs: now(),
|
|
1136
|
+
transcript: detailTranscript,
|
|
1137
|
+
audio: audioCache.get(rec.recordingId)
|
|
1138
|
+
}
|
|
1139
|
+
) });
|
|
606
1140
|
}
|
|
607
1141
|
const tab = screen.kind === "jobs" ? "jobs" : "overview";
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
1142
|
+
let body;
|
|
1143
|
+
let position = "";
|
|
1144
|
+
if (screen.kind === "overview") {
|
|
1145
|
+
const listBudget = Math.max(3, size.rows - 6);
|
|
1146
|
+
const buckets = recordings.map((r) => dateBucket(r.createdAt, now()));
|
|
1147
|
+
const win = groupedListWindow(buckets, selected, listBudget);
|
|
1148
|
+
const totalRecordings = Math.max(
|
|
1149
|
+
recordingsTotalCount ?? stats?.recordings.total ?? recordings.length,
|
|
1150
|
+
recordings.length
|
|
1151
|
+
);
|
|
1152
|
+
position = recordings.length ? `${selected + 1} / ${totalRecordings}${loadingMoreRecordings ? " \xB7 loading" : ""}` : loadingMoreRecordings ? "loading" : "0";
|
|
1153
|
+
const showPeek = size.columns >= 100;
|
|
1154
|
+
const peekWidth = showPeek ? 34 : 0;
|
|
1155
|
+
const listColumns = showPeek ? Math.max(30, size.columns - peekWidth - 3) : size.columns;
|
|
1156
|
+
body = /* @__PURE__ */ jsx11(
|
|
611
1157
|
OverviewView,
|
|
612
1158
|
{
|
|
613
|
-
|
|
614
|
-
selectedIndex: selected,
|
|
1159
|
+
recordings: recordings.slice(win.start, win.end),
|
|
1160
|
+
selectedIndex: selected - win.start,
|
|
1161
|
+
jobs,
|
|
1162
|
+
stats,
|
|
1163
|
+
nowMs: now(),
|
|
1164
|
+
columns: listColumns,
|
|
1165
|
+
jobStatusByRecording,
|
|
615
1166
|
spinnerFrame,
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
1167
|
+
peekItem: recordings[selected],
|
|
1168
|
+
peekSummary,
|
|
1169
|
+
showPeek,
|
|
1170
|
+
peekWidth
|
|
1171
|
+
}
|
|
1172
|
+
);
|
|
1173
|
+
} else {
|
|
1174
|
+
const win = listWindow(selected, jobs.length, Math.max(3, size.rows - 4));
|
|
1175
|
+
position = jobs.length ? `${selected + 1} / ${jobs.length}` : "0";
|
|
1176
|
+
body = /* @__PURE__ */ jsx11(
|
|
1177
|
+
JobsView,
|
|
625
1178
|
{
|
|
626
|
-
|
|
1179
|
+
items: jobs.slice(win.start, win.end),
|
|
1180
|
+
selectedIndex: selected - win.start,
|
|
1181
|
+
spinnerFrame
|
|
627
1182
|
}
|
|
628
|
-
)
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1185
|
+
const footerKeys = screen.kind === "jobs" ? `${position} \xB7 \u2191\u2193 select \xB7 \u23CE job \xB7 t transcript \xB7 1 overview \xB7 r refresh \xB7 q quit` : `${position} \xB7 \u2191\u2193 scroll \xB7 \u23CE open \xB7 t transcript \xB7 2 jobs \xB7 r refresh \xB7 q quit`;
|
|
1186
|
+
return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
|
|
1187
|
+
/* @__PURE__ */ jsx11(Header, { active: tab }),
|
|
1188
|
+
/* @__PURE__ */ jsxs10(Box11, { flexGrow: 1, flexDirection: "column", children: [
|
|
1189
|
+
body,
|
|
1190
|
+
loadError && jobs.length === 0 && recordings.length === 0 ? /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsxs10(Text11, { color: "red", children: [
|
|
1191
|
+
"! ",
|
|
1192
|
+
loadError
|
|
1193
|
+
] }) }) : null
|
|
1194
|
+
] }),
|
|
1195
|
+
/* @__PURE__ */ jsx11(Footer, { keys: footerKeys })
|
|
1196
|
+
] });
|
|
1197
|
+
}
|
|
1198
|
+
function Detail({
|
|
1199
|
+
notice,
|
|
1200
|
+
children
|
|
1201
|
+
}) {
|
|
1202
|
+
return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
|
|
1203
|
+
children,
|
|
1204
|
+
notice ? /* @__PURE__ */ jsx11(Box11, { paddingX: 1, children: /* @__PURE__ */ jsx11(Text11, { color: "green", children: notice }) }) : null
|
|
1205
|
+
] });
|
|
1206
|
+
}
|
|
1207
|
+
function Missing({ label }) {
|
|
1208
|
+
return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", paddingX: 1, children: [
|
|
1209
|
+
/* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
|
|
1210
|
+
label,
|
|
1211
|
+
" no longer in the list."
|
|
1212
|
+
] }),
|
|
1213
|
+
/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "esc back \xB7 q quit" })
|
|
629
1214
|
] });
|
|
630
1215
|
}
|
|
1216
|
+
var RECORDINGS_PAGE_SIZE, RECORDINGS_PREFETCH_REMAINING;
|
|
631
1217
|
var init_AppShell = __esm({
|
|
632
1218
|
"src/tui/AppShell.tsx"() {
|
|
633
1219
|
"use strict";
|
|
@@ -635,8 +1221,12 @@ var init_AppShell = __esm({
|
|
|
635
1221
|
init_JobsView();
|
|
636
1222
|
init_OverviewView();
|
|
637
1223
|
init_JobDetailView();
|
|
1224
|
+
init_RecordingDetailView();
|
|
638
1225
|
init_TranscriptView();
|
|
639
1226
|
init_format();
|
|
1227
|
+
init_terminal();
|
|
1228
|
+
RECORDINGS_PAGE_SIZE = 50;
|
|
1229
|
+
RECORDINGS_PREFETCH_REMAINING = 8;
|
|
640
1230
|
}
|
|
641
1231
|
});
|
|
642
1232
|
|
|
@@ -644,41 +1234,49 @@ var init_AppShell = __esm({
|
|
|
644
1234
|
var tui_exports = {};
|
|
645
1235
|
__export(tui_exports, {
|
|
646
1236
|
AppShell: () => AppShell,
|
|
1237
|
+
DASHBOARD_RENDER_OPTIONS: () => DASHBOARD_RENDER_OPTIONS,
|
|
647
1238
|
JobDetailView: () => JobDetailView,
|
|
648
1239
|
JobsView: () => JobsView,
|
|
649
1240
|
OverviewView: () => OverviewView,
|
|
650
|
-
runDashboard: () => runDashboard
|
|
1241
|
+
runDashboard: () => runDashboard,
|
|
1242
|
+
useTerminalSize: () => useTerminalSize
|
|
651
1243
|
});
|
|
652
|
-
import
|
|
1244
|
+
import React3 from "react";
|
|
653
1245
|
import { render } from "ink";
|
|
654
|
-
import { spawn } from "child_process";
|
|
1246
|
+
import { spawn as spawn2 } from "child_process";
|
|
655
1247
|
function openUrl(url2) {
|
|
656
1248
|
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
657
1249
|
try {
|
|
658
|
-
|
|
1250
|
+
spawn2(cmd, [url2], { stdio: "ignore", detached: true }).unref();
|
|
659
1251
|
} catch {
|
|
660
1252
|
}
|
|
661
1253
|
}
|
|
662
1254
|
function copyText(text) {
|
|
663
1255
|
if (process.platform !== "darwin") return;
|
|
664
1256
|
try {
|
|
665
|
-
const child =
|
|
1257
|
+
const child = spawn2("pbcopy", { stdio: ["pipe", "ignore", "ignore"] });
|
|
666
1258
|
child.stdin.end(text);
|
|
667
1259
|
} catch {
|
|
668
1260
|
}
|
|
669
1261
|
}
|
|
670
1262
|
async function runDashboard(deps) {
|
|
671
|
-
const
|
|
672
|
-
|
|
1263
|
+
const renderApp = deps.renderApp ?? render;
|
|
1264
|
+
const app = renderApp(
|
|
1265
|
+
React3.createElement(AppShell, {
|
|
673
1266
|
fetchJobs: deps.fetchJobs,
|
|
674
1267
|
fetchTranscript: deps.fetchTranscript,
|
|
1268
|
+
fetchRecordings: deps.fetchRecordings,
|
|
1269
|
+
fetchDashboardStats: deps.fetchDashboardStats,
|
|
1270
|
+
recordingAudio: deps.recordingAudio,
|
|
675
1271
|
initialView: deps.initialView ?? "overview",
|
|
676
1272
|
openUrl,
|
|
677
1273
|
copyText
|
|
678
|
-
})
|
|
1274
|
+
}),
|
|
1275
|
+
DASHBOARD_RENDER_OPTIONS
|
|
679
1276
|
);
|
|
680
1277
|
await app.waitUntilExit();
|
|
681
1278
|
}
|
|
1279
|
+
var DASHBOARD_RENDER_OPTIONS;
|
|
682
1280
|
var init_tui = __esm({
|
|
683
1281
|
"src/tui/index.ts"() {
|
|
684
1282
|
"use strict";
|
|
@@ -687,12 +1285,17 @@ var init_tui = __esm({
|
|
|
687
1285
|
init_JobsView();
|
|
688
1286
|
init_OverviewView();
|
|
689
1287
|
init_JobDetailView();
|
|
1288
|
+
init_terminal();
|
|
1289
|
+
DASHBOARD_RENDER_OPTIONS = {
|
|
1290
|
+
alternateScreen: true,
|
|
1291
|
+
interactive: true
|
|
1292
|
+
};
|
|
690
1293
|
}
|
|
691
1294
|
});
|
|
692
1295
|
|
|
693
1296
|
// src/cli.ts
|
|
694
1297
|
import { Command, CommanderError, InvalidArgumentError } from "commander/esm.mjs";
|
|
695
|
-
import
|
|
1298
|
+
import os4 from "os";
|
|
696
1299
|
|
|
697
1300
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
698
1301
|
var external_exports = {};
|
|
@@ -1460,10 +2063,10 @@ function mergeDefs(...defs) {
|
|
|
1460
2063
|
function cloneDef(schema) {
|
|
1461
2064
|
return mergeDefs(schema._zod.def);
|
|
1462
2065
|
}
|
|
1463
|
-
function getElementAtPath(obj,
|
|
1464
|
-
if (!
|
|
2066
|
+
function getElementAtPath(obj, path4) {
|
|
2067
|
+
if (!path4)
|
|
1465
2068
|
return obj;
|
|
1466
|
-
return
|
|
2069
|
+
return path4.reduce((acc, key) => acc?.[key], obj);
|
|
1467
2070
|
}
|
|
1468
2071
|
function promiseAllObject(promisesObj) {
|
|
1469
2072
|
const keys = Object.keys(promisesObj);
|
|
@@ -1872,11 +2475,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
1872
2475
|
}
|
|
1873
2476
|
return false;
|
|
1874
2477
|
}
|
|
1875
|
-
function prefixIssues(
|
|
2478
|
+
function prefixIssues(path4, issues) {
|
|
1876
2479
|
return issues.map((iss) => {
|
|
1877
2480
|
var _a3;
|
|
1878
2481
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
1879
|
-
iss.path.unshift(
|
|
2482
|
+
iss.path.unshift(path4);
|
|
1880
2483
|
return iss;
|
|
1881
2484
|
});
|
|
1882
2485
|
}
|
|
@@ -2023,16 +2626,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
|
|
|
2023
2626
|
}
|
|
2024
2627
|
function formatError(error51, mapper = (issue2) => issue2.message) {
|
|
2025
2628
|
const fieldErrors = { _errors: [] };
|
|
2026
|
-
const processError = (error52,
|
|
2629
|
+
const processError = (error52, path4 = []) => {
|
|
2027
2630
|
for (const issue2 of error52.issues) {
|
|
2028
2631
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
2029
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
2632
|
+
issue2.errors.map((issues) => processError({ issues }, [...path4, ...issue2.path]));
|
|
2030
2633
|
} else if (issue2.code === "invalid_key") {
|
|
2031
|
-
processError({ issues: issue2.issues }, [...
|
|
2634
|
+
processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
|
|
2032
2635
|
} else if (issue2.code === "invalid_element") {
|
|
2033
|
-
processError({ issues: issue2.issues }, [...
|
|
2636
|
+
processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
|
|
2034
2637
|
} else {
|
|
2035
|
-
const fullpath = [...
|
|
2638
|
+
const fullpath = [...path4, ...issue2.path];
|
|
2036
2639
|
if (fullpath.length === 0) {
|
|
2037
2640
|
fieldErrors._errors.push(mapper(issue2));
|
|
2038
2641
|
} else {
|
|
@@ -2059,17 +2662,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
|
|
|
2059
2662
|
}
|
|
2060
2663
|
function treeifyError(error51, mapper = (issue2) => issue2.message) {
|
|
2061
2664
|
const result = { errors: [] };
|
|
2062
|
-
const processError = (error52,
|
|
2665
|
+
const processError = (error52, path4 = []) => {
|
|
2063
2666
|
var _a3, _b;
|
|
2064
2667
|
for (const issue2 of error52.issues) {
|
|
2065
2668
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
2066
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
2669
|
+
issue2.errors.map((issues) => processError({ issues }, [...path4, ...issue2.path]));
|
|
2067
2670
|
} else if (issue2.code === "invalid_key") {
|
|
2068
|
-
processError({ issues: issue2.issues }, [...
|
|
2671
|
+
processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
|
|
2069
2672
|
} else if (issue2.code === "invalid_element") {
|
|
2070
|
-
processError({ issues: issue2.issues }, [...
|
|
2673
|
+
processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
|
|
2071
2674
|
} else {
|
|
2072
|
-
const fullpath = [...
|
|
2675
|
+
const fullpath = [...path4, ...issue2.path];
|
|
2073
2676
|
if (fullpath.length === 0) {
|
|
2074
2677
|
result.errors.push(mapper(issue2));
|
|
2075
2678
|
continue;
|
|
@@ -2101,8 +2704,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
|
|
|
2101
2704
|
}
|
|
2102
2705
|
function toDotPath(_path) {
|
|
2103
2706
|
const segs = [];
|
|
2104
|
-
const
|
|
2105
|
-
for (const seg of
|
|
2707
|
+
const path4 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
2708
|
+
for (const seg of path4) {
|
|
2106
2709
|
if (typeof seg === "number")
|
|
2107
2710
|
segs.push(`[${seg}]`);
|
|
2108
2711
|
else if (typeof seg === "symbol")
|
|
@@ -14794,13 +15397,13 @@ function resolveRef(ref, ctx) {
|
|
|
14794
15397
|
if (!ref.startsWith("#")) {
|
|
14795
15398
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
14796
15399
|
}
|
|
14797
|
-
const
|
|
14798
|
-
if (
|
|
15400
|
+
const path4 = ref.slice(1).split("/").filter(Boolean);
|
|
15401
|
+
if (path4.length === 0) {
|
|
14799
15402
|
return ctx.rootSchema;
|
|
14800
15403
|
}
|
|
14801
15404
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
14802
|
-
if (
|
|
14803
|
-
const key =
|
|
15405
|
+
if (path4[0] === defsKey) {
|
|
15406
|
+
const key = path4[1];
|
|
14804
15407
|
if (!key || !ctx.defs[key]) {
|
|
14805
15408
|
throw new Error(`Reference not found: ${ref}`);
|
|
14806
15409
|
}
|
|
@@ -15340,6 +15943,45 @@ var jobListDataSchema = external_exports.object({
|
|
|
15340
15943
|
limit: external_exports.number().int().positive(),
|
|
15341
15944
|
origin: external_exports.string()
|
|
15342
15945
|
});
|
|
15946
|
+
var recordingDataSchema = external_exports.object({
|
|
15947
|
+
recordingId: external_exports.string(),
|
|
15948
|
+
title: external_exports.string().nullable().optional(),
|
|
15949
|
+
summaryTitle: external_exports.string().nullable().optional(),
|
|
15950
|
+
status: recordingStatusSchema,
|
|
15951
|
+
durationMs: external_exports.number().int().nonnegative().nullable().optional(),
|
|
15952
|
+
sizeBytes: external_exports.number().int().nonnegative().nullable().optional(),
|
|
15953
|
+
contentType: external_exports.string().optional(),
|
|
15954
|
+
activeTranscriptId: external_exports.string().nullable().optional(),
|
|
15955
|
+
createdAt: external_exports.number().int(),
|
|
15956
|
+
updatedAt: external_exports.number().int(),
|
|
15957
|
+
origin: external_exports.string()
|
|
15958
|
+
});
|
|
15959
|
+
var recordingListDataSchema = external_exports.object({
|
|
15960
|
+
items: external_exports.array(recordingDataSchema),
|
|
15961
|
+
limit: external_exports.number().int().positive(),
|
|
15962
|
+
nextCursor: external_exports.string().nullable().optional(),
|
|
15963
|
+
totalCount: external_exports.number().int().nonnegative().optional(),
|
|
15964
|
+
origin: external_exports.string()
|
|
15965
|
+
});
|
|
15966
|
+
var dashboardStatsDataSchema = external_exports.object({
|
|
15967
|
+
origin: external_exports.string(),
|
|
15968
|
+
recordings: external_exports.object({
|
|
15969
|
+
total: external_exports.number().int().nonnegative(),
|
|
15970
|
+
ready: external_exports.number().int().nonnegative(),
|
|
15971
|
+
uploading: external_exports.number().int().nonnegative(),
|
|
15972
|
+
failed: external_exports.number().int().nonnegative(),
|
|
15973
|
+
aborted: external_exports.number().int().nonnegative(),
|
|
15974
|
+
totalDurationMs: external_exports.number().int().nonnegative(),
|
|
15975
|
+
totalSizeBytes: external_exports.number().int().nonnegative()
|
|
15976
|
+
}),
|
|
15977
|
+
jobs: external_exports.object({
|
|
15978
|
+
active: external_exports.number().int().nonnegative(),
|
|
15979
|
+
queued: external_exports.number().int().nonnegative(),
|
|
15980
|
+
running: external_exports.number().int().nonnegative(),
|
|
15981
|
+
succeeded: external_exports.number().int().nonnegative(),
|
|
15982
|
+
failed: external_exports.number().int().nonnegative()
|
|
15983
|
+
})
|
|
15984
|
+
});
|
|
15343
15985
|
var transcriptSegmentSchema = external_exports.object({
|
|
15344
15986
|
startMs: external_exports.number().nonnegative(),
|
|
15345
15987
|
endMs: external_exports.number().nonnegative(),
|
|
@@ -15845,7 +16487,11 @@ function isRecord(value) {
|
|
|
15845
16487
|
}
|
|
15846
16488
|
|
|
15847
16489
|
// src/api.ts
|
|
15848
|
-
import { promises as fs3 } from "fs";
|
|
16490
|
+
import { createWriteStream, promises as fs3 } from "fs";
|
|
16491
|
+
import os3 from "os";
|
|
16492
|
+
import path3 from "path";
|
|
16493
|
+
import { Readable } from "stream";
|
|
16494
|
+
import { pipeline } from "stream/promises";
|
|
15849
16495
|
|
|
15850
16496
|
// src/files.ts
|
|
15851
16497
|
import { promises as fs2 } from "fs";
|
|
@@ -16130,6 +16776,60 @@ var RecappiApiClient = class {
|
|
|
16130
16776
|
origin: this.auth.origin
|
|
16131
16777
|
});
|
|
16132
16778
|
}
|
|
16779
|
+
async listRecordings(opts) {
|
|
16780
|
+
const params = new URLSearchParams({ limit: String(opts.limit) });
|
|
16781
|
+
if (opts.cursor) params.set("cursor", opts.cursor);
|
|
16782
|
+
if (opts.search) params.set("search", opts.search);
|
|
16783
|
+
const parsed = await this.getJson(`/api/recordings?${params}`);
|
|
16784
|
+
const items = Array.isArray(parsed.items) ? parsed.items.filter(isRecord2).map((row) => mapRecording(row, this.auth.origin)) : [];
|
|
16785
|
+
return recordingListDataSchema.parse({
|
|
16786
|
+
items,
|
|
16787
|
+
limit: opts.limit,
|
|
16788
|
+
...typeof parsed.nextCursor === "string" || parsed.nextCursor === null ? { nextCursor: parsed.nextCursor } : {},
|
|
16789
|
+
...typeof parsed.totalCount === "number" ? { totalCount: parsed.totalCount } : {},
|
|
16790
|
+
origin: this.auth.origin
|
|
16791
|
+
});
|
|
16792
|
+
}
|
|
16793
|
+
async getRecording(recordingId) {
|
|
16794
|
+
const parsed = await this.getJson(
|
|
16795
|
+
`/api/recordings/${encodeURIComponent(recordingId)}`
|
|
16796
|
+
);
|
|
16797
|
+
return mapRecording(parsed, this.auth.origin);
|
|
16798
|
+
}
|
|
16799
|
+
async downloadRecordingAudio(recordingId, opts = {}) {
|
|
16800
|
+
const response = await this.request(
|
|
16801
|
+
"GET",
|
|
16802
|
+
`/api/recordings/${encodeURIComponent(recordingId)}/audio`
|
|
16803
|
+
);
|
|
16804
|
+
if (!response.body) {
|
|
16805
|
+
throw cliError("cloud.invalid_response", "Recording audio response was empty.");
|
|
16806
|
+
}
|
|
16807
|
+
const contentType = normalizeContentType(response.headers.get("content-type"));
|
|
16808
|
+
const contentLength = numberHeader(response.headers.get("content-length"));
|
|
16809
|
+
const dir = opts.directory ?? await fs3.mkdtemp(path3.join(os3.tmpdir(), "recappi-cli-audio-"));
|
|
16810
|
+
if (opts.directory) await fs3.mkdir(dir, { recursive: true });
|
|
16811
|
+
const filePath = path3.join(dir, recordingAudioFileName(recordingId, opts.title, contentType));
|
|
16812
|
+
try {
|
|
16813
|
+
await pipeline(
|
|
16814
|
+
Readable.fromWeb(response.body),
|
|
16815
|
+
createWriteStream(filePath)
|
|
16816
|
+
);
|
|
16817
|
+
} catch (error51) {
|
|
16818
|
+
await fs3.rm(filePath, { force: true }).catch(() => void 0);
|
|
16819
|
+
throw error51;
|
|
16820
|
+
}
|
|
16821
|
+
return {
|
|
16822
|
+
recordingId,
|
|
16823
|
+
localPath: filePath,
|
|
16824
|
+
contentType,
|
|
16825
|
+
...contentLength !== void 0 ? { contentLength } : {},
|
|
16826
|
+
origin: this.auth.origin
|
|
16827
|
+
};
|
|
16828
|
+
}
|
|
16829
|
+
async dashboardStats() {
|
|
16830
|
+
const parsed = await this.getJson("/api/dashboard/stats");
|
|
16831
|
+
return mapDashboardStats(parsed, this.auth.origin);
|
|
16832
|
+
}
|
|
16133
16833
|
async uploadPathBatch(opts) {
|
|
16134
16834
|
const files = await collectAudioFiles(opts.inputPath);
|
|
16135
16835
|
if (files.length === 0) {
|
|
@@ -16310,12 +17010,12 @@ var RecappiApiClient = class {
|
|
|
16310
17010
|
...typeof parsed.language === "string" || parsed.language === null ? { language: parsed.language } : {}
|
|
16311
17011
|
};
|
|
16312
17012
|
}
|
|
16313
|
-
async getJson(
|
|
16314
|
-
const response = await this.request("GET",
|
|
17013
|
+
async getJson(path4) {
|
|
17014
|
+
const response = await this.request("GET", path4);
|
|
16315
17015
|
return await parseJson(response);
|
|
16316
17016
|
}
|
|
16317
|
-
async postJson(
|
|
16318
|
-
const response = await this.request("POST",
|
|
17017
|
+
async postJson(path4, body) {
|
|
17018
|
+
const response = await this.request("POST", path4, JSON.stringify(body), {
|
|
16319
17019
|
headers: { "content-type": "application/json" }
|
|
16320
17020
|
});
|
|
16321
17021
|
return await parseJson(response);
|
|
@@ -16358,6 +17058,53 @@ async function responseMessage(response) {
|
|
|
16358
17058
|
return response.statusText;
|
|
16359
17059
|
}
|
|
16360
17060
|
}
|
|
17061
|
+
function normalizeContentType(value) {
|
|
17062
|
+
return value?.split(";")[0]?.trim().toLowerCase() || "audio/wav";
|
|
17063
|
+
}
|
|
17064
|
+
function numberHeader(value) {
|
|
17065
|
+
if (!value) return void 0;
|
|
17066
|
+
const parsed = Number(value);
|
|
17067
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
|
|
17068
|
+
}
|
|
17069
|
+
function audioExtensionForContentType(contentType) {
|
|
17070
|
+
switch (contentType) {
|
|
17071
|
+
case "audio/mpeg":
|
|
17072
|
+
case "audio/mp3":
|
|
17073
|
+
return "mp3";
|
|
17074
|
+
case "audio/aiff":
|
|
17075
|
+
case "audio/x-aiff":
|
|
17076
|
+
return "aiff";
|
|
17077
|
+
case "audio/aac":
|
|
17078
|
+
case "audio/mp4":
|
|
17079
|
+
case "audio/m4a":
|
|
17080
|
+
case "audio/x-m4a":
|
|
17081
|
+
return "m4a";
|
|
17082
|
+
case "audio/ogg":
|
|
17083
|
+
return "ogg";
|
|
17084
|
+
case "audio/flac":
|
|
17085
|
+
case "audio/x-flac":
|
|
17086
|
+
return "flac";
|
|
17087
|
+
case "audio/webm":
|
|
17088
|
+
return "webm";
|
|
17089
|
+
case "audio/wav":
|
|
17090
|
+
case "audio/x-wav":
|
|
17091
|
+
default:
|
|
17092
|
+
return "wav";
|
|
17093
|
+
}
|
|
17094
|
+
}
|
|
17095
|
+
function recordingAudioFileName(recordingId, title, contentType) {
|
|
17096
|
+
const idStem = truncateFileStem(safeFileStem(recordingId), 48);
|
|
17097
|
+
const titleStem = title ? truncateFileStem(safeFileStem(title), 80) : "";
|
|
17098
|
+
const stem = titleStem ? `${titleStem}-${idStem}` : idStem;
|
|
17099
|
+
return `${stem}.${audioExtensionForContentType(contentType)}`;
|
|
17100
|
+
}
|
|
17101
|
+
function truncateFileStem(value, maxLength) {
|
|
17102
|
+
return [...value].slice(0, maxLength).join("");
|
|
17103
|
+
}
|
|
17104
|
+
function safeFileStem(value) {
|
|
17105
|
+
const safe = value.normalize("NFKC").replace(/[^\p{L}\p{N}._-]+/gu, "-").replace(/^-+|-+$/g, "");
|
|
17106
|
+
return safe || "recording";
|
|
17107
|
+
}
|
|
16361
17108
|
function isRecord2(value) {
|
|
16362
17109
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16363
17110
|
}
|
|
@@ -16510,9 +17257,59 @@ function mapJobListItem(row) {
|
|
|
16510
17257
|
}
|
|
16511
17258
|
};
|
|
16512
17259
|
}
|
|
17260
|
+
function mapRecording(row, origin) {
|
|
17261
|
+
const recordingId = stringValue(row.id) ?? stringValue(row.recordingId);
|
|
17262
|
+
const status = stringValue(row.status);
|
|
17263
|
+
const createdAt = numberValue(row.createdAt);
|
|
17264
|
+
const updatedAt = numberValue(row.updatedAt);
|
|
17265
|
+
if (!recordingId) {
|
|
17266
|
+
throw cliError("cloud.invalid_response", "Recording response was missing id.");
|
|
17267
|
+
}
|
|
17268
|
+
if (!status) {
|
|
17269
|
+
throw cliError("cloud.invalid_response", "Recording response was missing status.");
|
|
17270
|
+
}
|
|
17271
|
+
if (createdAt === void 0 || updatedAt === void 0) {
|
|
17272
|
+
throw cliError("cloud.invalid_response", "Recording response was missing timestamps.");
|
|
17273
|
+
}
|
|
17274
|
+
return recordingDataSchema.parse({
|
|
17275
|
+
recordingId,
|
|
17276
|
+
...typeof row.title === "string" || row.title === null ? { title: row.title } : {},
|
|
17277
|
+
...typeof row.summaryTitle === "string" || row.summaryTitle === null ? { summaryTitle: row.summaryTitle } : {},
|
|
17278
|
+
status,
|
|
17279
|
+
...typeof row.durationMs === "number" || row.durationMs === null ? { durationMs: row.durationMs } : {},
|
|
17280
|
+
...typeof row.sizeBytes === "number" || row.sizeBytes === null ? { sizeBytes: row.sizeBytes } : {},
|
|
17281
|
+
...typeof row.contentType === "string" ? { contentType: row.contentType } : {},
|
|
17282
|
+
...typeof row.activeTranscriptId === "string" || row.activeTranscriptId === null ? { activeTranscriptId: row.activeTranscriptId } : {},
|
|
17283
|
+
createdAt,
|
|
17284
|
+
updatedAt,
|
|
17285
|
+
origin
|
|
17286
|
+
});
|
|
17287
|
+
}
|
|
17288
|
+
function mapDashboardStats(row, origin) {
|
|
17289
|
+
return dashboardStatsDataSchema.parse({
|
|
17290
|
+
origin,
|
|
17291
|
+
recordings: mapCountObject(row.recordings, [
|
|
17292
|
+
"total",
|
|
17293
|
+
"ready",
|
|
17294
|
+
"uploading",
|
|
17295
|
+
"failed",
|
|
17296
|
+
"aborted",
|
|
17297
|
+
"totalDurationMs",
|
|
17298
|
+
"totalSizeBytes"
|
|
17299
|
+
]),
|
|
17300
|
+
jobs: mapCountObject(row.jobs, ["active", "queued", "running", "succeeded", "failed"])
|
|
17301
|
+
});
|
|
17302
|
+
}
|
|
17303
|
+
function mapCountObject(value, keys) {
|
|
17304
|
+
const source = isRecord2(value) ? value : {};
|
|
17305
|
+
return Object.fromEntries(keys.map((key) => [key, numberValue(source[key]) ?? 0]));
|
|
17306
|
+
}
|
|
16513
17307
|
function stringValue(value) {
|
|
16514
17308
|
return typeof value === "string" ? value : void 0;
|
|
16515
17309
|
}
|
|
17310
|
+
function numberValue(value) {
|
|
17311
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
17312
|
+
}
|
|
16516
17313
|
function parseSummaryStatus(value) {
|
|
16517
17314
|
const allowed = /* @__PURE__ */ new Set([
|
|
16518
17315
|
"pending",
|
|
@@ -16545,6 +17342,62 @@ function decodeJsonRecord(value) {
|
|
|
16545
17342
|
}
|
|
16546
17343
|
}
|
|
16547
17344
|
|
|
17345
|
+
// src/audio.ts
|
|
17346
|
+
import { spawn } from "child_process";
|
|
17347
|
+
function createRecordingAudioRuntime(client, deps = {}) {
|
|
17348
|
+
return {
|
|
17349
|
+
downloadRecordingAudio: async (recordingId, opts) => (await client.downloadRecordingAudio(recordingId, opts)).localPath,
|
|
17350
|
+
openPath: (localPath) => openPath(localPath, deps),
|
|
17351
|
+
revealInFinder: (localPath) => revealInFinder(localPath, deps)
|
|
17352
|
+
};
|
|
17353
|
+
}
|
|
17354
|
+
function openPath(localPath, deps = {}) {
|
|
17355
|
+
return runMacOpen([localPath], deps);
|
|
17356
|
+
}
|
|
17357
|
+
function revealInFinder(localPath, deps = {}) {
|
|
17358
|
+
return runMacOpen(["-R", localPath], deps);
|
|
17359
|
+
}
|
|
17360
|
+
function runMacOpen(args, deps) {
|
|
17361
|
+
if ((deps.platform ?? process.platform) !== "darwin") {
|
|
17362
|
+
return Promise.reject(
|
|
17363
|
+
cliError(
|
|
17364
|
+
"usage.invalid_argument",
|
|
17365
|
+
"Recording audio file actions are supported on macOS only.",
|
|
17366
|
+
{
|
|
17367
|
+
hint: "Download the audio and open the printed local path manually on this platform."
|
|
17368
|
+
}
|
|
17369
|
+
)
|
|
17370
|
+
);
|
|
17371
|
+
}
|
|
17372
|
+
const spawnProcess = deps.spawnProcess ?? spawn;
|
|
17373
|
+
return new Promise((resolve, reject) => {
|
|
17374
|
+
let settled = false;
|
|
17375
|
+
const finish = (error51) => {
|
|
17376
|
+
if (settled) return;
|
|
17377
|
+
settled = true;
|
|
17378
|
+
if (error51) reject(error51);
|
|
17379
|
+
else resolve();
|
|
17380
|
+
};
|
|
17381
|
+
try {
|
|
17382
|
+
const child = spawnProcess("open", args, { stdio: "ignore" });
|
|
17383
|
+
child.once(
|
|
17384
|
+
"error",
|
|
17385
|
+
(error51) => finish(error51 instanceof Error ? error51 : new Error(String(error51)))
|
|
17386
|
+
);
|
|
17387
|
+
child.once("close", (code) => {
|
|
17388
|
+
if (code === 0) finish();
|
|
17389
|
+
else {
|
|
17390
|
+
finish(
|
|
17391
|
+
cliError("internal.unexpected", `open failed with exit code ${code ?? "unknown"}.`)
|
|
17392
|
+
);
|
|
17393
|
+
}
|
|
17394
|
+
});
|
|
17395
|
+
} catch (error51) {
|
|
17396
|
+
finish(error51 instanceof Error ? error51 : new Error(String(error51)));
|
|
17397
|
+
}
|
|
17398
|
+
});
|
|
17399
|
+
}
|
|
17400
|
+
|
|
16548
17401
|
// src/render.ts
|
|
16549
17402
|
function createHumanProgressState(interactive) {
|
|
16550
17403
|
return {
|
|
@@ -16672,6 +17525,56 @@ function renderHumanSuccess(command, data, opts) {
|
|
|
16672
17525
|
renderTranscriptHuman(data, opts);
|
|
16673
17526
|
return;
|
|
16674
17527
|
}
|
|
17528
|
+
if (command === "recordings list" && isRecord3(data) && Array.isArray(data.items)) {
|
|
17529
|
+
opts.stdout("Recordings:\n");
|
|
17530
|
+
for (const item of data.items) {
|
|
17531
|
+
if (!isRecord3(item)) continue;
|
|
17532
|
+
opts.stdout(` ${recordingLabel(item)}
|
|
17533
|
+
`);
|
|
17534
|
+
}
|
|
17535
|
+
if (data.items.length === 0) opts.stdout(" No recordings found.\n");
|
|
17536
|
+
if (typeof data.nextCursor === "string") {
|
|
17537
|
+
opts.stdout(`
|
|
17538
|
+
Next cursor: ${data.nextCursor}
|
|
17539
|
+
`);
|
|
17540
|
+
}
|
|
17541
|
+
return;
|
|
17542
|
+
}
|
|
17543
|
+
if (command === "recordings get" && isRecord3(data)) {
|
|
17544
|
+
opts.stdout(`${recordingTitle(data)}
|
|
17545
|
+
`);
|
|
17546
|
+
opts.stdout(` recordingId: ${String(data.recordingId)}
|
|
17547
|
+
`);
|
|
17548
|
+
if (typeof data.status === "string") opts.stdout(` status: ${data.status}
|
|
17549
|
+
`);
|
|
17550
|
+
if (typeof data.durationMs === "number")
|
|
17551
|
+
opts.stdout(` duration: ${formatDurationMs(data.durationMs)}
|
|
17552
|
+
`);
|
|
17553
|
+
if (typeof data.sizeBytes === "number") opts.stdout(` size: ${formatBytes(data.sizeBytes)}
|
|
17554
|
+
`);
|
|
17555
|
+
if (typeof data.activeTranscriptId === "string") {
|
|
17556
|
+
opts.stdout(` activeTranscriptId: ${data.activeTranscriptId}
|
|
17557
|
+
`);
|
|
17558
|
+
opts.stdout(`
|
|
17559
|
+
Next:
|
|
17560
|
+
recappi transcript get ${data.activeTranscriptId}
|
|
17561
|
+
`);
|
|
17562
|
+
}
|
|
17563
|
+
return;
|
|
17564
|
+
}
|
|
17565
|
+
if (command === "dashboard stats" && isRecord3(data)) {
|
|
17566
|
+
const recordings = isRecord3(data.recordings) ? data.recordings : {};
|
|
17567
|
+
const jobs = isRecord3(data.jobs) ? data.jobs : {};
|
|
17568
|
+
opts.stdout(
|
|
17569
|
+
`Recordings: ${numberText(recordings.total)} total, ${numberText(recordings.ready)} ready
|
|
17570
|
+
`
|
|
17571
|
+
);
|
|
17572
|
+
opts.stdout(
|
|
17573
|
+
`Jobs: ${numberText(jobs.active)} active (${numberText(jobs.queued)} queued, ${numberText(jobs.running)} running)
|
|
17574
|
+
`
|
|
17575
|
+
);
|
|
17576
|
+
return;
|
|
17577
|
+
}
|
|
16675
17578
|
if (command === "upload" && isUploadBatch(data)) {
|
|
16676
17579
|
if (data.successes.length > 0) {
|
|
16677
17580
|
opts.stdout(data.successes.length === 1 ? "Upload complete\n" : "Uploads complete\n");
|
|
@@ -16852,6 +17755,37 @@ Summary:
|
|
|
16852
17755
|
}
|
|
16853
17756
|
}
|
|
16854
17757
|
}
|
|
17758
|
+
function recordingLabel(item) {
|
|
17759
|
+
const id = typeof item.recordingId === "string" ? item.recordingId : "unknown";
|
|
17760
|
+
const status = typeof item.status === "string" ? item.status : "unknown";
|
|
17761
|
+
const duration3 = typeof item.durationMs === "number" ? ` \xB7 ${formatDurationMs(item.durationMs)}` : "";
|
|
17762
|
+
return `${recordingTitle(item)} (${status}, ${id}${duration3})`;
|
|
17763
|
+
}
|
|
17764
|
+
function recordingTitle(item) {
|
|
17765
|
+
for (const key of ["title", "summaryTitle"]) {
|
|
17766
|
+
const value = item[key];
|
|
17767
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
17768
|
+
}
|
|
17769
|
+
return "Untitled recording";
|
|
17770
|
+
}
|
|
17771
|
+
function numberText(value) {
|
|
17772
|
+
return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US") : "0";
|
|
17773
|
+
}
|
|
17774
|
+
function formatDurationMs(ms) {
|
|
17775
|
+
return formatClock(ms / 1e3);
|
|
17776
|
+
}
|
|
17777
|
+
function formatBytes(bytes) {
|
|
17778
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
17779
|
+
const units = ["KB", "MB", "GB", "TB"];
|
|
17780
|
+
let value = bytes / 1024;
|
|
17781
|
+
let unit = units[0];
|
|
17782
|
+
for (const next of units.slice(1)) {
|
|
17783
|
+
if (value < 1024) break;
|
|
17784
|
+
value /= 1024;
|
|
17785
|
+
unit = next;
|
|
17786
|
+
}
|
|
17787
|
+
return `${value >= 10 ? value.toFixed(0) : value.toFixed(1)} ${unit}`;
|
|
17788
|
+
}
|
|
16855
17789
|
function formatClock(seconds) {
|
|
16856
17790
|
const total = Math.max(0, Math.floor(seconds));
|
|
16857
17791
|
const hours = Math.floor(total / 3600);
|
|
@@ -16942,7 +17876,10 @@ var COMMAND_DATA_SCHEMAS = {
|
|
|
16942
17876
|
"auth import-macos": authImportDataSchema,
|
|
16943
17877
|
"auth status": authStatusDataSchema,
|
|
16944
17878
|
doctor: doctorDataSchema,
|
|
17879
|
+
"dashboard stats": dashboardStatsDataSchema,
|
|
16945
17880
|
upload: uploadBatchDataSchema,
|
|
17881
|
+
"recordings get": recordingDataSchema,
|
|
17882
|
+
"recordings list": recordingListDataSchema,
|
|
16946
17883
|
"jobs list": jobListDataSchema,
|
|
16947
17884
|
"jobs wait": jobDataSchema,
|
|
16948
17885
|
"transcript get": transcriptDataSchema
|
|
@@ -16977,11 +17914,11 @@ function buildSchemaDocument(program) {
|
|
|
16977
17914
|
event: toJsonSchema(operationEventSchema)
|
|
16978
17915
|
};
|
|
16979
17916
|
}
|
|
16980
|
-
function walkCommands(command,
|
|
17917
|
+
function walkCommands(command, path4, out) {
|
|
16981
17918
|
for (const sub of subcommandsOf(command)) {
|
|
16982
17919
|
const name = sub.name();
|
|
16983
17920
|
if (name === "help") continue;
|
|
16984
|
-
const fullPath = [...
|
|
17921
|
+
const fullPath = [...path4, name];
|
|
16985
17922
|
const children = subcommandsOf(sub).filter((child) => child.name() !== "help");
|
|
16986
17923
|
if (children.length === 0) {
|
|
16987
17924
|
out.push(leafCommandDoc(sub, fullPath.join(" ")));
|
|
@@ -17041,6 +17978,7 @@ function readCliVersion() {
|
|
|
17041
17978
|
var CLI_VERSION = readCliVersion();
|
|
17042
17979
|
|
|
17043
17980
|
// src/cli.ts
|
|
17981
|
+
var DASHBOARD_RECORDINGS_PAGE_SIZE = 50;
|
|
17044
17982
|
async function runCli(deps = {}) {
|
|
17045
17983
|
const argv = deps.argv ?? process.argv.slice(2);
|
|
17046
17984
|
const stdout = deps.stdout ?? ((text) => process.stdout.write(text));
|
|
@@ -17084,7 +18022,10 @@ async function runCli(deps = {}) {
|
|
|
17084
18022
|
const runDashboard2 = deps.runDashboard ?? (await Promise.resolve().then(() => (init_tui(), tui_exports))).runDashboard;
|
|
17085
18023
|
await runDashboard2({
|
|
17086
18024
|
fetchJobs: () => client.listJobs({ status: "active", limit: 20 }),
|
|
18025
|
+
fetchRecordings: ({ cursor, limit = DASHBOARD_RECORDINGS_PAGE_SIZE } = {}) => client.listRecordings({ limit, cursor }),
|
|
18026
|
+
fetchDashboardStats: () => client.dashboardStats(),
|
|
17087
18027
|
fetchTranscript: (transcriptId) => client.getTranscript(transcriptId),
|
|
18028
|
+
recordingAudio: createRecordingAudioRuntime(client),
|
|
17088
18029
|
initialView: parsed.initialView
|
|
17089
18030
|
});
|
|
17090
18031
|
return 0;
|
|
@@ -17110,7 +18051,7 @@ async function runCli(deps = {}) {
|
|
|
17110
18051
|
return 0;
|
|
17111
18052
|
}
|
|
17112
18053
|
if (parsed.kind === "auth-logout") {
|
|
17113
|
-
const cleared = await clearAuthConfig(deps.homeDir ??
|
|
18054
|
+
const cleared = await clearAuthConfig(deps.homeDir ?? os4.homedir());
|
|
17114
18055
|
renderSuccess("auth logout", { loggedIn: false, origin: auth.origin, cleared }, render2);
|
|
17115
18056
|
return 0;
|
|
17116
18057
|
}
|
|
@@ -17121,7 +18062,7 @@ async function runCli(deps = {}) {
|
|
|
17121
18062
|
hint: keychain.hint ?? "Run recappi auth login instead."
|
|
17122
18063
|
});
|
|
17123
18064
|
}
|
|
17124
|
-
await saveAuthConfig(deps.homeDir ??
|
|
18065
|
+
await saveAuthConfig(deps.homeDir ?? os4.homedir(), {
|
|
17125
18066
|
origin: auth.origin,
|
|
17126
18067
|
token: keychain.token
|
|
17127
18068
|
});
|
|
@@ -17181,6 +18122,25 @@ async function runCli(deps = {}) {
|
|
|
17181
18122
|
renderSuccess("jobs list", data, render2);
|
|
17182
18123
|
return 0;
|
|
17183
18124
|
}
|
|
18125
|
+
if (parsed.kind === "recordings-list") {
|
|
18126
|
+
const data = await client.listRecordings({
|
|
18127
|
+
limit: parsed.limit,
|
|
18128
|
+
cursor: parsed.cursor,
|
|
18129
|
+
search: parsed.search
|
|
18130
|
+
});
|
|
18131
|
+
renderSuccess("recordings list", data, render2);
|
|
18132
|
+
return 0;
|
|
18133
|
+
}
|
|
18134
|
+
if (parsed.kind === "recordings-get") {
|
|
18135
|
+
const data = await client.getRecording(parsed.recordingId);
|
|
18136
|
+
renderSuccess("recordings get", data, render2);
|
|
18137
|
+
return 0;
|
|
18138
|
+
}
|
|
18139
|
+
if (parsed.kind === "dashboard-stats") {
|
|
18140
|
+
const data = await client.dashboardStats();
|
|
18141
|
+
renderSuccess("dashboard stats", data, render2);
|
|
18142
|
+
return 0;
|
|
18143
|
+
}
|
|
17184
18144
|
if (parsed.kind === "transcript-get") {
|
|
17185
18145
|
const data = await client.getTranscript(parsed.transcriptId);
|
|
17186
18146
|
renderSuccess("transcript get", data, render2);
|
|
@@ -17402,6 +18362,44 @@ Agent mode:
|
|
|
17402
18362
|
document: buildSchemaDocument(program)
|
|
17403
18363
|
});
|
|
17404
18364
|
});
|
|
18365
|
+
const dashboard = program.command("dashboard").description("Dashboard data commands");
|
|
18366
|
+
addCommonOptions(dashboard);
|
|
18367
|
+
const dashboardStats = dashboard.command("stats").description("Fetch dashboard counters");
|
|
18368
|
+
addCommonOptions(dashboardStats);
|
|
18369
|
+
dashboardStats.action((_options, command) => {
|
|
18370
|
+
onSelect({
|
|
18371
|
+
kind: "dashboard-stats",
|
|
18372
|
+
options: collectGlobalOptions(command),
|
|
18373
|
+
commandName: "dashboard stats"
|
|
18374
|
+
});
|
|
18375
|
+
});
|
|
18376
|
+
const recordings = program.command("recordings").description("Recording commands");
|
|
18377
|
+
addCommonOptions(recordings);
|
|
18378
|
+
const recordingsList = recordings.command("list").description("List recent recordings").option("--limit <n>", "number of recordings to show", parseLimitOption("--limit", 1, 100), 20).option("--cursor <cursor>", "pagination cursor", parseStringOption("--cursor")).option("--search <query>", "search recordings and transcripts", parseStringOption("--search"));
|
|
18379
|
+
addCommonOptions(recordingsList);
|
|
18380
|
+
recordingsList.action((_options, command) => {
|
|
18381
|
+
const opts = command.opts();
|
|
18382
|
+
onSelect({
|
|
18383
|
+
kind: "recordings-list",
|
|
18384
|
+
options: collectGlobalOptions(command),
|
|
18385
|
+
commandName: "recordings list",
|
|
18386
|
+
limit: opts.limit ?? 20,
|
|
18387
|
+
...typeof opts.cursor === "string" ? { cursor: opts.cursor } : {},
|
|
18388
|
+
...typeof opts.search === "string" ? { search: opts.search } : {}
|
|
18389
|
+
});
|
|
18390
|
+
});
|
|
18391
|
+
const recordingsGet = recordings.command("get <recordingId>").description("Fetch a recording by recording id");
|
|
18392
|
+
addCommonOptions(recordingsGet);
|
|
18393
|
+
recordingsGet.action(
|
|
18394
|
+
(recordingId, _options, command) => {
|
|
18395
|
+
onSelect({
|
|
18396
|
+
kind: "recordings-get",
|
|
18397
|
+
options: collectGlobalOptions(command),
|
|
18398
|
+
commandName: "recordings get",
|
|
18399
|
+
recordingId
|
|
18400
|
+
});
|
|
18401
|
+
}
|
|
18402
|
+
);
|
|
17405
18403
|
const transcript = program.command("transcript").description("Transcript commands");
|
|
17406
18404
|
addCommonOptions(transcript);
|
|
17407
18405
|
const transcriptGet = transcript.command("get <transcriptId>").description("Fetch a transcript by transcript id");
|
|
@@ -17516,7 +18514,9 @@ var VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
|
17516
18514
|
"--provider",
|
|
17517
18515
|
"--prompt",
|
|
17518
18516
|
"--status",
|
|
17519
|
-
"--limit"
|
|
18517
|
+
"--limit",
|
|
18518
|
+
"--cursor",
|
|
18519
|
+
"--search"
|
|
17520
18520
|
]);
|
|
17521
18521
|
function hasCommandToken(argv) {
|
|
17522
18522
|
return commandTokens(argv).length > 0;
|