recappi 0.1.3 → 0.1.5
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 +2332 -391
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -9,39 +9,6 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
// src/tui/chrome.tsx
|
|
13
|
-
import { Box, Text } from "ink";
|
|
14
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
-
function Header({ active }) {
|
|
16
|
-
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
17
|
-
/* @__PURE__ */ jsxs(Text, { bold: true, color: "magenta", children: [
|
|
18
|
-
"Recappi",
|
|
19
|
-
" "
|
|
20
|
-
] }),
|
|
21
|
-
/* @__PURE__ */ jsx(Tab, { num: "1", label: "Overview", active: active === "overview" }),
|
|
22
|
-
/* @__PURE__ */ jsx(Tab, { num: "2", label: "Jobs", active: active === "jobs" })
|
|
23
|
-
] });
|
|
24
|
-
}
|
|
25
|
-
function Tab({
|
|
26
|
-
num,
|
|
27
|
-
label,
|
|
28
|
-
active
|
|
29
|
-
}) {
|
|
30
|
-
const text = ` ${num} ${label} `;
|
|
31
|
-
if (active) {
|
|
32
|
-
return /* @__PURE__ */ jsx(Text, { bold: true, inverse: true, color: "cyan", children: text });
|
|
33
|
-
}
|
|
34
|
-
return /* @__PURE__ */ jsx(Text, { children: text });
|
|
35
|
-
}
|
|
36
|
-
function Footer({ keys }) {
|
|
37
|
-
return /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: keys }) });
|
|
38
|
-
}
|
|
39
|
-
var init_chrome = __esm({
|
|
40
|
-
"src/tui/chrome.tsx"() {
|
|
41
|
-
"use strict";
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
|
|
45
12
|
// src/tui/format.ts
|
|
46
13
|
function formatClockMs(ms) {
|
|
47
14
|
if (ms == null || !Number.isFinite(ms) || ms < 0) return "--:--";
|
|
@@ -230,6 +197,29 @@ function listWindow(selected, total, size) {
|
|
|
230
197
|
start = Math.max(0, Math.min(start, total - size));
|
|
231
198
|
return { start, end: start + size };
|
|
232
199
|
}
|
|
200
|
+
function windowByHeights(heights, scroll, budget) {
|
|
201
|
+
const n = heights.length;
|
|
202
|
+
if (n === 0 || budget <= 0) return { start: 0, end: 0, maxScroll: 0 };
|
|
203
|
+
let acc = 0;
|
|
204
|
+
let maxScroll = 0;
|
|
205
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
206
|
+
acc += Math.max(1, heights[i]);
|
|
207
|
+
if (acc > budget) {
|
|
208
|
+
maxScroll = i + 1;
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const start = Math.max(0, Math.min(scroll, maxScroll));
|
|
213
|
+
let used = 0;
|
|
214
|
+
let end = start;
|
|
215
|
+
for (let i = start; i < n; i++) {
|
|
216
|
+
const h = Math.max(1, heights[i]);
|
|
217
|
+
if (used + h > budget && end > start) break;
|
|
218
|
+
used += h;
|
|
219
|
+
end = i + 1;
|
|
220
|
+
}
|
|
221
|
+
return { start, end, maxScroll };
|
|
222
|
+
}
|
|
233
223
|
function groupedListWindow(buckets, selected, budget) {
|
|
234
224
|
const total = buckets.length;
|
|
235
225
|
if (budget <= 0 || total <= 0) return { start: 0, end: 0 };
|
|
@@ -267,9 +257,353 @@ var init_format = __esm({
|
|
|
267
257
|
}
|
|
268
258
|
});
|
|
269
259
|
|
|
270
|
-
// src/tui/
|
|
260
|
+
// src/tui/terminal.ts
|
|
261
|
+
import { useWindowSize } from "ink";
|
|
262
|
+
function useTerminalSize() {
|
|
263
|
+
return useWindowSize();
|
|
264
|
+
}
|
|
265
|
+
var init_terminal = __esm({
|
|
266
|
+
"src/tui/terminal.ts"() {
|
|
267
|
+
"use strict";
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// src/tui/liveCaptions.ts
|
|
272
|
+
function initialLiveCaptionsState() {
|
|
273
|
+
return { status: "connecting", lines: [] };
|
|
274
|
+
}
|
|
275
|
+
function liveCaptionReducer(state, event) {
|
|
276
|
+
switch (event.kind) {
|
|
277
|
+
case "status": {
|
|
278
|
+
const next = { ...state, status: event.status };
|
|
279
|
+
if (event.status === "live" && state.startedAtMs == null) {
|
|
280
|
+
next.startedAtMs = event.atMs ?? state.startedAtMs;
|
|
281
|
+
}
|
|
282
|
+
if (event.status !== "error") next.error = void 0;
|
|
283
|
+
return next;
|
|
284
|
+
}
|
|
285
|
+
case "partial":
|
|
286
|
+
return { ...state, partial: event.text };
|
|
287
|
+
case "final": {
|
|
288
|
+
const lines = [...state.lines, event.line];
|
|
289
|
+
return { ...state, lines, partial: void 0 };
|
|
290
|
+
}
|
|
291
|
+
case "translationPartial":
|
|
292
|
+
return { ...state, translationPartial: event.text };
|
|
293
|
+
case "translationFinal": {
|
|
294
|
+
const lines = [...state.lines];
|
|
295
|
+
let idx = event.segmentId ? lines.findIndex((l) => l.id === event.segmentId) : -1;
|
|
296
|
+
if (idx < 0) idx = lines.length - 1;
|
|
297
|
+
if (idx >= 0) lines[idx] = { ...lines[idx], translation: event.text };
|
|
298
|
+
return { ...state, lines, translationPartial: void 0 };
|
|
299
|
+
}
|
|
300
|
+
case "error":
|
|
301
|
+
return { ...state, status: "error", error: event.message };
|
|
302
|
+
default:
|
|
303
|
+
return state;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function recordingStateToStatus(state) {
|
|
307
|
+
switch (state) {
|
|
308
|
+
case "idle":
|
|
309
|
+
case "starting":
|
|
310
|
+
return "connecting";
|
|
311
|
+
case "recording":
|
|
312
|
+
return "live";
|
|
313
|
+
case "stopping":
|
|
314
|
+
case "finalizing":
|
|
315
|
+
case "uploading":
|
|
316
|
+
case "completed":
|
|
317
|
+
case "cancelled":
|
|
318
|
+
return "stopped";
|
|
319
|
+
case "failed":
|
|
320
|
+
return "error";
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function sidecarToLiveCaptionEvent(event) {
|
|
324
|
+
switch (event.type) {
|
|
325
|
+
case "ready":
|
|
326
|
+
return { kind: "status", status: "connecting" };
|
|
327
|
+
case "recording.state":
|
|
328
|
+
if (event.state === "failed") {
|
|
329
|
+
return { kind: "error", message: event.message ?? "Recording failed" };
|
|
330
|
+
}
|
|
331
|
+
return { kind: "status", status: recordingStateToStatus(event.state) };
|
|
332
|
+
case "live_caption.delta": {
|
|
333
|
+
if (event.stream === "translation") {
|
|
334
|
+
return event.isFinal ? { kind: "translationFinal", segmentId: event.segmentId, text: event.text } : { kind: "translationPartial", text: event.text };
|
|
335
|
+
}
|
|
336
|
+
if (event.isFinal) {
|
|
337
|
+
return {
|
|
338
|
+
kind: "final",
|
|
339
|
+
line: {
|
|
340
|
+
id: event.segmentId ?? `${event.startMs ?? event.atMs ?? 0}`,
|
|
341
|
+
text: event.text,
|
|
342
|
+
speaker: event.speaker,
|
|
343
|
+
atMs: event.startMs ?? event.atMs
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
return { kind: "partial", text: event.text };
|
|
348
|
+
}
|
|
349
|
+
case "error":
|
|
350
|
+
return { kind: "error", message: event.message };
|
|
351
|
+
case "audio.level":
|
|
352
|
+
case "local_artifact.upserted":
|
|
353
|
+
return null;
|
|
354
|
+
default:
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function liveCaptionStatusLabel(status) {
|
|
359
|
+
switch (status) {
|
|
360
|
+
case "connecting":
|
|
361
|
+
return "Connecting\u2026";
|
|
362
|
+
case "live":
|
|
363
|
+
return "\u25CF LIVE";
|
|
364
|
+
case "reconnecting":
|
|
365
|
+
return "Reconnecting\u2026";
|
|
366
|
+
case "stopped":
|
|
367
|
+
return "Stopped";
|
|
368
|
+
case "error":
|
|
369
|
+
return "Error";
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
var init_liveCaptions = __esm({
|
|
373
|
+
"src/tui/liveCaptions.ts"() {
|
|
374
|
+
"use strict";
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// src/tui/LiveCaptionsView.tsx
|
|
379
|
+
import { useMemo, useState } from "react";
|
|
380
|
+
import { Box, Text, useInput } from "ink";
|
|
381
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
382
|
+
function LiveCaptionsView({
|
|
383
|
+
state,
|
|
384
|
+
nowMs
|
|
385
|
+
}) {
|
|
386
|
+
const size = useTerminalSize();
|
|
387
|
+
const [scrollUp, setScrollUp] = useState(0);
|
|
388
|
+
const innerWidth = Math.max(10, size.columns - 2);
|
|
389
|
+
const items = useMemo(() => {
|
|
390
|
+
const rows = [];
|
|
391
|
+
for (const l of state.lines) {
|
|
392
|
+
rows.push({ key: l.id, kind: "final", speaker: l.speaker, text: l.text });
|
|
393
|
+
if (l.translation) rows.push({ key: `${l.id}__t`, kind: "translation", text: l.translation });
|
|
394
|
+
}
|
|
395
|
+
if (state.partial && state.partial.length > 0) {
|
|
396
|
+
rows.push({ key: "__partial__", kind: "partial", text: state.partial });
|
|
397
|
+
}
|
|
398
|
+
if (state.translationPartial && state.translationPartial.length > 0) {
|
|
399
|
+
rows.push({ key: "__tpartial__", kind: "translation", text: state.translationPartial });
|
|
400
|
+
}
|
|
401
|
+
return rows;
|
|
402
|
+
}, [state.lines, state.partial, state.translationPartial]);
|
|
403
|
+
const heights = useMemo(
|
|
404
|
+
() => items.map((it) => {
|
|
405
|
+
const prefix = it.kind === "translation" ? "\u21B3 " : it.speaker ? `${it.speaker}: ` : "";
|
|
406
|
+
return Math.max(1, Math.ceil(displayWidth(prefix + it.text) / innerWidth));
|
|
407
|
+
}),
|
|
408
|
+
[items, innerWidth]
|
|
409
|
+
);
|
|
410
|
+
const budget = Math.max(3, size.rows - 3);
|
|
411
|
+
const maxScroll = windowByHeights(heights, Number.MAX_SAFE_INTEGER, budget).maxScroll;
|
|
412
|
+
const top = Math.max(0, maxScroll - scrollUp);
|
|
413
|
+
const win = windowByHeights(heights, top, budget);
|
|
414
|
+
const following = scrollUp === 0;
|
|
415
|
+
const page = Math.max(1, budget - 1);
|
|
416
|
+
useInput((input, key) => {
|
|
417
|
+
if (key.upArrow || input === "k") setScrollUp((s) => Math.min(maxScroll, s + 1));
|
|
418
|
+
else if (key.downArrow || input === "j") setScrollUp((s) => Math.max(0, s - 1));
|
|
419
|
+
else if (key.pageUp || input === "b") setScrollUp((s) => Math.min(maxScroll, s + page));
|
|
420
|
+
else if (key.pageDown || input === " ") setScrollUp((s) => Math.max(0, s - page));
|
|
421
|
+
else if (input === "G") setScrollUp(0);
|
|
422
|
+
else if (input === "g") setScrollUp(maxScroll);
|
|
423
|
+
});
|
|
424
|
+
const elapsed = state.startedAtMs != null ? formatClockMs(Math.max(0, nowMs - state.startedAtMs)) : null;
|
|
425
|
+
const statusColor = STATUS_COLOR[state.status] ?? "white";
|
|
426
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
427
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
428
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: statusColor, children: liveCaptionStatusLabel(state.status) }),
|
|
429
|
+
elapsed ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${elapsed}` }) : null,
|
|
430
|
+
!following ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u23F8 scrolled \u2014 G for live" }) : null
|
|
431
|
+
] }),
|
|
432
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "column", children: items.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: state.status === "error" ? state.error ? `Error: ${state.error}` : "Live captions error" : "Waiting for captions\u2026" }) : items.slice(win.start, win.end).map(
|
|
433
|
+
(it) => it.kind === "translation" ? /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: `\u21B3 ${it.text}` }, it.key) : /* @__PURE__ */ jsxs(Text, { dimColor: it.kind === "partial", italic: it.kind === "partial", children: [
|
|
434
|
+
it.speaker ? /* @__PURE__ */ jsx(Text, { color: "cyan", children: `${it.speaker}: ` }) : null,
|
|
435
|
+
it.text
|
|
436
|
+
] }, it.key)
|
|
437
|
+
) }),
|
|
438
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
439
|
+
maxScroll > 0 ? "\u2191\u2193 scroll \xB7 G live \xB7 " : "",
|
|
440
|
+
"q / esc / \u2190 back"
|
|
441
|
+
] }) })
|
|
442
|
+
] });
|
|
443
|
+
}
|
|
444
|
+
var STATUS_COLOR;
|
|
445
|
+
var init_LiveCaptionsView = __esm({
|
|
446
|
+
"src/tui/LiveCaptionsView.tsx"() {
|
|
447
|
+
"use strict";
|
|
448
|
+
init_format();
|
|
449
|
+
init_terminal();
|
|
450
|
+
init_liveCaptions();
|
|
451
|
+
STATUS_COLOR = {
|
|
452
|
+
connecting: "yellow",
|
|
453
|
+
live: "red",
|
|
454
|
+
reconnecting: "yellow",
|
|
455
|
+
stopped: "gray",
|
|
456
|
+
error: "red"
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// src/tui/LiveCaptionsScreen.tsx
|
|
462
|
+
import { useEffect, useState as useState2 } from "react";
|
|
463
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
464
|
+
function LiveCaptionsScreen({
|
|
465
|
+
source,
|
|
466
|
+
now = () => Date.now()
|
|
467
|
+
}) {
|
|
468
|
+
const [state, setState] = useState2(initialLiveCaptionsState);
|
|
469
|
+
const [tick, setTick] = useState2(() => now());
|
|
470
|
+
useEffect(() => {
|
|
471
|
+
const unsubscribe = source.onEvent((event) => {
|
|
472
|
+
const mapped = sidecarToLiveCaptionEvent(event);
|
|
473
|
+
if (mapped) setState((s) => liveCaptionReducer(s, mapped));
|
|
474
|
+
});
|
|
475
|
+
return unsubscribe;
|
|
476
|
+
}, [source]);
|
|
477
|
+
useEffect(() => {
|
|
478
|
+
const id = setInterval(() => setTick(now()), 1e3);
|
|
479
|
+
return () => clearInterval(id);
|
|
480
|
+
}, []);
|
|
481
|
+
return /* @__PURE__ */ jsx2(LiveCaptionsView, { state, nowMs: tick });
|
|
482
|
+
}
|
|
483
|
+
var init_LiveCaptionsScreen = __esm({
|
|
484
|
+
"src/tui/LiveCaptionsScreen.tsx"() {
|
|
485
|
+
"use strict";
|
|
486
|
+
init_LiveCaptionsView();
|
|
487
|
+
init_liveCaptions();
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
// src/tui/AccountView.tsx
|
|
271
492
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
272
|
-
import { jsx as
|
|
493
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
494
|
+
function AccountView({ status }) {
|
|
495
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
496
|
+
/* @__PURE__ */ jsx4(Text2, { dimColor: true, children: "\u2039 Account" }),
|
|
497
|
+
status === "loading" || status === void 0 ? /* @__PURE__ */ jsx4(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text2, { dimColor: true, children: "Loading account\u2026" }) }) : status === "error" ? /* @__PURE__ */ jsx4(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text2, { color: "red", children: "Couldn't load account status" }) }) : !status.loggedIn ? /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
498
|
+
/* @__PURE__ */ jsx4(Text2, { color: "yellow", children: "Not signed in" }),
|
|
499
|
+
/* @__PURE__ */ jsx4(Text2, { dimColor: true, children: `origin ${status.origin}` }),
|
|
500
|
+
/* @__PURE__ */ jsx4(Text2, { dimColor: true, children: "Run `recappi auth login` to sign in." })
|
|
501
|
+
] }) : /* @__PURE__ */ jsx4(AccountBody, { status }),
|
|
502
|
+
/* @__PURE__ */ jsx4(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text2, { dimColor: true, children: "r refresh \xB7 esc back \xB7 q quit" }) })
|
|
503
|
+
] });
|
|
504
|
+
}
|
|
505
|
+
function AccountBody({ status }) {
|
|
506
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
507
|
+
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
508
|
+
/* @__PURE__ */ jsx4(Text2, { bold: true, color: "magenta", children: status.email ?? status.userId ?? "Signed in" }),
|
|
509
|
+
status.email && status.userId ? /* @__PURE__ */ jsx4(Text2, { dimColor: true, children: status.userId }) : null,
|
|
510
|
+
/* @__PURE__ */ jsx4(Text2, { dimColor: true, children: `origin ${status.origin}` })
|
|
511
|
+
] }),
|
|
512
|
+
status.billing ? /* @__PURE__ */ jsx4(Usage, { billing: status.billing }) : null,
|
|
513
|
+
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
514
|
+
/* @__PURE__ */ jsx4(Text2, { bold: true, children: "Local store" }),
|
|
515
|
+
/* @__PURE__ */ jsx4(Text2, { dimColor: true, wrap: "truncate-middle", children: status.localStore.path }),
|
|
516
|
+
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
517
|
+
`${status.localStore.accountScopedArtifacts} artifact${status.localStore.accountScopedArtifacts === 1 ? "" : "s"} for this account`,
|
|
518
|
+
status.localStore.unattributedArtifacts > 0 ? ` \xB7 ${status.localStore.unattributedArtifacts} unattributed` : ""
|
|
519
|
+
] })
|
|
520
|
+
] })
|
|
521
|
+
] });
|
|
522
|
+
}
|
|
523
|
+
function Usage({ billing }) {
|
|
524
|
+
const minutesCap = billing.minutesCap;
|
|
525
|
+
const minutesUsed = billing.minutesUsed;
|
|
526
|
+
const storageCap = billing.storageCapBytes;
|
|
527
|
+
return /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
528
|
+
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
529
|
+
/* @__PURE__ */ jsx4(Text2, { dimColor: true, children: "Plan " }),
|
|
530
|
+
/* @__PURE__ */ jsx4(Text2, { bold: true, children: billing.tier })
|
|
531
|
+
] }),
|
|
532
|
+
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
533
|
+
/* @__PURE__ */ jsx4(Text2, { dimColor: true, children: "Minutes " }),
|
|
534
|
+
minutesCap != null ? /* @__PURE__ */ jsx4(Text2, { color: billing.isOverMinutes ? "red" : "cyan", children: `${progressBar(minutesUsed / Math.max(1, minutesCap), 12)} ` }) : null,
|
|
535
|
+
/* @__PURE__ */ jsx4(Text2, { color: billing.isOverMinutes ? "red" : void 0, children: `${Math.round(minutesUsed)}` }),
|
|
536
|
+
/* @__PURE__ */ jsx4(Text2, { dimColor: true, children: ` / ${minutesCap != null ? Math.round(minutesCap) : "\u221E"} min` }),
|
|
537
|
+
/* @__PURE__ */ jsx4(Text2, { dimColor: true, children: ` (batch ${Math.round(billing.batchMinutesUsed)} \xB7 live ${Math.round(billing.realtimeMinutesUsed)})` })
|
|
538
|
+
] }),
|
|
539
|
+
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
540
|
+
/* @__PURE__ */ jsx4(Text2, { dimColor: true, children: "Storage " }),
|
|
541
|
+
storageCap != null ? /* @__PURE__ */ jsx4(Text2, { color: billing.isOverStorage ? "red" : "cyan", children: `${progressBar(billing.storageBytes / Math.max(1, storageCap), 12)} ` }) : null,
|
|
542
|
+
/* @__PURE__ */ jsx4(Text2, { color: billing.isOverStorage ? "red" : void 0, children: formatBytes2(billing.storageBytes) }),
|
|
543
|
+
/* @__PURE__ */ jsx4(Text2, { dimColor: true, children: ` / ${storageCap != null ? formatBytes2(storageCap) : "\u221E"}` })
|
|
544
|
+
] }),
|
|
545
|
+
billing.isOverMinutes || billing.isOverStorage ? /* @__PURE__ */ jsxs2(Text2, { color: "red", children: [
|
|
546
|
+
billing.isOverMinutes ? "Over minutes limit. " : "",
|
|
547
|
+
billing.isOverStorage ? "Over storage limit." : ""
|
|
548
|
+
] }) : null,
|
|
549
|
+
/* @__PURE__ */ jsx4(Text2, { dimColor: true, children: `Period ${periodText(billing)}` })
|
|
550
|
+
] });
|
|
551
|
+
}
|
|
552
|
+
function periodText(billing) {
|
|
553
|
+
const remainingMs = epochToMs(billing.periodEnd) - Date.now();
|
|
554
|
+
if (!Number.isFinite(remainingMs) || remainingMs <= 0) return "\u2014";
|
|
555
|
+
const days = Math.floor(remainingMs / 864e5);
|
|
556
|
+
if (days >= 1) return `${days}d left`;
|
|
557
|
+
return `${formatClockMs(remainingMs)} left`;
|
|
558
|
+
}
|
|
559
|
+
function epochToMs(value) {
|
|
560
|
+
return value > 1e12 ? value : value * 1e3;
|
|
561
|
+
}
|
|
562
|
+
var init_AccountView = __esm({
|
|
563
|
+
"src/tui/AccountView.tsx"() {
|
|
564
|
+
"use strict";
|
|
565
|
+
init_format();
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
// src/tui/chrome.tsx
|
|
570
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
571
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
572
|
+
function Header({ active }) {
|
|
573
|
+
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
574
|
+
/* @__PURE__ */ jsxs3(Text3, { bold: true, color: "magenta", children: [
|
|
575
|
+
"Recappi",
|
|
576
|
+
" "
|
|
577
|
+
] }),
|
|
578
|
+
/* @__PURE__ */ jsx5(Tab, { num: "1", label: "Overview", active: active === "overview" }),
|
|
579
|
+
/* @__PURE__ */ jsx5(Tab, { num: "2", label: "Jobs", active: active === "jobs" }),
|
|
580
|
+
/* @__PURE__ */ jsx5(Tab, { num: "3", label: "Account", active: active === "account" }),
|
|
581
|
+
/* @__PURE__ */ jsx5(Tab, { num: "4", label: "Record", active: active === "record" })
|
|
582
|
+
] });
|
|
583
|
+
}
|
|
584
|
+
function Tab({
|
|
585
|
+
num,
|
|
586
|
+
label,
|
|
587
|
+
active
|
|
588
|
+
}) {
|
|
589
|
+
const text = ` ${num} ${label} `;
|
|
590
|
+
if (active) {
|
|
591
|
+
return /* @__PURE__ */ jsx5(Text3, { bold: true, inverse: true, color: "cyan", children: text });
|
|
592
|
+
}
|
|
593
|
+
return /* @__PURE__ */ jsx5(Text3, { children: text });
|
|
594
|
+
}
|
|
595
|
+
function Footer({ keys }) {
|
|
596
|
+
return /* @__PURE__ */ jsx5(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: keys }) });
|
|
597
|
+
}
|
|
598
|
+
var init_chrome = __esm({
|
|
599
|
+
"src/tui/chrome.tsx"() {
|
|
600
|
+
"use strict";
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
// src/tui/JobRow.tsx
|
|
605
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
606
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
273
607
|
function JobRow({
|
|
274
608
|
item,
|
|
275
609
|
selected,
|
|
@@ -278,11 +612,11 @@ function JobRow({
|
|
|
278
612
|
const style = statusStyle(item.status);
|
|
279
613
|
const glyph = statusGlyph(item.status, spinnerFrame);
|
|
280
614
|
const title = item.recording?.title ?? item.recordingId;
|
|
281
|
-
return /* @__PURE__ */
|
|
282
|
-
/* @__PURE__ */
|
|
283
|
-
/* @__PURE__ */
|
|
284
|
-
/* @__PURE__ */
|
|
285
|
-
/* @__PURE__ */
|
|
615
|
+
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
616
|
+
/* @__PURE__ */ jsx6(Text4, { color: "cyan", children: selected ? "\u25B8 " : " " }),
|
|
617
|
+
/* @__PURE__ */ jsx6(Text4, { color: style.color, children: `${glyph} ${padCell(style.label, 13)}` }),
|
|
618
|
+
/* @__PURE__ */ jsx6(Text4, { bold: selected, children: padCell(title, 24) }),
|
|
619
|
+
/* @__PURE__ */ jsx6(Text4, { dimColor: !selected, children: jobDetail(item) })
|
|
286
620
|
] });
|
|
287
621
|
}
|
|
288
622
|
var init_JobRow = __esm({
|
|
@@ -293,17 +627,17 @@ var init_JobRow = __esm({
|
|
|
293
627
|
});
|
|
294
628
|
|
|
295
629
|
// src/tui/JobsView.tsx
|
|
296
|
-
import { Box as
|
|
297
|
-
import { jsx as
|
|
630
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
631
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
298
632
|
function JobsView({
|
|
299
633
|
items,
|
|
300
634
|
selectedIndex,
|
|
301
635
|
spinnerFrame
|
|
302
636
|
}) {
|
|
303
637
|
if (items.length === 0) {
|
|
304
|
-
return /* @__PURE__ */
|
|
638
|
+
return /* @__PURE__ */ jsx7(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text5, { dimColor: true, children: "No transcription jobs yet \u2014 run: recappi upload <file> --transcribe" }) });
|
|
305
639
|
}
|
|
306
|
-
return /* @__PURE__ */
|
|
640
|
+
return /* @__PURE__ */ jsx7(Box5, { marginTop: 1, flexDirection: "column", children: items.map((item, index) => /* @__PURE__ */ jsx7(
|
|
307
641
|
JobRow,
|
|
308
642
|
{
|
|
309
643
|
item,
|
|
@@ -321,8 +655,8 @@ var init_JobsView = __esm({
|
|
|
321
655
|
});
|
|
322
656
|
|
|
323
657
|
// src/tui/RecordingRow.tsx
|
|
324
|
-
import { Box as
|
|
325
|
-
import { jsx as
|
|
658
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
659
|
+
import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
326
660
|
function recordingTitle2(item) {
|
|
327
661
|
const named = (item.title || item.summaryTitle || "").trim();
|
|
328
662
|
if (named && !UUID_RE.test(named)) return named;
|
|
@@ -352,26 +686,28 @@ function RecordingRow({
|
|
|
352
686
|
nowMs,
|
|
353
687
|
columns,
|
|
354
688
|
jobStatus,
|
|
355
|
-
spinnerFrame = 0
|
|
689
|
+
spinnerFrame = 0,
|
|
690
|
+
downloaded = false
|
|
356
691
|
}) {
|
|
357
692
|
const { title, showWhen } = recordingLayout(columns);
|
|
358
693
|
const { glyph, color } = recordingProcessingState(item, jobStatus, spinnerFrame);
|
|
359
694
|
const duration3 = item.durationMs ? formatClockMs(item.durationMs) : "\u2014";
|
|
360
|
-
return /* @__PURE__ */
|
|
361
|
-
/* @__PURE__ */
|
|
362
|
-
/* @__PURE__ */
|
|
363
|
-
/* @__PURE__ */
|
|
364
|
-
/* @__PURE__ */
|
|
365
|
-
showWhen ? /* @__PURE__ */
|
|
695
|
+
return /* @__PURE__ */ jsxs5(Box6, { children: [
|
|
696
|
+
/* @__PURE__ */ jsx8(Text6, { color: "cyan", children: selected ? "\u25B8 " : " " }),
|
|
697
|
+
/* @__PURE__ */ jsx8(Text6, { color, children: `${glyph} ` }),
|
|
698
|
+
/* @__PURE__ */ jsx8(Text6, { bold: selected, children: padDisplay(recordingTitle2(item), title) }),
|
|
699
|
+
/* @__PURE__ */ jsx8(Text6, { dimColor: true, children: padDisplay(duration3, LENGTH_W) }),
|
|
700
|
+
showWhen ? /* @__PURE__ */ jsx8(Text6, { dimColor: true, children: padDisplay(formatAge(item.createdAt, nowMs), WHEN_W) }) : null,
|
|
701
|
+
/* @__PURE__ */ jsx8(Text6, { color: "green", children: downloaded ? " \u2913" : " " })
|
|
366
702
|
] });
|
|
367
703
|
}
|
|
368
704
|
function RecordingHeader({ columns }) {
|
|
369
705
|
const { title, showWhen } = recordingLayout(columns);
|
|
370
|
-
return /* @__PURE__ */
|
|
371
|
-
/* @__PURE__ */
|
|
372
|
-
/* @__PURE__ */
|
|
373
|
-
/* @__PURE__ */
|
|
374
|
-
showWhen ? /* @__PURE__ */
|
|
706
|
+
return /* @__PURE__ */ jsxs5(Box6, { children: [
|
|
707
|
+
/* @__PURE__ */ jsx8(Text6, { dimColor: true, children: padDisplay("", MARKER_W + GLYPH_W) }),
|
|
708
|
+
/* @__PURE__ */ jsx8(Text6, { dimColor: true, children: padDisplay("TITLE", title) }),
|
|
709
|
+
/* @__PURE__ */ jsx8(Text6, { dimColor: true, children: padDisplay("LENGTH", LENGTH_W) }),
|
|
710
|
+
showWhen ? /* @__PURE__ */ jsx8(Text6, { dimColor: true, children: "WHEN" }) : null
|
|
375
711
|
] });
|
|
376
712
|
}
|
|
377
713
|
var UUID_RE, MARKER_W, GLYPH_W, LENGTH_W, WHEN_W;
|
|
@@ -388,28 +724,29 @@ var init_RecordingRow = __esm({
|
|
|
388
724
|
});
|
|
389
725
|
|
|
390
726
|
// src/tui/RecordingsView.tsx
|
|
391
|
-
import
|
|
392
|
-
import { Box as
|
|
393
|
-
import { jsx as
|
|
727
|
+
import React3 from "react";
|
|
728
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
729
|
+
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
394
730
|
function RecordingsView({
|
|
395
731
|
items,
|
|
396
732
|
selectedIndex,
|
|
397
733
|
nowMs,
|
|
398
734
|
columns,
|
|
399
735
|
jobStatusByRecording,
|
|
736
|
+
downloadedRecordingIds,
|
|
400
737
|
spinnerFrame = 0
|
|
401
738
|
}) {
|
|
402
739
|
if (items.length === 0) {
|
|
403
|
-
return /* @__PURE__ */
|
|
740
|
+
return /* @__PURE__ */ jsx9(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: "No recordings yet \u2014 run: recappi upload <file>" }) });
|
|
404
741
|
}
|
|
405
|
-
return /* @__PURE__ */
|
|
406
|
-
/* @__PURE__ */
|
|
742
|
+
return /* @__PURE__ */ jsxs6(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
743
|
+
/* @__PURE__ */ jsx9(RecordingHeader, { columns }),
|
|
407
744
|
items.map((item, index) => {
|
|
408
745
|
const bucket = dateBucket(item.createdAt, nowMs);
|
|
409
746
|
const showHeader = index === 0 || bucket !== dateBucket(items[index - 1].createdAt, nowMs);
|
|
410
|
-
return /* @__PURE__ */
|
|
411
|
-
showHeader ? /* @__PURE__ */
|
|
412
|
-
/* @__PURE__ */
|
|
747
|
+
return /* @__PURE__ */ jsxs6(React3.Fragment, { children: [
|
|
748
|
+
showHeader ? /* @__PURE__ */ jsx9(Box7, { marginTop: index === 0 ? 0 : 1, children: /* @__PURE__ */ jsx9(Text7, { bold: true, color: "blue", children: bucket }) }) : null,
|
|
749
|
+
/* @__PURE__ */ jsx9(
|
|
413
750
|
RecordingRow,
|
|
414
751
|
{
|
|
415
752
|
item,
|
|
@@ -417,6 +754,7 @@ function RecordingsView({
|
|
|
417
754
|
nowMs,
|
|
418
755
|
columns,
|
|
419
756
|
jobStatus: jobStatusByRecording?.get(item.recordingId),
|
|
757
|
+
downloaded: downloadedRecordingIds?.has(item.recordingId) ?? false,
|
|
420
758
|
spinnerFrame
|
|
421
759
|
}
|
|
422
760
|
)
|
|
@@ -433,15 +771,15 @@ var init_RecordingsView = __esm({
|
|
|
433
771
|
});
|
|
434
772
|
|
|
435
773
|
// src/tui/RecordingPeek.tsx
|
|
436
|
-
import { Box as
|
|
437
|
-
import { Fragment, jsx as
|
|
774
|
+
import { Box as Box8, Text as Text8 } from "ink";
|
|
775
|
+
import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
438
776
|
function RecordingPeek({
|
|
439
777
|
item,
|
|
440
778
|
summary,
|
|
441
779
|
nowMs,
|
|
442
780
|
width
|
|
443
781
|
}) {
|
|
444
|
-
return /* @__PURE__ */
|
|
782
|
+
return /* @__PURE__ */ jsx10(Box8, { width, borderStyle: "round", borderColor: "gray", paddingX: 1, flexDirection: "column", children: !item ? /* @__PURE__ */ jsx10(Text8, { dimColor: true, children: "No selection" }) : /* @__PURE__ */ jsx10(PeekBody, { item, summary, nowMs }) });
|
|
445
783
|
}
|
|
446
784
|
function PeekBody({
|
|
447
785
|
item,
|
|
@@ -454,30 +792,30 @@ function PeekBody({
|
|
|
454
792
|
formatBytes2(item.sizeBytes) || null,
|
|
455
793
|
item.contentType || null
|
|
456
794
|
].filter(Boolean).join(" \xB7 ");
|
|
457
|
-
return /* @__PURE__ */
|
|
458
|
-
/* @__PURE__ */
|
|
459
|
-
/* @__PURE__ */
|
|
460
|
-
meta3 ? /* @__PURE__ */
|
|
461
|
-
/* @__PURE__ */
|
|
462
|
-
/* @__PURE__ */
|
|
463
|
-
/* @__PURE__ */
|
|
795
|
+
return /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
796
|
+
/* @__PURE__ */ jsx10(Text8, { bold: true, color: "magenta", wrap: "truncate-end", children: recordingTitle2(item) }),
|
|
797
|
+
/* @__PURE__ */ jsx10(Text8, { color: style.color, children: `${style.glyph} ${style.label}` }),
|
|
798
|
+
meta3 ? /* @__PURE__ */ jsx10(Text8, { dimColor: true, children: meta3 }) : null,
|
|
799
|
+
/* @__PURE__ */ jsx10(Text8, { dimColor: true, children: formatAge(item.createdAt, nowMs) }),
|
|
800
|
+
/* @__PURE__ */ jsx10(Box8, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx10(SummarySection, { item, summary }) }),
|
|
801
|
+
/* @__PURE__ */ jsx10(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text8, { dimColor: true, children: "\u23CE open \xB7 t transcript \xB7 o web" }) })
|
|
464
802
|
] });
|
|
465
803
|
}
|
|
466
804
|
function SummarySection({
|
|
467
805
|
item,
|
|
468
806
|
summary
|
|
469
807
|
}) {
|
|
470
|
-
if (!item.activeTranscriptId) return /* @__PURE__ */
|
|
471
|
-
if (summary === "loading" || summary === void 0) return /* @__PURE__ */
|
|
472
|
-
if (summary === "error") return /* @__PURE__ */
|
|
808
|
+
if (!item.activeTranscriptId) return /* @__PURE__ */ jsx10(Text8, { dimColor: true, children: "No transcript yet" });
|
|
809
|
+
if (summary === "loading" || summary === void 0) return /* @__PURE__ */ jsx10(Text8, { dimColor: true, children: "Loading summary\u2026" });
|
|
810
|
+
if (summary === "error") return /* @__PURE__ */ jsx10(Text8, { dimColor: true, children: "(summary unavailable)" });
|
|
473
811
|
if (summary.status !== "succeeded" || !summary.tldr) {
|
|
474
|
-
return /* @__PURE__ */
|
|
812
|
+
return /* @__PURE__ */ jsx10(Text8, { dimColor: true, children: `Summary ${summary.status}` });
|
|
475
813
|
}
|
|
476
814
|
const points = (summary.keyPoints ?? []).slice(0, 3);
|
|
477
|
-
return /* @__PURE__ */
|
|
478
|
-
/* @__PURE__ */
|
|
479
|
-
/* @__PURE__ */
|
|
480
|
-
points.length > 0 ? /* @__PURE__ */
|
|
815
|
+
return /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
816
|
+
/* @__PURE__ */ jsx10(Text8, { bold: true, children: "Summary" }),
|
|
817
|
+
/* @__PURE__ */ jsx10(Text8, { children: summary.tldr }),
|
|
818
|
+
points.length > 0 ? /* @__PURE__ */ jsx10(Box8, { marginTop: 1, flexDirection: "column", children: points.map((point, i) => /* @__PURE__ */ jsx10(Text8, { dimColor: true, wrap: "truncate-end", children: `\u2022 ${point}` }, i)) }) : null
|
|
481
819
|
] });
|
|
482
820
|
}
|
|
483
821
|
var init_RecordingPeek = __esm({
|
|
@@ -489,8 +827,8 @@ var init_RecordingPeek = __esm({
|
|
|
489
827
|
});
|
|
490
828
|
|
|
491
829
|
// src/tui/OverviewView.tsx
|
|
492
|
-
import { Box as
|
|
493
|
-
import { jsx as
|
|
830
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
831
|
+
import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
494
832
|
function OverviewView({
|
|
495
833
|
recordings,
|
|
496
834
|
jobs,
|
|
@@ -500,6 +838,7 @@ function OverviewView({
|
|
|
500
838
|
nowMs,
|
|
501
839
|
columns,
|
|
502
840
|
jobStatusByRecording,
|
|
841
|
+
downloadedRecordingIds,
|
|
503
842
|
peekItem,
|
|
504
843
|
peekSummary,
|
|
505
844
|
showPeek = false,
|
|
@@ -508,17 +847,17 @@ function OverviewView({
|
|
|
508
847
|
const jobCounts = countJobs(jobs);
|
|
509
848
|
const running = stats?.jobs.running ?? jobCounts.running;
|
|
510
849
|
const queued = stats?.jobs.queued ?? jobCounts.queued;
|
|
511
|
-
return /* @__PURE__ */
|
|
512
|
-
/* @__PURE__ */
|
|
513
|
-
/* @__PURE__ */
|
|
514
|
-
/* @__PURE__ */
|
|
515
|
-
stats?.recordings.ready != null ? /* @__PURE__ */
|
|
516
|
-
stats?.recordings.totalDurationMs != null ? /* @__PURE__ */
|
|
517
|
-
running > 0 ? /* @__PURE__ */
|
|
518
|
-
queued > 0 ? /* @__PURE__ */
|
|
850
|
+
return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", children: [
|
|
851
|
+
/* @__PURE__ */ jsxs8(Box9, { children: [
|
|
852
|
+
/* @__PURE__ */ jsx11(Text9, { dimColor: true, children: "Recordings " }),
|
|
853
|
+
/* @__PURE__ */ jsx11(Text9, { bold: true, children: stats?.recordings.total ?? recordings.length }),
|
|
854
|
+
stats?.recordings.ready != null ? /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: ` \xB7 ${stats.recordings.ready} ready` }) : null,
|
|
855
|
+
stats?.recordings.totalDurationMs != null ? /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: ` \xB7 ${formatClockMs(stats.recordings.totalDurationMs)} transcribed` }) : null,
|
|
856
|
+
running > 0 ? /* @__PURE__ */ jsx11(Text9, { color: "cyan", children: ` \xB7 ${running} transcribing` }) : null,
|
|
857
|
+
queued > 0 ? /* @__PURE__ */ jsx11(Text9, { color: "yellow", children: ` \xB7 ${queued} queued` }) : null
|
|
519
858
|
] }),
|
|
520
|
-
/* @__PURE__ */
|
|
521
|
-
/* @__PURE__ */
|
|
859
|
+
/* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", alignItems: "flex-start", children: [
|
|
860
|
+
/* @__PURE__ */ jsx11(Box9, { flexGrow: 1, flexDirection: "column", children: /* @__PURE__ */ jsx11(
|
|
522
861
|
RecordingsView,
|
|
523
862
|
{
|
|
524
863
|
items: recordings,
|
|
@@ -526,10 +865,11 @@ function OverviewView({
|
|
|
526
865
|
nowMs,
|
|
527
866
|
columns,
|
|
528
867
|
jobStatusByRecording,
|
|
868
|
+
downloadedRecordingIds,
|
|
529
869
|
spinnerFrame
|
|
530
870
|
}
|
|
531
871
|
) }),
|
|
532
|
-
showPeek ? /* @__PURE__ */
|
|
872
|
+
showPeek ? /* @__PURE__ */ jsx11(Box9, { marginLeft: 1, marginTop: 1, children: /* @__PURE__ */ jsx11(RecordingPeek, { item: peekItem, summary: peekSummary, nowMs, width: peekWidth }) }) : null
|
|
533
873
|
] })
|
|
534
874
|
] });
|
|
535
875
|
}
|
|
@@ -543,8 +883,8 @@ var init_OverviewView = __esm({
|
|
|
543
883
|
});
|
|
544
884
|
|
|
545
885
|
// src/tui/JobDetailView.tsx
|
|
546
|
-
import { Box as
|
|
547
|
-
import { jsx as
|
|
886
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
887
|
+
import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
548
888
|
function JobDetailView({
|
|
549
889
|
item,
|
|
550
890
|
origin,
|
|
@@ -554,13 +894,13 @@ function JobDetailView({
|
|
|
554
894
|
const style = statusStyle(item.status);
|
|
555
895
|
const links = resolveJobLinks(item, origin);
|
|
556
896
|
const title = item.recording?.title ?? item.recordingId;
|
|
557
|
-
return /* @__PURE__ */
|
|
558
|
-
/* @__PURE__ */
|
|
897
|
+
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", paddingX: 1, children: [
|
|
898
|
+
/* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
|
|
559
899
|
"\u2039 Jobs / ",
|
|
560
900
|
title
|
|
561
901
|
] }),
|
|
562
|
-
/* @__PURE__ */
|
|
563
|
-
|
|
902
|
+
/* @__PURE__ */ jsxs9(
|
|
903
|
+
Box10,
|
|
564
904
|
{
|
|
565
905
|
marginTop: 1,
|
|
566
906
|
borderStyle: "round",
|
|
@@ -568,17 +908,17 @@ function JobDetailView({
|
|
|
568
908
|
paddingX: 1,
|
|
569
909
|
flexDirection: "column",
|
|
570
910
|
children: [
|
|
571
|
-
/* @__PURE__ */
|
|
911
|
+
/* @__PURE__ */ jsxs9(Text10, { color: style.color, bold: true, children: [
|
|
572
912
|
style.label,
|
|
573
|
-
item.provider ? /* @__PURE__ */
|
|
913
|
+
item.provider ? /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: ` ${item.provider}` }) : null
|
|
574
914
|
] }),
|
|
575
|
-
/* @__PURE__ */
|
|
915
|
+
/* @__PURE__ */ jsx12(StatusLine, { item, spinnerFrame, nowMs })
|
|
576
916
|
]
|
|
577
917
|
}
|
|
578
918
|
),
|
|
579
|
-
/* @__PURE__ */
|
|
580
|
-
/* @__PURE__ */
|
|
581
|
-
/* @__PURE__ */
|
|
919
|
+
/* @__PURE__ */ jsxs9(Box10, { marginTop: 1, flexDirection: "column", children: [
|
|
920
|
+
/* @__PURE__ */ jsx12(Text10, { bold: true, children: "Timeline" }),
|
|
921
|
+
/* @__PURE__ */ jsx12(
|
|
582
922
|
TimelineRow,
|
|
583
923
|
{
|
|
584
924
|
label: "Enqueued",
|
|
@@ -587,7 +927,7 @@ function JobDetailView({
|
|
|
587
927
|
nowMs
|
|
588
928
|
}
|
|
589
929
|
),
|
|
590
|
-
/* @__PURE__ */
|
|
930
|
+
/* @__PURE__ */ jsx12(
|
|
591
931
|
TimelineRow,
|
|
592
932
|
{
|
|
593
933
|
label: "Started",
|
|
@@ -596,7 +936,7 @@ function JobDetailView({
|
|
|
596
936
|
nowMs
|
|
597
937
|
}
|
|
598
938
|
),
|
|
599
|
-
/* @__PURE__ */
|
|
939
|
+
/* @__PURE__ */ jsx12(
|
|
600
940
|
TimelineRow,
|
|
601
941
|
{
|
|
602
942
|
label: item.status === "failed" ? "Failed" : item.status === "running" ? "Transcribing" : "Finished",
|
|
@@ -608,21 +948,21 @@ function JobDetailView({
|
|
|
608
948
|
}
|
|
609
949
|
)
|
|
610
950
|
] }),
|
|
611
|
-
/* @__PURE__ */
|
|
612
|
-
/* @__PURE__ */
|
|
951
|
+
/* @__PURE__ */ jsx12(Box10, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text10, { children: [
|
|
952
|
+
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: "Recording " }),
|
|
613
953
|
title,
|
|
614
|
-
item.recording?.durationMs ? /* @__PURE__ */
|
|
954
|
+
item.recording?.durationMs ? /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: ` \xB7 ${formatClockMs(item.recording.durationMs)}` }) : null
|
|
615
955
|
] }) }),
|
|
616
|
-
/* @__PURE__ */
|
|
617
|
-
/* @__PURE__ */
|
|
618
|
-
/* @__PURE__ */
|
|
619
|
-
/* @__PURE__ */
|
|
620
|
-
/* @__PURE__ */
|
|
621
|
-
/* @__PURE__ */
|
|
622
|
-
/* @__PURE__ */
|
|
623
|
-
/* @__PURE__ */
|
|
956
|
+
/* @__PURE__ */ jsx12(Box10, { marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsxs9(Text10, { children: [
|
|
957
|
+
/* @__PURE__ */ jsx12(Text10, { color: links.webUrl ? "cyan" : "gray", children: "o open" }),
|
|
958
|
+
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: " \xB7 " }),
|
|
959
|
+
/* @__PURE__ */ jsx12(Text10, { color: links.webUrl ? "cyan" : "gray", children: "w web" }),
|
|
960
|
+
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: " \xB7 " }),
|
|
961
|
+
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: "m mac app (soon)" }),
|
|
962
|
+
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: " \xB7 " }),
|
|
963
|
+
/* @__PURE__ */ jsx12(Text10, { color: links.webUrl ? "cyan" : "gray", children: "c copy" })
|
|
624
964
|
] }) }),
|
|
625
|
-
/* @__PURE__ */
|
|
965
|
+
/* @__PURE__ */ jsx12(Box10, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
|
|
626
966
|
"esc back \xB7 t transcript",
|
|
627
967
|
item.transcriptId ? "" : " (when ready)",
|
|
628
968
|
" \xB7 q quit"
|
|
@@ -639,24 +979,24 @@ function StatusLine({
|
|
|
639
979
|
const elapsed = item.startedAt ? ` \xB7 ${formatClockMs(nowMs - item.startedAt)} elapsed` : "";
|
|
640
980
|
if (fraction != null) {
|
|
641
981
|
const pct = Math.round(fraction * 100);
|
|
642
|
-
return /* @__PURE__ */
|
|
982
|
+
return /* @__PURE__ */ jsxs9(Text10, { children: [
|
|
643
983
|
`${progressBar(fraction)} ${pct}% ${formatClockMs(item.processedDurationMs)} / ${formatClockMs(
|
|
644
984
|
item.recording?.durationMs
|
|
645
985
|
)}`,
|
|
646
|
-
/* @__PURE__ */
|
|
986
|
+
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: elapsed })
|
|
647
987
|
] });
|
|
648
988
|
}
|
|
649
989
|
const spinner = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"][spinnerFrame % 10];
|
|
650
|
-
return /* @__PURE__ */
|
|
990
|
+
return /* @__PURE__ */ jsxs9(Text10, { children: [
|
|
651
991
|
`${spinner} transcribing\u2026`,
|
|
652
|
-
/* @__PURE__ */
|
|
992
|
+
/* @__PURE__ */ jsx12(Text10, { dimColor: true, children: elapsed })
|
|
653
993
|
] });
|
|
654
994
|
}
|
|
655
995
|
if (item.status === "succeeded")
|
|
656
|
-
return /* @__PURE__ */
|
|
657
|
-
if (item.status === "queued") return /* @__PURE__ */
|
|
658
|
-
if (item.status === "failed") return /* @__PURE__ */
|
|
659
|
-
return /* @__PURE__ */
|
|
996
|
+
return /* @__PURE__ */ jsx12(Text10, { children: item.transcriptId ? "transcript ready" : "done" });
|
|
997
|
+
if (item.status === "queued") return /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: "waiting to start\u2026" });
|
|
998
|
+
if (item.status === "failed") return /* @__PURE__ */ jsx12(Text10, { color: "red", children: "transcription failed" });
|
|
999
|
+
return /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: item.status });
|
|
660
1000
|
}
|
|
661
1001
|
function TimelineRow({
|
|
662
1002
|
label,
|
|
@@ -669,10 +1009,10 @@ function TimelineRow({
|
|
|
669
1009
|
const glyph = failed ? "\u2717" : done ? "\u2713" : running ? "\u280B" : "\u25CB";
|
|
670
1010
|
const color = failed ? "red" : done ? "green" : running ? "cyan" : "gray";
|
|
671
1011
|
const age = at ? formatAge(at, nowMs) : running ? "now" : "";
|
|
672
|
-
return /* @__PURE__ */
|
|
673
|
-
/* @__PURE__ */
|
|
674
|
-
/* @__PURE__ */
|
|
675
|
-
age ? /* @__PURE__ */
|
|
1012
|
+
return /* @__PURE__ */ jsxs9(Box10, { children: [
|
|
1013
|
+
/* @__PURE__ */ jsx12(Text10, { color, children: ` ${glyph} ` }),
|
|
1014
|
+
/* @__PURE__ */ jsx12(Text10, { dimColor: !done && !running, children: label }),
|
|
1015
|
+
age ? /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: ` ${age}` }) : null
|
|
676
1016
|
] });
|
|
677
1017
|
}
|
|
678
1018
|
var init_JobDetailView = __esm({
|
|
@@ -683,14 +1023,19 @@ var init_JobDetailView = __esm({
|
|
|
683
1023
|
});
|
|
684
1024
|
|
|
685
1025
|
// src/tui/RecordingDetailView.tsx
|
|
686
|
-
import {
|
|
687
|
-
import {
|
|
1026
|
+
import React4, { useMemo as useMemo2, useState as useState3 } from "react";
|
|
1027
|
+
import { Box as Box11, Text as Text11, useInput as useInput3 } from "ink";
|
|
1028
|
+
import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
688
1029
|
function RecordingDetailView({
|
|
689
1030
|
item,
|
|
690
1031
|
nowMs,
|
|
691
1032
|
transcript,
|
|
692
1033
|
audio
|
|
693
1034
|
}) {
|
|
1035
|
+
const size = useTerminalSize();
|
|
1036
|
+
const [tab, setTab] = useState3("summary");
|
|
1037
|
+
const [scroll, setScroll] = useState3(0);
|
|
1038
|
+
const [chapterSel, setChapterSel] = useState3(0);
|
|
694
1039
|
const style = recordingStatusStyle(item.status);
|
|
695
1040
|
const links = resolveRecordingLinks(item.recordingId, item.origin);
|
|
696
1041
|
const title = recordingTitle2(item);
|
|
@@ -699,27 +1044,81 @@ function RecordingDetailView({
|
|
|
699
1044
|
formatBytes2(item.sizeBytes) || void 0,
|
|
700
1045
|
item.contentType || void 0
|
|
701
1046
|
].filter(Boolean).join(" \xB7 ");
|
|
702
|
-
const
|
|
703
|
-
const
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
1047
|
+
const ready = typeof transcript === "object";
|
|
1048
|
+
const summary = ready ? transcript.summary : void 0;
|
|
1049
|
+
const segments = ready ? transcript.segments : [];
|
|
1050
|
+
const chapters = summary?.timeline ?? [];
|
|
1051
|
+
const innerWidth = Math.max(10, size.columns - 2);
|
|
1052
|
+
const paneBudget = Math.max(3, size.rows - 12);
|
|
1053
|
+
const segHeights = useMemo2(
|
|
1054
|
+
() => segments.map((seg) => {
|
|
1055
|
+
const prefix = `[${formatClockMs(seg.startMs)}] ${seg.speaker ? `${seg.speaker}: ` : ""}`;
|
|
1056
|
+
return Math.max(1, Math.ceil(displayWidth(prefix + seg.text) / innerWidth));
|
|
1057
|
+
}),
|
|
1058
|
+
[segments, innerWidth]
|
|
1059
|
+
);
|
|
1060
|
+
const segWin = windowByHeights(segHeights, scroll, paneBudget);
|
|
1061
|
+
const chapWin = listWindow(Math.min(chapterSel, Math.max(0, chapters.length - 1)), chapters.length, paneBudget);
|
|
1062
|
+
const page = Math.max(1, paneBudget - 1);
|
|
1063
|
+
const scrollable = tab === "transcript" ? segWin.maxScroll > 0 : false;
|
|
1064
|
+
const jumpToChapter = (index) => {
|
|
1065
|
+
const chapter = chapters[index];
|
|
1066
|
+
if (!chapter) return;
|
|
1067
|
+
const found = segments.findIndex((s) => s.startMs >= chapter.startMs);
|
|
1068
|
+
setScroll(found < 0 ? Math.max(0, segments.length - 1) : found);
|
|
1069
|
+
setTab("transcript");
|
|
1070
|
+
};
|
|
1071
|
+
useInput3((input, key) => {
|
|
1072
|
+
if (!item.activeTranscriptId || !ready) return;
|
|
1073
|
+
if (key.tab) {
|
|
1074
|
+
setTab((t) => TAB_ORDER[(TAB_ORDER.indexOf(t) + (key.shift ? TAB_ORDER.length - 1 : 1)) % TAB_ORDER.length]);
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
if (tab === "summary") return;
|
|
1078
|
+
if (tab === "chapters") {
|
|
1079
|
+
if (key.downArrow || input === "j") setChapterSel((i) => Math.min(chapters.length - 1, i + 1));
|
|
1080
|
+
else if (key.upArrow || input === "k") setChapterSel((i) => Math.max(0, i - 1));
|
|
1081
|
+
else if (key.return || key.rightArrow) jumpToChapter(chapterSel);
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
if (key.downArrow || input === "j") setScroll((s) => Math.min(segWin.maxScroll, s + 1));
|
|
1085
|
+
else if (key.upArrow || input === "k") setScroll((s) => Math.max(0, s - 1));
|
|
1086
|
+
else if (key.pageDown || input === " ") setScroll((s) => Math.min(segWin.maxScroll, s + page));
|
|
1087
|
+
else if (key.pageUp || input === "b") setScroll((s) => Math.max(0, s - page));
|
|
1088
|
+
else if (input === "g") setScroll(0);
|
|
1089
|
+
else if (input === "G") setScroll(segWin.maxScroll);
|
|
1090
|
+
});
|
|
1091
|
+
return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", paddingX: 1, children: [
|
|
1092
|
+
/* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "\u2039 Recordings" }),
|
|
1093
|
+
/* @__PURE__ */ jsx13(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text11, { bold: true, color: "magenta", children: title }) }),
|
|
1094
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
1095
|
+
/* @__PURE__ */ jsx13(Text11, { color: style.color, children: `${style.glyph} ${style.label}` }),
|
|
1096
|
+
/* @__PURE__ */ jsx13(Text11, { dimColor: true, children: ` ${formatAge(item.createdAt, nowMs) || "\u2014"}` })
|
|
1097
|
+
] }),
|
|
1098
|
+
meta3 ? /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: meta3 }) : null,
|
|
1099
|
+
/* @__PURE__ */ jsx13(AudioActionRow, { item, audio }),
|
|
1100
|
+
!item.activeTranscriptId ? /* @__PURE__ */ jsx13(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "Transcript not available yet" }) }) : transcript === "loading" || transcript === void 0 ? /* @__PURE__ */ jsx13(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "Loading\u2026" }) }) : transcript === "error" ? /* @__PURE__ */ jsx13(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "(transcript unavailable)" }) }) : /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
1101
|
+
/* @__PURE__ */ jsx13(TabBar, { active: tab }),
|
|
1102
|
+
/* @__PURE__ */ jsx13(Box11, { marginTop: 1, flexDirection: "column", children: tab === "summary" ? /* @__PURE__ */ jsx13(SummaryPane, { summary, budget: paneBudget }) : tab === "chapters" ? /* @__PURE__ */ jsx13(ChaptersPane, { chapters, win: chapWin, selectedIndex: chapterSel }) : /* @__PURE__ */ jsx13(TranscriptPane, { segments, win: segWin }) })
|
|
710
1103
|
] }),
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
`
|
|
717
|
-
item.activeTranscriptId ? " \xB7 t
|
|
1104
|
+
/* @__PURE__ */ jsx13(Box11, { marginTop: 1, children: /* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
|
|
1105
|
+
ready ? "tab switch" : "",
|
|
1106
|
+
ready && tab === "chapters" ? " \xB7 \u2191\u2193 select \xB7 \u23CE jump" : "",
|
|
1107
|
+
scrollable ? " \xB7 \u2191\u2193 scroll" : "",
|
|
1108
|
+
ready ? " \xB7 " : "",
|
|
1109
|
+
`o open \xB7 d download \xB7 f finder`,
|
|
1110
|
+
item.activeTranscriptId ? " \xB7 t full" : "",
|
|
718
1111
|
links.webUrl ? " \xB7 w web" : "",
|
|
719
|
-
" \xB7
|
|
1112
|
+
" \xB7 esc back"
|
|
720
1113
|
] }) })
|
|
721
1114
|
] });
|
|
722
1115
|
}
|
|
1116
|
+
function TabBar({ active }) {
|
|
1117
|
+
return /* @__PURE__ */ jsx13(Box11, { marginTop: 1, children: TAB_ORDER.map((tab, i) => /* @__PURE__ */ jsxs10(React4.Fragment, { children: [
|
|
1118
|
+
i > 0 ? /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: " " }) : null,
|
|
1119
|
+
tab === active ? /* @__PURE__ */ jsx13(Text11, { inverse: true, bold: true, children: ` ${TAB_LABEL[tab]} ` }) : /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: ` ${TAB_LABEL[tab]} ` })
|
|
1120
|
+
] }, tab)) });
|
|
1121
|
+
}
|
|
723
1122
|
function AudioActionRow({
|
|
724
1123
|
item,
|
|
725
1124
|
audio
|
|
@@ -728,149 +1127,185 @@ function AudioActionRow({
|
|
|
728
1127
|
const status = audio?.status ?? "idle";
|
|
729
1128
|
let line;
|
|
730
1129
|
if (!ready) {
|
|
731
|
-
line = /* @__PURE__ */
|
|
1130
|
+
line = /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "Audio available once the recording is ready" });
|
|
732
1131
|
} else if (status === "downloading") {
|
|
733
|
-
line = /* @__PURE__ */
|
|
1132
|
+
line = /* @__PURE__ */ jsx13(Text11, { color: "cyan", children: "Downloading audio\u2026" });
|
|
734
1133
|
} else if (status === "opening") {
|
|
735
|
-
line = /* @__PURE__ */
|
|
1134
|
+
line = /* @__PURE__ */ jsx13(Text11, { color: "cyan", children: "Opening\u2026" });
|
|
736
1135
|
} else if (status === "error") {
|
|
737
|
-
line = /* @__PURE__ */
|
|
1136
|
+
line = /* @__PURE__ */ jsx13(Text11, { color: "red", children: audio?.error ? `Audio failed: ${audio.error}` : "Audio failed" });
|
|
738
1137
|
} else if (status === "ready" && audio?.localPath) {
|
|
739
|
-
line = /* @__PURE__ */
|
|
740
|
-
/* @__PURE__ */
|
|
741
|
-
/* @__PURE__ */
|
|
1138
|
+
line = /* @__PURE__ */ jsxs10(Text11, { children: [
|
|
1139
|
+
/* @__PURE__ */ jsx13(Text11, { color: "green", children: "\u2713 Downloaded " }),
|
|
1140
|
+
/* @__PURE__ */ jsx13(Text11, { dimColor: true, wrap: "truncate-middle", children: audio.localPath })
|
|
742
1141
|
] });
|
|
743
1142
|
} else {
|
|
744
|
-
line = /* @__PURE__ */
|
|
745
|
-
/* @__PURE__ */
|
|
746
|
-
/* @__PURE__ */
|
|
747
|
-
/* @__PURE__ */
|
|
748
|
-
/* @__PURE__ */
|
|
749
|
-
/* @__PURE__ */
|
|
750
|
-
/* @__PURE__ */
|
|
1143
|
+
line = /* @__PURE__ */ jsxs10(Text11, { children: [
|
|
1144
|
+
/* @__PURE__ */ jsx13(Text11, { color: "cyan", children: "o" }),
|
|
1145
|
+
/* @__PURE__ */ jsx13(Text11, { dimColor: true, children: " open in player \xB7 " }),
|
|
1146
|
+
/* @__PURE__ */ jsx13(Text11, { color: "cyan", children: "d" }),
|
|
1147
|
+
/* @__PURE__ */ jsx13(Text11, { dimColor: true, children: " download \xB7 " }),
|
|
1148
|
+
/* @__PURE__ */ jsx13(Text11, { color: "cyan", children: "f" }),
|
|
1149
|
+
/* @__PURE__ */ jsx13(Text11, { dimColor: true, children: " reveal in Finder" })
|
|
751
1150
|
] });
|
|
752
1151
|
}
|
|
753
|
-
return /* @__PURE__ */
|
|
754
|
-
/* @__PURE__ */
|
|
1152
|
+
return /* @__PURE__ */ jsxs10(Box11, { marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: [
|
|
1153
|
+
/* @__PURE__ */ jsx13(Text11, { color: ready ? "cyan" : "gray", children: "\u266A " }),
|
|
755
1154
|
line
|
|
756
1155
|
] });
|
|
757
1156
|
}
|
|
758
|
-
function
|
|
1157
|
+
function SummaryPane({
|
|
759
1158
|
summary,
|
|
760
|
-
|
|
761
|
-
|
|
1159
|
+
budget
|
|
1160
|
+
}) {
|
|
1161
|
+
if (!summary || summary.status !== "succeeded" || !summary.tldr) {
|
|
1162
|
+
return /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: `Summary ${summary?.status ?? "unavailable"}` });
|
|
1163
|
+
}
|
|
1164
|
+
const points = (summary.keyPoints ?? []).slice(0, Math.max(1, budget - 4));
|
|
1165
|
+
return /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
1166
|
+
/* @__PURE__ */ jsx13(Text11, { children: summary.tldr }),
|
|
1167
|
+
points.length > 0 ? /* @__PURE__ */ jsx13(Box11, { marginTop: 1, flexDirection: "column", children: points.map((point, i) => /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: `\u2022 ${point}` }, i)) }) : null
|
|
1168
|
+
] });
|
|
1169
|
+
}
|
|
1170
|
+
function ChaptersPane({
|
|
1171
|
+
chapters,
|
|
1172
|
+
win,
|
|
1173
|
+
selectedIndex
|
|
762
1174
|
}) {
|
|
763
|
-
if (
|
|
764
|
-
return /* @__PURE__ */
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
1175
|
+
if (chapters.length === 0) return /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "No chapters" });
|
|
1176
|
+
return /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
1177
|
+
chapters.slice(win.start, win.end).map((chapter, i) => {
|
|
1178
|
+
const index = win.start + i;
|
|
1179
|
+
const selected = index === selectedIndex;
|
|
1180
|
+
return /* @__PURE__ */ jsxs10(Text11, { wrap: "truncate-end", children: [
|
|
1181
|
+
/* @__PURE__ */ jsx13(Text11, { color: "cyan", children: selected ? "\u25B8 " : " " }),
|
|
1182
|
+
/* @__PURE__ */ jsx13(Text11, { color: "blue", children: `[${formatClockMs(chapter.startMs)}] ` }),
|
|
1183
|
+
/* @__PURE__ */ jsx13(Text11, { bold: selected, children: chapter.title })
|
|
1184
|
+
] }, index);
|
|
1185
|
+
}),
|
|
1186
|
+
win.end < chapters.length || win.start > 0 ? /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: ` ${selectedIndex + 1} / ${chapters.length}` }) : null
|
|
770
1187
|
] });
|
|
771
1188
|
}
|
|
772
|
-
function
|
|
1189
|
+
function TranscriptPane({
|
|
773
1190
|
segments,
|
|
774
|
-
|
|
775
|
-
hasTranscript
|
|
1191
|
+
win
|
|
776
1192
|
}) {
|
|
777
|
-
if (
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
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
|
|
1193
|
+
if (segments.length === 0) return /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "(no segments)" });
|
|
1194
|
+
return /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
1195
|
+
segments.slice(win.start, win.end).map((seg, i) => /* @__PURE__ */ jsxs10(Text11, { children: [
|
|
1196
|
+
/* @__PURE__ */ jsx13(Text11, { color: "blue", children: `[${formatClockMs(seg.startMs)}] ` }),
|
|
1197
|
+
seg.speaker ? /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: `${seg.speaker} ` }) : null,
|
|
1198
|
+
/* @__PURE__ */ jsx13(Text11, { children: seg.text })
|
|
1199
|
+
] }, win.start + i)),
|
|
1200
|
+
win.maxScroll > 0 ? /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: ` ${win.start + 1}\u2013${win.end} / ${segments.length}` }) : null
|
|
792
1201
|
] });
|
|
793
1202
|
}
|
|
794
|
-
var
|
|
1203
|
+
var TAB_ORDER, TAB_LABEL;
|
|
795
1204
|
var init_RecordingDetailView = __esm({
|
|
796
1205
|
"src/tui/RecordingDetailView.tsx"() {
|
|
797
1206
|
"use strict";
|
|
798
1207
|
init_format();
|
|
799
1208
|
init_RecordingRow();
|
|
800
|
-
|
|
1209
|
+
init_terminal();
|
|
1210
|
+
TAB_ORDER = ["summary", "chapters", "transcript"];
|
|
1211
|
+
TAB_LABEL = {
|
|
1212
|
+
summary: "Summary",
|
|
1213
|
+
chapters: "Chapters",
|
|
1214
|
+
transcript: "Transcript"
|
|
1215
|
+
};
|
|
801
1216
|
}
|
|
802
1217
|
});
|
|
803
1218
|
|
|
804
1219
|
// src/tui/TranscriptView.tsx
|
|
805
|
-
import {
|
|
806
|
-
import {
|
|
1220
|
+
import { useMemo as useMemo3, useState as useState4 } from "react";
|
|
1221
|
+
import { Box as Box12, Text as Text12, useInput as useInput4 } from "ink";
|
|
1222
|
+
import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
807
1223
|
function TranscriptView({ loading, data, error: error51 }) {
|
|
1224
|
+
const size = useTerminalSize();
|
|
1225
|
+
const [scroll, setScroll] = useState4(0);
|
|
1226
|
+
const segments = data?.segments ?? [];
|
|
1227
|
+
const innerWidth = Math.max(10, size.columns - 2);
|
|
1228
|
+
const heights = useMemo3(
|
|
1229
|
+
() => segments.map((s) => {
|
|
1230
|
+
const prefix = `[${formatClockMs(s.startMs)}] ${s.speaker ? `${s.speaker}: ` : ""}`;
|
|
1231
|
+
return Math.max(1, Math.ceil(displayWidth(prefix + s.text) / innerWidth));
|
|
1232
|
+
}),
|
|
1233
|
+
[segments, innerWidth]
|
|
1234
|
+
);
|
|
1235
|
+
const budget = Math.max(3, size.rows - 3);
|
|
1236
|
+
const win = windowByHeights(heights, scroll, budget);
|
|
1237
|
+
const page = Math.max(1, budget - 1);
|
|
1238
|
+
useInput4((input, key) => {
|
|
1239
|
+
if (key.downArrow || input === "j") setScroll((s) => Math.min(win.maxScroll, s + 1));
|
|
1240
|
+
else if (key.upArrow || input === "k") setScroll((s) => Math.max(0, s - 1));
|
|
1241
|
+
else if (key.pageDown || input === " ") setScroll((s) => Math.min(win.maxScroll, s + page));
|
|
1242
|
+
else if (key.pageUp || input === "b") setScroll((s) => Math.max(0, s - page));
|
|
1243
|
+
else if (input === "g") setScroll(0);
|
|
1244
|
+
else if (input === "G") setScroll(win.maxScroll);
|
|
1245
|
+
});
|
|
808
1246
|
if (loading) {
|
|
809
|
-
return /* @__PURE__ */
|
|
1247
|
+
return /* @__PURE__ */ jsx14(Box12, { paddingX: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Loading transcript\u2026" }) });
|
|
810
1248
|
}
|
|
811
1249
|
if (error51) {
|
|
812
|
-
return /* @__PURE__ */
|
|
813
|
-
/* @__PURE__ */
|
|
1250
|
+
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingX: 1, children: [
|
|
1251
|
+
/* @__PURE__ */ jsxs11(Text12, { color: "red", children: [
|
|
814
1252
|
"! ",
|
|
815
1253
|
error51
|
|
816
1254
|
] }),
|
|
817
|
-
/* @__PURE__ */
|
|
1255
|
+
/* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "q / esc / \u2190 back" })
|
|
818
1256
|
] });
|
|
819
1257
|
}
|
|
820
1258
|
if (!data) {
|
|
821
|
-
return /* @__PURE__ */
|
|
822
|
-
}
|
|
823
|
-
const
|
|
824
|
-
const
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
1259
|
+
return /* @__PURE__ */ jsx14(Box12, { paddingX: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "No transcript." }) });
|
|
1260
|
+
}
|
|
1261
|
+
const title = data.summary?.title ?? "Transcript";
|
|
1262
|
+
const total = segments.length;
|
|
1263
|
+
const more = win.maxScroll > 0;
|
|
1264
|
+
const position = total === 0 ? "" : `${win.start + 1}\u2013${win.end} / ${total}`;
|
|
1265
|
+
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingX: 1, children: [
|
|
1266
|
+
/* @__PURE__ */ jsxs11(Text12, { bold: true, color: "magenta", children: [
|
|
1267
|
+
title,
|
|
1268
|
+
more ? /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: ` ${position}` }) : null
|
|
1269
|
+
] }),
|
|
1270
|
+
/* @__PURE__ */ jsx14(Box12, { marginTop: 1, flexDirection: "column", children: total === 0 ? /* @__PURE__ */ jsx14(Text12, { children: data.text }) : segments.slice(win.start, win.end).map((segment, index) => /* @__PURE__ */ jsxs11(Text12, { children: [
|
|
1271
|
+
/* @__PURE__ */ jsxs11(Text12, { dimColor: true, children: [
|
|
829
1272
|
"[",
|
|
830
1273
|
formatClockMs(segment.startMs),
|
|
831
1274
|
"] "
|
|
832
1275
|
] }),
|
|
833
|
-
segment.speaker ? /* @__PURE__ */
|
|
1276
|
+
segment.speaker ? /* @__PURE__ */ jsxs11(Text12, { color: "cyan", children: [
|
|
834
1277
|
segment.speaker,
|
|
835
1278
|
": "
|
|
836
1279
|
] }) : null,
|
|
837
1280
|
segment.text
|
|
838
|
-
] }, index)) }),
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
] })
|
|
843
|
-
/* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "q / esc / \u2190 back" }) })
|
|
1281
|
+
] }, win.start + index)) }),
|
|
1282
|
+
/* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsxs11(Text12, { dimColor: true, children: [
|
|
1283
|
+
more ? "\u2191\u2193 scroll \xB7 PgUp/PgDn \xB7 g/G top/bottom \xB7 " : "",
|
|
1284
|
+
"q / esc / \u2190 back"
|
|
1285
|
+
] }) })
|
|
844
1286
|
] });
|
|
845
1287
|
}
|
|
846
1288
|
var init_TranscriptView = __esm({
|
|
847
1289
|
"src/tui/TranscriptView.tsx"() {
|
|
848
1290
|
"use strict";
|
|
849
1291
|
init_format();
|
|
850
|
-
|
|
851
|
-
});
|
|
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";
|
|
1292
|
+
init_terminal();
|
|
861
1293
|
}
|
|
862
1294
|
});
|
|
863
1295
|
|
|
864
1296
|
// src/tui/AppShell.tsx
|
|
865
|
-
import { useCallback, useEffect, useState } from "react";
|
|
866
|
-
import { Box as
|
|
867
|
-
import { jsx as
|
|
1297
|
+
import { useCallback, useEffect as useEffect2, useState as useState5 } from "react";
|
|
1298
|
+
import { Box as Box13, Text as Text13, useApp, useInput as useInput5 } from "ink";
|
|
1299
|
+
import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
868
1300
|
function AppShell({
|
|
869
1301
|
fetchJobs,
|
|
870
1302
|
fetchTranscript,
|
|
871
1303
|
fetchRecordings,
|
|
872
1304
|
fetchDashboardStats,
|
|
1305
|
+
fetchAccountStatus,
|
|
873
1306
|
recordingAudio,
|
|
1307
|
+
listDownloadedRecordingIds,
|
|
1308
|
+
startLiveRecord,
|
|
874
1309
|
initialView = "overview",
|
|
875
1310
|
openUrl: openUrl2,
|
|
876
1311
|
copyText: copyText2,
|
|
@@ -880,27 +1315,76 @@ function AppShell({
|
|
|
880
1315
|
}) {
|
|
881
1316
|
const { exit } = useApp();
|
|
882
1317
|
const size = useTerminalSize();
|
|
883
|
-
const [jobs, setJobs] =
|
|
884
|
-
const [recordings, setRecordings] =
|
|
885
|
-
const [recordingsNextCursor, setRecordingsNextCursor] =
|
|
886
|
-
const [recordingsTotalCount, setRecordingsTotalCount] =
|
|
887
|
-
const [stats, setStats] =
|
|
888
|
-
const [
|
|
889
|
-
const [
|
|
890
|
-
const [
|
|
891
|
-
const [
|
|
892
|
-
const [
|
|
893
|
-
const [
|
|
894
|
-
const [
|
|
895
|
-
const [
|
|
896
|
-
const [
|
|
1318
|
+
const [jobs, setJobs] = useState5([]);
|
|
1319
|
+
const [recordings, setRecordings] = useState5([]);
|
|
1320
|
+
const [recordingsNextCursor, setRecordingsNextCursor] = useState5(null);
|
|
1321
|
+
const [recordingsTotalCount, setRecordingsTotalCount] = useState5(void 0);
|
|
1322
|
+
const [stats, setStats] = useState5(void 0);
|
|
1323
|
+
const [accountStatus, setAccountStatus] = useState5("loading");
|
|
1324
|
+
const [origin, setOrigin] = useState5("");
|
|
1325
|
+
const [stack, setStack] = useState5([{ kind: initialView }]);
|
|
1326
|
+
const [selected, setSelected] = useState5(0);
|
|
1327
|
+
const [spinnerFrame, setSpinnerFrame] = useState5(0);
|
|
1328
|
+
const [loadingMoreRecordings, setLoadingMoreRecordings] = useState5(false);
|
|
1329
|
+
const [loadError, setLoadError] = useState5(void 0);
|
|
1330
|
+
const [notice, setNotice] = useState5(void 0);
|
|
1331
|
+
const [summaryCache, setSummaryCache] = useState5(() => /* @__PURE__ */ new Map());
|
|
1332
|
+
const [transcriptCache, setTranscriptCache] = useState5(
|
|
897
1333
|
() => /* @__PURE__ */ new Map()
|
|
898
1334
|
);
|
|
899
|
-
const [audioCache, setAudioCache] =
|
|
1335
|
+
const [audioCache, setAudioCache] = useState5(() => /* @__PURE__ */ new Map());
|
|
1336
|
+
const [downloadedIds, setDownloadedIds] = useState5(() => /* @__PURE__ */ new Set());
|
|
1337
|
+
const [liveRecord, setLiveRecord] = useState5(void 0);
|
|
1338
|
+
const refreshDownloadedIds = useCallback(async () => {
|
|
1339
|
+
if (!listDownloadedRecordingIds) return;
|
|
1340
|
+
try {
|
|
1341
|
+
setDownloadedIds(await listDownloadedRecordingIds());
|
|
1342
|
+
} catch {
|
|
1343
|
+
}
|
|
1344
|
+
}, [listDownloadedRecordingIds]);
|
|
1345
|
+
useEffect2(() => {
|
|
1346
|
+
void refreshDownloadedIds();
|
|
1347
|
+
}, [refreshDownloadedIds]);
|
|
900
1348
|
const screen = stack[stack.length - 1];
|
|
1349
|
+
const stopLiveRecord = useCallback(async () => {
|
|
1350
|
+
const current = liveRecord;
|
|
1351
|
+
if (current?.kind === "live") {
|
|
1352
|
+
setLiveRecord({ kind: "starting" });
|
|
1353
|
+
try {
|
|
1354
|
+
await current.session.stop();
|
|
1355
|
+
} catch (error51) {
|
|
1356
|
+
setNotice(error51 instanceof Error ? error51.message : String(error51));
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
setLiveRecord(void 0);
|
|
1360
|
+
setStack([{ kind: "overview" }]);
|
|
1361
|
+
}, [liveRecord]);
|
|
1362
|
+
useEffect2(() => {
|
|
1363
|
+
if (screen.kind !== "record") return;
|
|
1364
|
+
if (!startLiveRecord) {
|
|
1365
|
+
setLiveRecord({ kind: "error", message: "Live recording is not available" });
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
let cancelled = false;
|
|
1369
|
+
setLiveRecord({ kind: "starting" });
|
|
1370
|
+
startLiveRecord().then((session) => {
|
|
1371
|
+
if (cancelled) {
|
|
1372
|
+
void session.stop();
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
setLiveRecord({ kind: "live", session });
|
|
1376
|
+
}).catch((error51) => {
|
|
1377
|
+
if (!cancelled) {
|
|
1378
|
+
setLiveRecord({ kind: "error", message: error51 instanceof Error ? error51.message : String(error51) });
|
|
1379
|
+
}
|
|
1380
|
+
});
|
|
1381
|
+
return () => {
|
|
1382
|
+
cancelled = true;
|
|
1383
|
+
};
|
|
1384
|
+
}, [screen.kind, startLiveRecord]);
|
|
901
1385
|
const selectedRecording = screen.kind === "overview" ? recordings[selected] : void 0;
|
|
902
1386
|
const peekTranscriptId = selectedRecording?.activeTranscriptId ?? void 0;
|
|
903
|
-
|
|
1387
|
+
useEffect2(() => {
|
|
904
1388
|
if (!peekTranscriptId || summaryCache.has(peekTranscriptId)) return;
|
|
905
1389
|
let cancelled = false;
|
|
906
1390
|
const timer = setTimeout(() => {
|
|
@@ -918,10 +1402,11 @@ function AppShell({
|
|
|
918
1402
|
}, [peekTranscriptId, fetchTranscript]);
|
|
919
1403
|
const peekSummary = peekTranscriptId ? summaryCache.get(peekTranscriptId) : void 0;
|
|
920
1404
|
const refresh = useCallback(async ({ resetRecordings = false } = {}) => {
|
|
921
|
-
const [jobsR, recR, statsR] = await Promise.allSettled([
|
|
1405
|
+
const [jobsR, recR, statsR, accountR] = await Promise.allSettled([
|
|
922
1406
|
fetchJobs(),
|
|
923
1407
|
resetRecordings && fetchRecordings ? fetchRecordings({ limit: RECORDINGS_PAGE_SIZE }) : Promise.resolve(void 0),
|
|
924
|
-
fetchDashboardStats ? fetchDashboardStats() : Promise.resolve(void 0)
|
|
1408
|
+
fetchDashboardStats ? fetchDashboardStats() : Promise.resolve(void 0),
|
|
1409
|
+
fetchAccountStatus ? fetchAccountStatus() : Promise.resolve(void 0)
|
|
925
1410
|
]);
|
|
926
1411
|
if (jobsR.status === "fulfilled") {
|
|
927
1412
|
setJobs(jobsR.value.items);
|
|
@@ -936,7 +1421,12 @@ function AppShell({
|
|
|
936
1421
|
setRecordingsTotalCount(recR.value.totalCount);
|
|
937
1422
|
}
|
|
938
1423
|
if (statsR.status === "fulfilled" && statsR.value) setStats(statsR.value);
|
|
939
|
-
|
|
1424
|
+
if (accountR.status === "fulfilled") {
|
|
1425
|
+
setAccountStatus(accountR.value);
|
|
1426
|
+
} else {
|
|
1427
|
+
setAccountStatus("error");
|
|
1428
|
+
}
|
|
1429
|
+
}, [fetchJobs, fetchRecordings, fetchDashboardStats, fetchAccountStatus]);
|
|
940
1430
|
const loadMoreRecordings = useCallback(async () => {
|
|
941
1431
|
if (!fetchRecordings || !recordingsNextCursor || loadingMoreRecordings) return;
|
|
942
1432
|
setLoadingMoreRecordings(true);
|
|
@@ -962,13 +1452,13 @@ function AppShell({
|
|
|
962
1452
|
setLoadingMoreRecordings(false);
|
|
963
1453
|
}
|
|
964
1454
|
}, [fetchRecordings, loadingMoreRecordings, recordingsNextCursor]);
|
|
965
|
-
|
|
1455
|
+
useEffect2(() => {
|
|
966
1456
|
void refresh({ resetRecordings: true });
|
|
967
1457
|
const id = setInterval(() => void refresh(), pollMs);
|
|
968
1458
|
return () => clearInterval(id);
|
|
969
1459
|
}, [refresh, pollMs]);
|
|
970
1460
|
const hasRunning = jobs.some((item) => item.status === "running");
|
|
971
|
-
|
|
1461
|
+
useEffect2(() => {
|
|
972
1462
|
if (!hasRunning) return;
|
|
973
1463
|
const id = setInterval(() => setSpinnerFrame((f) => f + 1), spinnerMs);
|
|
974
1464
|
return () => clearInterval(id);
|
|
@@ -981,12 +1471,12 @@ function AppShell({
|
|
|
981
1471
|
jobStatusByRecording.set(job.recordingId, job.status);
|
|
982
1472
|
}
|
|
983
1473
|
}
|
|
984
|
-
const listLength = screen.kind === "jobs" ? jobs.length : recordings.length;
|
|
985
|
-
|
|
1474
|
+
const listLength = screen.kind === "jobs" ? jobs.length : screen.kind === "overview" ? recordings.length : 0;
|
|
1475
|
+
useEffect2(() => {
|
|
986
1476
|
setSelected((i) => Math.max(0, Math.min(i, Math.max(0, listLength - 1))));
|
|
987
1477
|
}, [listLength]);
|
|
988
1478
|
const visibleRecordingRows = Math.max(3, size.rows - 6);
|
|
989
|
-
|
|
1479
|
+
useEffect2(() => {
|
|
990
1480
|
if (screen.kind !== "overview" || !recordingsNextCursor) return;
|
|
991
1481
|
const nearLoadedEnd = recordings.length - selected <= RECORDINGS_PREFETCH_REMAINING;
|
|
992
1482
|
const underfilledViewport = recordings.length < visibleRecordingRows;
|
|
@@ -1019,7 +1509,7 @@ function AppShell({
|
|
|
1019
1509
|
[fetchTranscript]
|
|
1020
1510
|
);
|
|
1021
1511
|
const detailTranscriptId = screen.kind === "recordingDetail" ? recordings.find((r) => r.recordingId === screen.recordingId)?.activeTranscriptId : void 0;
|
|
1022
|
-
|
|
1512
|
+
useEffect2(() => {
|
|
1023
1513
|
if (!detailTranscriptId || transcriptCache.has(detailTranscriptId)) return;
|
|
1024
1514
|
let cancelled = false;
|
|
1025
1515
|
setTranscriptCache((m) => new Map(m).set(detailTranscriptId, "loading"));
|
|
@@ -1050,6 +1540,7 @@ function AppShell({
|
|
|
1050
1540
|
await recordingAudio.revealInFinder(localPath);
|
|
1051
1541
|
}
|
|
1052
1542
|
setAudio(recordingId, { status: "ready", localPath });
|
|
1543
|
+
void refreshDownloadedIds();
|
|
1053
1544
|
} catch (error51) {
|
|
1054
1545
|
setAudio(recordingId, {
|
|
1055
1546
|
status: "error",
|
|
@@ -1057,7 +1548,7 @@ function AppShell({
|
|
|
1057
1548
|
});
|
|
1058
1549
|
}
|
|
1059
1550
|
},
|
|
1060
|
-
[recordingAudio]
|
|
1551
|
+
[recordingAudio, refreshDownloadedIds]
|
|
1061
1552
|
);
|
|
1062
1553
|
const goTab = (tab2) => {
|
|
1063
1554
|
setStack([{ kind: tab2 }]);
|
|
@@ -1065,12 +1556,18 @@ function AppShell({
|
|
|
1065
1556
|
setNotice(void 0);
|
|
1066
1557
|
};
|
|
1067
1558
|
const back = () => setStack((st) => st.length > 1 ? st.slice(0, -1) : st);
|
|
1068
|
-
|
|
1559
|
+
useInput5((input, key) => {
|
|
1069
1560
|
setNotice(void 0);
|
|
1561
|
+
if (screen.kind === "record") {
|
|
1562
|
+
if (input === "q" || key.escape || key.leftArrow) void stopLiveRecord();
|
|
1563
|
+
return;
|
|
1564
|
+
}
|
|
1070
1565
|
if (input === "q") return exit();
|
|
1071
1566
|
if (key.escape || key.leftArrow) return back();
|
|
1072
1567
|
if (input === "1") return goTab("overview");
|
|
1073
1568
|
if (input === "2") return goTab("jobs");
|
|
1569
|
+
if (input === "3") return goTab("account");
|
|
1570
|
+
if (input === "4") return goTab("record");
|
|
1074
1571
|
if (input === "r") return void refresh({ resetRecordings: true });
|
|
1075
1572
|
if (screen.kind === "overview") {
|
|
1076
1573
|
if (key.upArrow || input === "k") setSelected((i) => Math.max(0, i - 1));
|
|
@@ -1117,18 +1614,18 @@ function AppShell({
|
|
|
1117
1614
|
}
|
|
1118
1615
|
});
|
|
1119
1616
|
if (screen.kind === "transcript") {
|
|
1120
|
-
return /* @__PURE__ */
|
|
1617
|
+
return /* @__PURE__ */ jsx15(TranscriptView, { loading: screen.loading, data: screen.data, error: screen.error });
|
|
1121
1618
|
}
|
|
1122
1619
|
if (screen.kind === "jobDetail") {
|
|
1123
1620
|
const job = jobs.find((j) => j.jobId === screen.jobId);
|
|
1124
|
-
if (!job) return /* @__PURE__ */
|
|
1125
|
-
return /* @__PURE__ */
|
|
1621
|
+
if (!job) return /* @__PURE__ */ jsx15(Missing, { label: "Job" });
|
|
1622
|
+
return /* @__PURE__ */ jsx15(Detail, { notice, children: /* @__PURE__ */ jsx15(JobDetailView, { item: job, origin, spinnerFrame, nowMs: now() }) });
|
|
1126
1623
|
}
|
|
1127
1624
|
if (screen.kind === "recordingDetail") {
|
|
1128
1625
|
const rec = recordings.find((r) => r.recordingId === screen.recordingId);
|
|
1129
|
-
if (!rec) return /* @__PURE__ */
|
|
1626
|
+
if (!rec) return /* @__PURE__ */ jsx15(Missing, { label: "Recording" });
|
|
1130
1627
|
const detailTranscript = rec.activeTranscriptId ? transcriptCache.get(rec.activeTranscriptId) : void 0;
|
|
1131
|
-
return /* @__PURE__ */
|
|
1628
|
+
return /* @__PURE__ */ jsx15(Detail, { notice, children: /* @__PURE__ */ jsx15(
|
|
1132
1629
|
RecordingDetailView,
|
|
1133
1630
|
{
|
|
1134
1631
|
item: rec,
|
|
@@ -1138,7 +1635,20 @@ function AppShell({
|
|
|
1138
1635
|
}
|
|
1139
1636
|
) });
|
|
1140
1637
|
}
|
|
1141
|
-
|
|
1638
|
+
if (screen.kind === "record") {
|
|
1639
|
+
if (liveRecord?.kind === "live") {
|
|
1640
|
+
return /* @__PURE__ */ jsx15(LiveCaptionsScreen, { source: liveRecord.session.source, now });
|
|
1641
|
+
}
|
|
1642
|
+
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
|
|
1643
|
+
/* @__PURE__ */ jsx15(Header, { active: "record" }),
|
|
1644
|
+
/* @__PURE__ */ jsx15(Box13, { flexGrow: 1, flexDirection: "column", paddingX: 1, paddingTop: 1, children: liveRecord?.kind === "error" ? /* @__PURE__ */ jsxs12(Fragment4, { children: [
|
|
1645
|
+
/* @__PURE__ */ jsx15(Text13, { color: "red", children: "Couldn't start live recording" }),
|
|
1646
|
+
/* @__PURE__ */ jsx15(Text13, { dimColor: true, children: liveRecord.message })
|
|
1647
|
+
] }) : /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "Starting live recording\u2026" }) }),
|
|
1648
|
+
/* @__PURE__ */ jsx15(Footer, { keys: "q / esc / \u2190 back" })
|
|
1649
|
+
] });
|
|
1650
|
+
}
|
|
1651
|
+
const tab = screen.kind === "jobs" ? "jobs" : screen.kind === "account" ? "account" : "overview";
|
|
1142
1652
|
let body;
|
|
1143
1653
|
let position = "";
|
|
1144
1654
|
if (screen.kind === "overview") {
|
|
@@ -1153,7 +1663,7 @@ function AppShell({
|
|
|
1153
1663
|
const showPeek = size.columns >= 100;
|
|
1154
1664
|
const peekWidth = showPeek ? 34 : 0;
|
|
1155
1665
|
const listColumns = showPeek ? Math.max(30, size.columns - peekWidth - 3) : size.columns;
|
|
1156
|
-
body = /* @__PURE__ */
|
|
1666
|
+
body = /* @__PURE__ */ jsx15(
|
|
1157
1667
|
OverviewView,
|
|
1158
1668
|
{
|
|
1159
1669
|
recordings: recordings.slice(win.start, win.end),
|
|
@@ -1163,6 +1673,7 @@ function AppShell({
|
|
|
1163
1673
|
nowMs: now(),
|
|
1164
1674
|
columns: listColumns,
|
|
1165
1675
|
jobStatusByRecording,
|
|
1676
|
+
downloadedRecordingIds: downloadedIds,
|
|
1166
1677
|
spinnerFrame,
|
|
1167
1678
|
peekItem: recordings[selected],
|
|
1168
1679
|
peekSummary,
|
|
@@ -1170,10 +1681,13 @@ function AppShell({
|
|
|
1170
1681
|
peekWidth
|
|
1171
1682
|
}
|
|
1172
1683
|
);
|
|
1684
|
+
} else if (screen.kind === "account") {
|
|
1685
|
+
position = "";
|
|
1686
|
+
body = /* @__PURE__ */ jsx15(AccountView, { status: accountStatus });
|
|
1173
1687
|
} else {
|
|
1174
1688
|
const win = listWindow(selected, jobs.length, Math.max(3, size.rows - 4));
|
|
1175
1689
|
position = jobs.length ? `${selected + 1} / ${jobs.length}` : "0";
|
|
1176
|
-
body = /* @__PURE__ */
|
|
1690
|
+
body = /* @__PURE__ */ jsx15(
|
|
1177
1691
|
JobsView,
|
|
1178
1692
|
{
|
|
1179
1693
|
items: jobs.slice(win.start, win.end),
|
|
@@ -1182,47 +1696,49 @@ function AppShell({
|
|
|
1182
1696
|
}
|
|
1183
1697
|
);
|
|
1184
1698
|
}
|
|
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__ */
|
|
1187
|
-
/* @__PURE__ */
|
|
1188
|
-
/* @__PURE__ */
|
|
1699
|
+
const footerKeys = screen.kind === "jobs" ? `${position} \xB7 \u2191\u2193 select \xB7 \u23CE job \xB7 t transcript \xB7 1 overview \xB7 3 account \xB7 4 record \xB7 r refresh \xB7 q quit` : screen.kind === "account" ? "3 account \xB7 1 overview \xB7 2 jobs \xB7 4 record \xB7 r refresh \xB7 q quit" : `${position} \xB7 \u2191\u2193 scroll \xB7 \u23CE open \xB7 t transcript \xB7 2 jobs \xB7 3 account \xB7 4 record \xB7 r refresh \xB7 q quit`;
|
|
1700
|
+
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
|
|
1701
|
+
/* @__PURE__ */ jsx15(Header, { active: tab }),
|
|
1702
|
+
/* @__PURE__ */ jsxs12(Box13, { flexGrow: 1, flexDirection: "column", children: [
|
|
1189
1703
|
body,
|
|
1190
|
-
loadError && jobs.length === 0 && recordings.length === 0 ? /* @__PURE__ */
|
|
1704
|
+
loadError && jobs.length === 0 && recordings.length === 0 ? /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text13, { color: "red", children: [
|
|
1191
1705
|
"! ",
|
|
1192
1706
|
loadError
|
|
1193
1707
|
] }) }) : null
|
|
1194
1708
|
] }),
|
|
1195
|
-
/* @__PURE__ */
|
|
1709
|
+
/* @__PURE__ */ jsx15(Footer, { keys: footerKeys })
|
|
1196
1710
|
] });
|
|
1197
1711
|
}
|
|
1198
1712
|
function Detail({
|
|
1199
1713
|
notice,
|
|
1200
1714
|
children
|
|
1201
1715
|
}) {
|
|
1202
|
-
return /* @__PURE__ */
|
|
1716
|
+
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", children: [
|
|
1203
1717
|
children,
|
|
1204
|
-
notice ? /* @__PURE__ */
|
|
1718
|
+
notice ? /* @__PURE__ */ jsx15(Box13, { paddingX: 1, children: /* @__PURE__ */ jsx15(Text13, { color: "green", children: notice }) }) : null
|
|
1205
1719
|
] });
|
|
1206
1720
|
}
|
|
1207
1721
|
function Missing({ label }) {
|
|
1208
|
-
return /* @__PURE__ */
|
|
1209
|
-
/* @__PURE__ */
|
|
1722
|
+
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingX: 1, children: [
|
|
1723
|
+
/* @__PURE__ */ jsxs12(Text13, { dimColor: true, children: [
|
|
1210
1724
|
label,
|
|
1211
1725
|
" no longer in the list."
|
|
1212
1726
|
] }),
|
|
1213
|
-
/* @__PURE__ */
|
|
1727
|
+
/* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "esc back \xB7 q quit" })
|
|
1214
1728
|
] });
|
|
1215
1729
|
}
|
|
1216
1730
|
var RECORDINGS_PAGE_SIZE, RECORDINGS_PREFETCH_REMAINING;
|
|
1217
1731
|
var init_AppShell = __esm({
|
|
1218
1732
|
"src/tui/AppShell.tsx"() {
|
|
1219
1733
|
"use strict";
|
|
1734
|
+
init_AccountView();
|
|
1220
1735
|
init_chrome();
|
|
1221
1736
|
init_JobsView();
|
|
1222
1737
|
init_OverviewView();
|
|
1223
1738
|
init_JobDetailView();
|
|
1224
1739
|
init_RecordingDetailView();
|
|
1225
1740
|
init_TranscriptView();
|
|
1741
|
+
init_LiveCaptionsScreen();
|
|
1226
1742
|
init_format();
|
|
1227
1743
|
init_terminal();
|
|
1228
1744
|
RECORDINGS_PAGE_SIZE = 50;
|
|
@@ -1241,33 +1757,36 @@ __export(tui_exports, {
|
|
|
1241
1757
|
runDashboard: () => runDashboard,
|
|
1242
1758
|
useTerminalSize: () => useTerminalSize
|
|
1243
1759
|
});
|
|
1244
|
-
import
|
|
1245
|
-
import { render } from "ink";
|
|
1246
|
-
import { spawn as
|
|
1760
|
+
import React7 from "react";
|
|
1761
|
+
import { render as render2 } from "ink";
|
|
1762
|
+
import { spawn as spawn3 } from "child_process";
|
|
1247
1763
|
function openUrl(url2) {
|
|
1248
1764
|
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
1249
1765
|
try {
|
|
1250
|
-
|
|
1766
|
+
spawn3(cmd, [url2], { stdio: "ignore", detached: true }).unref();
|
|
1251
1767
|
} catch {
|
|
1252
1768
|
}
|
|
1253
1769
|
}
|
|
1254
1770
|
function copyText(text) {
|
|
1255
1771
|
if (process.platform !== "darwin") return;
|
|
1256
1772
|
try {
|
|
1257
|
-
const child =
|
|
1773
|
+
const child = spawn3("pbcopy", { stdio: ["pipe", "ignore", "ignore"] });
|
|
1258
1774
|
child.stdin.end(text);
|
|
1259
1775
|
} catch {
|
|
1260
1776
|
}
|
|
1261
1777
|
}
|
|
1262
1778
|
async function runDashboard(deps) {
|
|
1263
|
-
const renderApp = deps.renderApp ??
|
|
1779
|
+
const renderApp = deps.renderApp ?? render2;
|
|
1264
1780
|
const app = renderApp(
|
|
1265
|
-
|
|
1781
|
+
React7.createElement(AppShell, {
|
|
1266
1782
|
fetchJobs: deps.fetchJobs,
|
|
1267
1783
|
fetchTranscript: deps.fetchTranscript,
|
|
1268
1784
|
fetchRecordings: deps.fetchRecordings,
|
|
1269
1785
|
fetchDashboardStats: deps.fetchDashboardStats,
|
|
1786
|
+
fetchAccountStatus: deps.fetchAccountStatus,
|
|
1270
1787
|
recordingAudio: deps.recordingAudio,
|
|
1788
|
+
listDownloadedRecordingIds: deps.listDownloadedRecordingIds,
|
|
1789
|
+
startLiveRecord: deps.startLiveRecord,
|
|
1271
1790
|
initialView: deps.initialView ?? "overview",
|
|
1272
1791
|
openUrl,
|
|
1273
1792
|
copyText
|
|
@@ -1295,7 +1814,7 @@ var init_tui = __esm({
|
|
|
1295
1814
|
|
|
1296
1815
|
// src/cli.ts
|
|
1297
1816
|
import { Command, CommanderError, InvalidArgumentError } from "commander/esm.mjs";
|
|
1298
|
-
import
|
|
1817
|
+
import os5 from "os";
|
|
1299
1818
|
|
|
1300
1819
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
1301
1820
|
var external_exports = {};
|
|
@@ -2063,10 +2582,10 @@ function mergeDefs(...defs) {
|
|
|
2063
2582
|
function cloneDef(schema) {
|
|
2064
2583
|
return mergeDefs(schema._zod.def);
|
|
2065
2584
|
}
|
|
2066
|
-
function getElementAtPath(obj,
|
|
2067
|
-
if (!
|
|
2585
|
+
function getElementAtPath(obj, path6) {
|
|
2586
|
+
if (!path6)
|
|
2068
2587
|
return obj;
|
|
2069
|
-
return
|
|
2588
|
+
return path6.reduce((acc, key) => acc?.[key], obj);
|
|
2070
2589
|
}
|
|
2071
2590
|
function promiseAllObject(promisesObj) {
|
|
2072
2591
|
const keys = Object.keys(promisesObj);
|
|
@@ -2475,11 +2994,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
2475
2994
|
}
|
|
2476
2995
|
return false;
|
|
2477
2996
|
}
|
|
2478
|
-
function prefixIssues(
|
|
2997
|
+
function prefixIssues(path6, issues) {
|
|
2479
2998
|
return issues.map((iss) => {
|
|
2480
2999
|
var _a3;
|
|
2481
3000
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
2482
|
-
iss.path.unshift(
|
|
3001
|
+
iss.path.unshift(path6);
|
|
2483
3002
|
return iss;
|
|
2484
3003
|
});
|
|
2485
3004
|
}
|
|
@@ -2626,16 +3145,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
|
|
|
2626
3145
|
}
|
|
2627
3146
|
function formatError(error51, mapper = (issue2) => issue2.message) {
|
|
2628
3147
|
const fieldErrors = { _errors: [] };
|
|
2629
|
-
const processError = (error52,
|
|
3148
|
+
const processError = (error52, path6 = []) => {
|
|
2630
3149
|
for (const issue2 of error52.issues) {
|
|
2631
3150
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
2632
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
3151
|
+
issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
|
|
2633
3152
|
} else if (issue2.code === "invalid_key") {
|
|
2634
|
-
processError({ issues: issue2.issues }, [...
|
|
3153
|
+
processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
|
|
2635
3154
|
} else if (issue2.code === "invalid_element") {
|
|
2636
|
-
processError({ issues: issue2.issues }, [...
|
|
3155
|
+
processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
|
|
2637
3156
|
} else {
|
|
2638
|
-
const fullpath = [...
|
|
3157
|
+
const fullpath = [...path6, ...issue2.path];
|
|
2639
3158
|
if (fullpath.length === 0) {
|
|
2640
3159
|
fieldErrors._errors.push(mapper(issue2));
|
|
2641
3160
|
} else {
|
|
@@ -2662,17 +3181,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
|
|
|
2662
3181
|
}
|
|
2663
3182
|
function treeifyError(error51, mapper = (issue2) => issue2.message) {
|
|
2664
3183
|
const result = { errors: [] };
|
|
2665
|
-
const processError = (error52,
|
|
3184
|
+
const processError = (error52, path6 = []) => {
|
|
2666
3185
|
var _a3, _b;
|
|
2667
3186
|
for (const issue2 of error52.issues) {
|
|
2668
3187
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
2669
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
3188
|
+
issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
|
|
2670
3189
|
} else if (issue2.code === "invalid_key") {
|
|
2671
|
-
processError({ issues: issue2.issues }, [...
|
|
3190
|
+
processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
|
|
2672
3191
|
} else if (issue2.code === "invalid_element") {
|
|
2673
|
-
processError({ issues: issue2.issues }, [...
|
|
3192
|
+
processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
|
|
2674
3193
|
} else {
|
|
2675
|
-
const fullpath = [...
|
|
3194
|
+
const fullpath = [...path6, ...issue2.path];
|
|
2676
3195
|
if (fullpath.length === 0) {
|
|
2677
3196
|
result.errors.push(mapper(issue2));
|
|
2678
3197
|
continue;
|
|
@@ -2704,8 +3223,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
|
|
|
2704
3223
|
}
|
|
2705
3224
|
function toDotPath(_path) {
|
|
2706
3225
|
const segs = [];
|
|
2707
|
-
const
|
|
2708
|
-
for (const seg of
|
|
3226
|
+
const path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
3227
|
+
for (const seg of path6) {
|
|
2709
3228
|
if (typeof seg === "number")
|
|
2710
3229
|
segs.push(`[${seg}]`);
|
|
2711
3230
|
else if (typeof seg === "symbol")
|
|
@@ -15397,13 +15916,13 @@ function resolveRef(ref, ctx) {
|
|
|
15397
15916
|
if (!ref.startsWith("#")) {
|
|
15398
15917
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
15399
15918
|
}
|
|
15400
|
-
const
|
|
15401
|
-
if (
|
|
15919
|
+
const path6 = ref.slice(1).split("/").filter(Boolean);
|
|
15920
|
+
if (path6.length === 0) {
|
|
15402
15921
|
return ctx.rootSchema;
|
|
15403
15922
|
}
|
|
15404
15923
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
15405
|
-
if (
|
|
15406
|
-
const key =
|
|
15924
|
+
if (path6[0] === defsKey) {
|
|
15925
|
+
const key = path6[1];
|
|
15407
15926
|
if (!key || !ctx.defs[key]) {
|
|
15408
15927
|
throw new Error(`Reference not found: ${ref}`);
|
|
15409
15928
|
}
|
|
@@ -15882,20 +16401,262 @@ var authImportDataSchema = external_exports.object({
|
|
|
15882
16401
|
origin: external_exports.string(),
|
|
15883
16402
|
source: external_exports.literal("macos-keychain")
|
|
15884
16403
|
});
|
|
15885
|
-
var
|
|
15886
|
-
|
|
15887
|
-
|
|
15888
|
-
|
|
15889
|
-
|
|
15890
|
-
|
|
15891
|
-
|
|
16404
|
+
var planTierSchema = external_exports.enum(["free", "starter", "pro", "business", "unlimited"]);
|
|
16405
|
+
var billingStatusDataSchema = external_exports.object({
|
|
16406
|
+
origin: external_exports.string(),
|
|
16407
|
+
tier: planTierSchema,
|
|
16408
|
+
periodStart: external_exports.number().int(),
|
|
16409
|
+
periodEnd: external_exports.number().int(),
|
|
16410
|
+
storageBytes: external_exports.number().int().nonnegative(),
|
|
16411
|
+
storageCapBytes: external_exports.number().int().nonnegative().nullable(),
|
|
16412
|
+
minutesUsed: external_exports.number().nonnegative(),
|
|
16413
|
+
batchMinutesUsed: external_exports.number().nonnegative(),
|
|
16414
|
+
realtimeMinutesUsed: external_exports.number().nonnegative(),
|
|
16415
|
+
minutesCap: external_exports.number().nonnegative().nullable(),
|
|
16416
|
+
isOverStorage: external_exports.boolean(),
|
|
16417
|
+
isOverMinutes: external_exports.boolean()
|
|
15892
16418
|
});
|
|
15893
|
-
var
|
|
15894
|
-
|
|
15895
|
-
|
|
16419
|
+
var accountStatusDataSchema = external_exports.object({
|
|
16420
|
+
origin: external_exports.string(),
|
|
16421
|
+
loggedIn: external_exports.boolean(),
|
|
16422
|
+
email: external_exports.string().optional(),
|
|
16423
|
+
userId: external_exports.string().optional(),
|
|
16424
|
+
localStore: external_exports.object({
|
|
16425
|
+
path: external_exports.string(),
|
|
16426
|
+
accountScopedArtifacts: external_exports.number().int().nonnegative(),
|
|
16427
|
+
unattributedArtifacts: external_exports.number().int().nonnegative()
|
|
16428
|
+
}),
|
|
16429
|
+
billing: billingStatusDataSchema.optional()
|
|
15896
16430
|
});
|
|
15897
|
-
var
|
|
15898
|
-
|
|
16431
|
+
var SIDECAR_PROTOCOL_VERSION = 1;
|
|
16432
|
+
var sidecarJsonRpcIdSchema = external_exports.union([external_exports.string(), external_exports.number().int()]);
|
|
16433
|
+
var sidecarCapabilitySchema = external_exports.enum([
|
|
16434
|
+
"recording.capture",
|
|
16435
|
+
"recording.upload",
|
|
16436
|
+
"live_captions.stream",
|
|
16437
|
+
"local_artifacts.index"
|
|
16438
|
+
]);
|
|
16439
|
+
var sidecarAccountSchema = external_exports.object({
|
|
16440
|
+
backendOrigin: external_exports.string(),
|
|
16441
|
+
userId: external_exports.string(),
|
|
16442
|
+
email: external_exports.string().optional()
|
|
16443
|
+
});
|
|
16444
|
+
var sidecarClientInfoSchema = external_exports.object({
|
|
16445
|
+
name: external_exports.string(),
|
|
16446
|
+
version: external_exports.string()
|
|
16447
|
+
});
|
|
16448
|
+
var sidecarInfoSchema = external_exports.object({
|
|
16449
|
+
name: external_exports.string(),
|
|
16450
|
+
version: external_exports.string()
|
|
16451
|
+
});
|
|
16452
|
+
var sidecarRecordingOptionsSchema = external_exports.object({
|
|
16453
|
+
includeSystemAudio: external_exports.boolean().default(true),
|
|
16454
|
+
includeMicrophone: external_exports.boolean().default(true),
|
|
16455
|
+
liveCaptions: external_exports.boolean().default(false),
|
|
16456
|
+
translationLanguage: external_exports.string().optional(),
|
|
16457
|
+
transcriptionLanguage: external_exports.string().optional(),
|
|
16458
|
+
title: external_exports.string().optional()
|
|
16459
|
+
});
|
|
16460
|
+
var sidecarRecordingStateSchema = external_exports.enum([
|
|
16461
|
+
"idle",
|
|
16462
|
+
"starting",
|
|
16463
|
+
"recording",
|
|
16464
|
+
"stopping",
|
|
16465
|
+
"finalizing",
|
|
16466
|
+
"uploading",
|
|
16467
|
+
"completed",
|
|
16468
|
+
"failed",
|
|
16469
|
+
"cancelled"
|
|
16470
|
+
]);
|
|
16471
|
+
var sidecarLocalArtifactKindSchema = external_exports.enum([
|
|
16472
|
+
"recording_session",
|
|
16473
|
+
"download",
|
|
16474
|
+
"live_caption_draft"
|
|
16475
|
+
]);
|
|
16476
|
+
var sidecarLocalArtifactSchema = external_exports.object({
|
|
16477
|
+
kind: sidecarLocalArtifactKindSchema,
|
|
16478
|
+
localPath: external_exports.string(),
|
|
16479
|
+
remoteId: external_exports.string().optional(),
|
|
16480
|
+
metadata: external_exports.unknown().optional()
|
|
16481
|
+
});
|
|
16482
|
+
var sidecarHandshakeParamsSchema = external_exports.object({
|
|
16483
|
+
protocolVersion: external_exports.literal(SIDECAR_PROTOCOL_VERSION),
|
|
16484
|
+
client: sidecarClientInfoSchema,
|
|
16485
|
+
account: sidecarAccountSchema.optional(),
|
|
16486
|
+
capabilities: external_exports.array(sidecarCapabilitySchema)
|
|
16487
|
+
});
|
|
16488
|
+
var sidecarHandshakeResultSchema = external_exports.object({
|
|
16489
|
+
protocolVersion: external_exports.literal(SIDECAR_PROTOCOL_VERSION),
|
|
16490
|
+
sidecar: sidecarInfoSchema,
|
|
16491
|
+
capabilities: external_exports.array(sidecarCapabilitySchema)
|
|
16492
|
+
});
|
|
16493
|
+
var sidecarRecordingStartParamsSchema = external_exports.object({
|
|
16494
|
+
account: sidecarAccountSchema,
|
|
16495
|
+
options: sidecarRecordingOptionsSchema
|
|
16496
|
+
});
|
|
16497
|
+
var sidecarRecordingStartResultSchema = external_exports.object({
|
|
16498
|
+
sessionId: external_exports.string(),
|
|
16499
|
+
state: sidecarRecordingStateSchema,
|
|
16500
|
+
localSessionRef: external_exports.string().optional()
|
|
16501
|
+
});
|
|
16502
|
+
var sidecarSessionParamsSchema = external_exports.object({
|
|
16503
|
+
sessionId: external_exports.string()
|
|
16504
|
+
});
|
|
16505
|
+
var sidecarRecordingStopResultSchema = external_exports.object({
|
|
16506
|
+
sessionId: external_exports.string(),
|
|
16507
|
+
state: sidecarRecordingStateSchema,
|
|
16508
|
+
recordingId: external_exports.string().optional(),
|
|
16509
|
+
localSessionRef: external_exports.string().optional(),
|
|
16510
|
+
artifacts: external_exports.array(sidecarLocalArtifactSchema).optional()
|
|
16511
|
+
});
|
|
16512
|
+
var sidecarRecordingStatusResultSchema = external_exports.object({
|
|
16513
|
+
sessionId: external_exports.string(),
|
|
16514
|
+
state: sidecarRecordingStateSchema,
|
|
16515
|
+
recordingId: external_exports.string().optional(),
|
|
16516
|
+
localSessionRef: external_exports.string().optional()
|
|
16517
|
+
});
|
|
16518
|
+
var sidecarRequestSchema = external_exports.discriminatedUnion("method", [
|
|
16519
|
+
external_exports.object({
|
|
16520
|
+
jsonrpc: external_exports.literal("2.0"),
|
|
16521
|
+
id: sidecarJsonRpcIdSchema,
|
|
16522
|
+
method: external_exports.literal("recappi.handshake"),
|
|
16523
|
+
params: sidecarHandshakeParamsSchema
|
|
16524
|
+
}),
|
|
16525
|
+
external_exports.object({
|
|
16526
|
+
jsonrpc: external_exports.literal("2.0"),
|
|
16527
|
+
id: sidecarJsonRpcIdSchema,
|
|
16528
|
+
method: external_exports.literal("recappi.recording.start"),
|
|
16529
|
+
params: sidecarRecordingStartParamsSchema
|
|
16530
|
+
}),
|
|
16531
|
+
external_exports.object({
|
|
16532
|
+
jsonrpc: external_exports.literal("2.0"),
|
|
16533
|
+
id: sidecarJsonRpcIdSchema,
|
|
16534
|
+
method: external_exports.literal("recappi.recording.stop"),
|
|
16535
|
+
params: sidecarSessionParamsSchema
|
|
16536
|
+
}),
|
|
16537
|
+
external_exports.object({
|
|
16538
|
+
jsonrpc: external_exports.literal("2.0"),
|
|
16539
|
+
id: sidecarJsonRpcIdSchema,
|
|
16540
|
+
method: external_exports.literal("recappi.recording.cancel"),
|
|
16541
|
+
params: sidecarSessionParamsSchema
|
|
16542
|
+
}),
|
|
16543
|
+
external_exports.object({
|
|
16544
|
+
jsonrpc: external_exports.literal("2.0"),
|
|
16545
|
+
id: sidecarJsonRpcIdSchema,
|
|
16546
|
+
method: external_exports.literal("recappi.recording.status"),
|
|
16547
|
+
params: sidecarSessionParamsSchema
|
|
16548
|
+
})
|
|
16549
|
+
]);
|
|
16550
|
+
var sidecarErrorSchema = external_exports.object({
|
|
16551
|
+
code: external_exports.number().int(),
|
|
16552
|
+
message: external_exports.string(),
|
|
16553
|
+
data: external_exports.unknown().optional()
|
|
16554
|
+
});
|
|
16555
|
+
var sidecarResponseSchema = external_exports.union([
|
|
16556
|
+
external_exports.object({
|
|
16557
|
+
jsonrpc: external_exports.literal("2.0"),
|
|
16558
|
+
id: sidecarJsonRpcIdSchema,
|
|
16559
|
+
result: external_exports.unknown()
|
|
16560
|
+
}),
|
|
16561
|
+
external_exports.object({
|
|
16562
|
+
jsonrpc: external_exports.literal("2.0"),
|
|
16563
|
+
id: sidecarJsonRpcIdSchema,
|
|
16564
|
+
error: sidecarErrorSchema
|
|
16565
|
+
})
|
|
16566
|
+
]);
|
|
16567
|
+
var sidecarEventSchema = external_exports.discriminatedUnion("type", [
|
|
16568
|
+
external_exports.object({
|
|
16569
|
+
type: external_exports.literal("ready"),
|
|
16570
|
+
protocolVersion: external_exports.literal(SIDECAR_PROTOCOL_VERSION),
|
|
16571
|
+
sidecar: sidecarInfoSchema
|
|
16572
|
+
}),
|
|
16573
|
+
external_exports.object({
|
|
16574
|
+
type: external_exports.literal("recording.state"),
|
|
16575
|
+
sessionId: external_exports.string(),
|
|
16576
|
+
state: sidecarRecordingStateSchema,
|
|
16577
|
+
recordingId: external_exports.string().optional(),
|
|
16578
|
+
localSessionRef: external_exports.string().optional(),
|
|
16579
|
+
message: external_exports.string().optional()
|
|
16580
|
+
}),
|
|
16581
|
+
external_exports.object({
|
|
16582
|
+
type: external_exports.literal("audio.level"),
|
|
16583
|
+
sessionId: external_exports.string(),
|
|
16584
|
+
input: external_exports.enum(["system", "microphone", "mixed"]),
|
|
16585
|
+
rmsDb: external_exports.number().optional(),
|
|
16586
|
+
peakDb: external_exports.number().optional(),
|
|
16587
|
+
at: external_exports.number().int().optional()
|
|
16588
|
+
}),
|
|
16589
|
+
external_exports.object({
|
|
16590
|
+
type: external_exports.literal("live_caption.delta"),
|
|
16591
|
+
sessionId: external_exports.string(),
|
|
16592
|
+
stream: external_exports.enum(["source", "translation"]),
|
|
16593
|
+
text: external_exports.string(),
|
|
16594
|
+
isFinal: external_exports.boolean().optional(),
|
|
16595
|
+
segmentId: external_exports.string().optional(),
|
|
16596
|
+
speaker: external_exports.string().optional(),
|
|
16597
|
+
language: external_exports.string().optional(),
|
|
16598
|
+
atMs: external_exports.number().int().nonnegative().optional(),
|
|
16599
|
+
startMs: external_exports.number().nonnegative().optional(),
|
|
16600
|
+
endMs: external_exports.number().nonnegative().optional()
|
|
16601
|
+
}),
|
|
16602
|
+
external_exports.object({
|
|
16603
|
+
type: external_exports.literal("local_artifact.upserted"),
|
|
16604
|
+
sessionId: external_exports.string().optional(),
|
|
16605
|
+
artifact: sidecarLocalArtifactSchema
|
|
16606
|
+
}),
|
|
16607
|
+
external_exports.object({
|
|
16608
|
+
type: external_exports.literal("error"),
|
|
16609
|
+
sessionId: external_exports.string().optional(),
|
|
16610
|
+
code: external_exports.string(),
|
|
16611
|
+
message: external_exports.string(),
|
|
16612
|
+
retryable: external_exports.boolean().optional()
|
|
16613
|
+
})
|
|
16614
|
+
]);
|
|
16615
|
+
var sidecarNotificationSchema = external_exports.object({
|
|
16616
|
+
jsonrpc: external_exports.literal("2.0"),
|
|
16617
|
+
method: external_exports.literal("recappi.event"),
|
|
16618
|
+
params: sidecarEventSchema
|
|
16619
|
+
});
|
|
16620
|
+
var sidecarMessageSchema = external_exports.union([
|
|
16621
|
+
sidecarRequestSchema,
|
|
16622
|
+
sidecarResponseSchema,
|
|
16623
|
+
sidecarNotificationSchema
|
|
16624
|
+
]);
|
|
16625
|
+
var recordCommandDataSchema = external_exports.object({
|
|
16626
|
+
origin: external_exports.string(),
|
|
16627
|
+
userId: external_exports.string(),
|
|
16628
|
+
live: external_exports.boolean(),
|
|
16629
|
+
sessionId: external_exports.string(),
|
|
16630
|
+
state: sidecarRecordingStateSchema,
|
|
16631
|
+
recordingId: external_exports.string().optional(),
|
|
16632
|
+
localSessionRef: external_exports.string().optional(),
|
|
16633
|
+
sidecar: sidecarInfoSchema.optional(),
|
|
16634
|
+
artifacts: external_exports.array(sidecarLocalArtifactSchema)
|
|
16635
|
+
});
|
|
16636
|
+
var audioCommandDataSchema = external_exports.object({
|
|
16637
|
+
origin: external_exports.string(),
|
|
16638
|
+
recordingId: external_exports.string(),
|
|
16639
|
+
localPath: external_exports.string(),
|
|
16640
|
+
action: external_exports.enum(["download", "open", "reveal"]),
|
|
16641
|
+
reused: external_exports.boolean(),
|
|
16642
|
+
artifactId: external_exports.number().int().positive().optional(),
|
|
16643
|
+
contentType: external_exports.string().optional(),
|
|
16644
|
+
contentLength: external_exports.number().int().nonnegative().optional()
|
|
16645
|
+
});
|
|
16646
|
+
var uploadSuccessSchema = external_exports.object({
|
|
16647
|
+
filePath: external_exports.string(),
|
|
16648
|
+
recordingId: external_exports.string(),
|
|
16649
|
+
jobId: external_exports.string().optional(),
|
|
16650
|
+
transcriptId: external_exports.string().optional(),
|
|
16651
|
+
status: external_exports.string(),
|
|
16652
|
+
origin: external_exports.string()
|
|
16653
|
+
});
|
|
16654
|
+
var uploadFailureSchema = external_exports.object({
|
|
16655
|
+
filePath: external_exports.string(),
|
|
16656
|
+
error: cliErrorDescriptorSchema
|
|
16657
|
+
});
|
|
16658
|
+
var uploadBatchDataSchema = external_exports.object({
|
|
16659
|
+
successes: external_exports.array(uploadSuccessSchema),
|
|
15899
16660
|
failures: external_exports.array(uploadFailureSchema),
|
|
15900
16661
|
totalCount: external_exports.number().int().nonnegative(),
|
|
15901
16662
|
attemptedCount: external_exports.number().int().nonnegative()
|
|
@@ -16488,8 +17249,8 @@ function isRecord(value) {
|
|
|
16488
17249
|
|
|
16489
17250
|
// src/api.ts
|
|
16490
17251
|
import { createWriteStream, promises as fs3 } from "fs";
|
|
16491
|
-
import
|
|
16492
|
-
import
|
|
17252
|
+
import os4 from "os";
|
|
17253
|
+
import path4 from "path";
|
|
16493
17254
|
import { Readable } from "stream";
|
|
16494
17255
|
import { pipeline } from "stream/promises";
|
|
16495
17256
|
|
|
@@ -16648,6 +17409,352 @@ async function readDurationMs(filePath, contentType) {
|
|
|
16648
17409
|
);
|
|
16649
17410
|
}
|
|
16650
17411
|
|
|
17412
|
+
// src/store.ts
|
|
17413
|
+
import { mkdirSync } from "fs";
|
|
17414
|
+
import os3 from "os";
|
|
17415
|
+
import path3 from "path";
|
|
17416
|
+
import { createRequire } from "module";
|
|
17417
|
+
var require2 = createRequire(import.meta.url);
|
|
17418
|
+
var Database = require2("better-sqlite3");
|
|
17419
|
+
var CLI_STORE_SCHEMA_VERSION = 2;
|
|
17420
|
+
function defaultStorePath(homeDir = os3.homedir(), env = process.env) {
|
|
17421
|
+
const explicit = env.RECAPPI_CLI_STORE_PATH?.trim();
|
|
17422
|
+
if (explicit) return explicit;
|
|
17423
|
+
const dataHome = env.XDG_DATA_HOME?.trim() || path3.join(homeDir, ".local", "share");
|
|
17424
|
+
return path3.join(dataHome, "recappi", "cli-state.sqlite");
|
|
17425
|
+
}
|
|
17426
|
+
function requireAccountPartition(input) {
|
|
17427
|
+
const account = parseAccountPartition(input, "strict");
|
|
17428
|
+
if (!account) {
|
|
17429
|
+
throw cliError(
|
|
17430
|
+
"usage.invalid_argument",
|
|
17431
|
+
"Account partition requires a backend origin and user id.",
|
|
17432
|
+
{
|
|
17433
|
+
hint: "Resolve Recappi auth first, then use the normalized origin and authenticated user id."
|
|
17434
|
+
}
|
|
17435
|
+
);
|
|
17436
|
+
}
|
|
17437
|
+
return account;
|
|
17438
|
+
}
|
|
17439
|
+
function openCliStore(opts = {}) {
|
|
17440
|
+
return new CliLocalStore(opts);
|
|
17441
|
+
}
|
|
17442
|
+
var CliLocalStore = class {
|
|
17443
|
+
db;
|
|
17444
|
+
now;
|
|
17445
|
+
constructor(opts = {}) {
|
|
17446
|
+
const dbPath = opts.dbPath ?? defaultStorePath(opts.homeDir, opts.env);
|
|
17447
|
+
if (!opts.readonly && dbPath !== ":memory:") {
|
|
17448
|
+
mkdirSync(path3.dirname(dbPath), { recursive: true, mode: 448 });
|
|
17449
|
+
}
|
|
17450
|
+
this.db = new Database(dbPath, opts.readonly === true ? { readonly: true } : void 0);
|
|
17451
|
+
this.now = opts.now ?? Date.now;
|
|
17452
|
+
if (!opts.readonly) this.migrate();
|
|
17453
|
+
}
|
|
17454
|
+
close() {
|
|
17455
|
+
this.db.close();
|
|
17456
|
+
}
|
|
17457
|
+
recordAccountSeen(account, email3) {
|
|
17458
|
+
const now = this.now();
|
|
17459
|
+
this.db.prepare(
|
|
17460
|
+
`
|
|
17461
|
+
INSERT INTO account_scopes (backend_origin, user_id, email, created_at, updated_at)
|
|
17462
|
+
VALUES (?, ?, ?, ?, ?)
|
|
17463
|
+
ON CONFLICT (backend_origin, user_id) DO UPDATE SET
|
|
17464
|
+
email = excluded.email,
|
|
17465
|
+
updated_at = excluded.updated_at
|
|
17466
|
+
`
|
|
17467
|
+
).run(account.backendOrigin, account.userId, email3?.trim() || null, now, now);
|
|
17468
|
+
}
|
|
17469
|
+
addLocalArtifact(input) {
|
|
17470
|
+
const account = input.account ? requireAccountPartition(input.account) : null;
|
|
17471
|
+
const localPath = input.localPath.trim();
|
|
17472
|
+
if (!localPath) {
|
|
17473
|
+
throw cliError("usage.invalid_argument", "Local artifact path is required.");
|
|
17474
|
+
}
|
|
17475
|
+
if (account) this.recordAccountSeen(account);
|
|
17476
|
+
const now = this.now();
|
|
17477
|
+
const result = this.db.prepare(
|
|
17478
|
+
`
|
|
17479
|
+
INSERT INTO local_artifacts (
|
|
17480
|
+
kind,
|
|
17481
|
+
backend_origin,
|
|
17482
|
+
user_id,
|
|
17483
|
+
remote_id,
|
|
17484
|
+
local_path,
|
|
17485
|
+
metadata_json,
|
|
17486
|
+
created_at,
|
|
17487
|
+
updated_at,
|
|
17488
|
+
last_opened_at
|
|
17489
|
+
)
|
|
17490
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
17491
|
+
`
|
|
17492
|
+
).run(
|
|
17493
|
+
input.kind,
|
|
17494
|
+
account?.backendOrigin ?? null,
|
|
17495
|
+
account?.userId ?? null,
|
|
17496
|
+
input.remoteId?.trim() || null,
|
|
17497
|
+
localPath,
|
|
17498
|
+
input.metadata === void 0 ? null : JSON.stringify(input.metadata),
|
|
17499
|
+
now,
|
|
17500
|
+
now,
|
|
17501
|
+
input.lastOpenedAt ?? null
|
|
17502
|
+
);
|
|
17503
|
+
return this.getLocalArtifact(Number(result.lastInsertRowid));
|
|
17504
|
+
}
|
|
17505
|
+
upsertLocalArtifact(input) {
|
|
17506
|
+
const remoteId = input.remoteId?.trim();
|
|
17507
|
+
if (!remoteId) return this.addLocalArtifact(input);
|
|
17508
|
+
const account = input.account ? requireAccountPartition(input.account) : null;
|
|
17509
|
+
if (account) this.recordAccountSeen(account);
|
|
17510
|
+
const existing = this.findLocalArtifact({
|
|
17511
|
+
account,
|
|
17512
|
+
kind: input.kind,
|
|
17513
|
+
remoteId
|
|
17514
|
+
});
|
|
17515
|
+
if (!existing) return this.addLocalArtifact({ ...input, remoteId });
|
|
17516
|
+
const localPath = input.localPath.trim();
|
|
17517
|
+
if (!localPath) {
|
|
17518
|
+
throw cliError("usage.invalid_argument", "Local artifact path is required.");
|
|
17519
|
+
}
|
|
17520
|
+
const now = this.now();
|
|
17521
|
+
this.db.prepare(
|
|
17522
|
+
`
|
|
17523
|
+
UPDATE local_artifacts
|
|
17524
|
+
SET local_path = ?,
|
|
17525
|
+
metadata_json = ?,
|
|
17526
|
+
updated_at = ?,
|
|
17527
|
+
last_opened_at = COALESCE(?, last_opened_at)
|
|
17528
|
+
WHERE id = ?
|
|
17529
|
+
`
|
|
17530
|
+
).run(
|
|
17531
|
+
localPath,
|
|
17532
|
+
input.metadata === void 0 ? null : JSON.stringify(input.metadata),
|
|
17533
|
+
now,
|
|
17534
|
+
input.lastOpenedAt ?? null,
|
|
17535
|
+
existing.id
|
|
17536
|
+
);
|
|
17537
|
+
return this.getLocalArtifact(existing.id);
|
|
17538
|
+
}
|
|
17539
|
+
getLocalArtifact(id) {
|
|
17540
|
+
const row = this.db.prepare("SELECT * FROM local_artifacts WHERE id = ?").get(id);
|
|
17541
|
+
if (!row) {
|
|
17542
|
+
throw cliError("usage.invalid_argument", `Local artifact ${id} does not exist.`);
|
|
17543
|
+
}
|
|
17544
|
+
return mapArtifactRow(row);
|
|
17545
|
+
}
|
|
17546
|
+
listLocalArtifactsForAccount(accountInput, opts = {}) {
|
|
17547
|
+
const account = requireAccountPartition(accountInput);
|
|
17548
|
+
const params = [account.backendOrigin, account.userId];
|
|
17549
|
+
let source = `
|
|
17550
|
+
SELECT * FROM local_artifacts
|
|
17551
|
+
WHERE backend_origin = ? AND user_id = ?
|
|
17552
|
+
`;
|
|
17553
|
+
if (opts.kind) {
|
|
17554
|
+
source += " AND kind = ?";
|
|
17555
|
+
params.push(opts.kind);
|
|
17556
|
+
}
|
|
17557
|
+
if (opts.remoteId) {
|
|
17558
|
+
source += " AND remote_id = ?";
|
|
17559
|
+
params.push(opts.remoteId);
|
|
17560
|
+
}
|
|
17561
|
+
source += " ORDER BY updated_at DESC, id DESC";
|
|
17562
|
+
return this.db.prepare(source).all(...params).map(mapArtifactRow);
|
|
17563
|
+
}
|
|
17564
|
+
listUnattributedLocalArtifacts(opts = {}) {
|
|
17565
|
+
const params = [];
|
|
17566
|
+
let source = `
|
|
17567
|
+
SELECT * FROM local_artifacts
|
|
17568
|
+
WHERE backend_origin IS NULL AND user_id IS NULL
|
|
17569
|
+
`;
|
|
17570
|
+
if (opts.kind) {
|
|
17571
|
+
source += " AND kind = ?";
|
|
17572
|
+
params.push(opts.kind);
|
|
17573
|
+
}
|
|
17574
|
+
if (opts.remoteId) {
|
|
17575
|
+
source += " AND remote_id = ?";
|
|
17576
|
+
params.push(opts.remoteId);
|
|
17577
|
+
}
|
|
17578
|
+
source += " ORDER BY updated_at DESC, id DESC";
|
|
17579
|
+
return this.db.prepare(source).all(...params).map(mapArtifactRow);
|
|
17580
|
+
}
|
|
17581
|
+
findLocalArtifactForAccount(accountInput, opts) {
|
|
17582
|
+
const account = requireAccountPartition(accountInput);
|
|
17583
|
+
return this.findLocalArtifact({ account, kind: opts.kind, remoteId: opts.remoteId });
|
|
17584
|
+
}
|
|
17585
|
+
listDownloadedRecordingIdsForAccount(accountInput) {
|
|
17586
|
+
return new Set(
|
|
17587
|
+
this.listLocalArtifactsForAccount(accountInput, { kind: "download" }).map((artifact) => artifact.remoteId).filter((remoteId) => Boolean(remoteId))
|
|
17588
|
+
);
|
|
17589
|
+
}
|
|
17590
|
+
markLocalArtifactOpened(id) {
|
|
17591
|
+
const now = this.now();
|
|
17592
|
+
const result = this.db.prepare(
|
|
17593
|
+
`
|
|
17594
|
+
UPDATE local_artifacts
|
|
17595
|
+
SET last_opened_at = ?, updated_at = ?
|
|
17596
|
+
WHERE id = ?
|
|
17597
|
+
`
|
|
17598
|
+
).run(now, now, id);
|
|
17599
|
+
if (result.changes !== 1) {
|
|
17600
|
+
throw cliError("usage.invalid_argument", `Local artifact ${id} does not exist.`);
|
|
17601
|
+
}
|
|
17602
|
+
return this.getLocalArtifact(id);
|
|
17603
|
+
}
|
|
17604
|
+
claimUnattributedLocalArtifact(id, accountInput) {
|
|
17605
|
+
const account = requireAccountPartition(accountInput);
|
|
17606
|
+
this.recordAccountSeen(account);
|
|
17607
|
+
const result = this.db.prepare(
|
|
17608
|
+
`
|
|
17609
|
+
UPDATE local_artifacts
|
|
17610
|
+
SET backend_origin = ?, user_id = ?, updated_at = ?
|
|
17611
|
+
WHERE id = ? AND backend_origin IS NULL AND user_id IS NULL
|
|
17612
|
+
`
|
|
17613
|
+
).run(account.backendOrigin, account.userId, this.now(), id);
|
|
17614
|
+
return result.changes === 1;
|
|
17615
|
+
}
|
|
17616
|
+
migrate() {
|
|
17617
|
+
this.db.pragma("journal_mode = WAL");
|
|
17618
|
+
this.db.pragma("foreign_keys = ON");
|
|
17619
|
+
this.db.exec(`
|
|
17620
|
+
CREATE TABLE IF NOT EXISTS schema_meta (
|
|
17621
|
+
key TEXT PRIMARY KEY NOT NULL,
|
|
17622
|
+
value TEXT NOT NULL
|
|
17623
|
+
);
|
|
17624
|
+
|
|
17625
|
+
INSERT INTO schema_meta (key, value)
|
|
17626
|
+
VALUES ('schema_version', '${CLI_STORE_SCHEMA_VERSION}')
|
|
17627
|
+
ON CONFLICT (key) DO UPDATE SET value = excluded.value;
|
|
17628
|
+
|
|
17629
|
+
CREATE TABLE IF NOT EXISTS account_scopes (
|
|
17630
|
+
backend_origin TEXT NOT NULL,
|
|
17631
|
+
user_id TEXT NOT NULL,
|
|
17632
|
+
email TEXT,
|
|
17633
|
+
created_at INTEGER NOT NULL,
|
|
17634
|
+
updated_at INTEGER NOT NULL,
|
|
17635
|
+
PRIMARY KEY (backend_origin, user_id)
|
|
17636
|
+
);
|
|
17637
|
+
|
|
17638
|
+
CREATE TABLE IF NOT EXISTS local_artifacts (
|
|
17639
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
17640
|
+
kind TEXT NOT NULL,
|
|
17641
|
+
backend_origin TEXT,
|
|
17642
|
+
user_id TEXT,
|
|
17643
|
+
remote_id TEXT,
|
|
17644
|
+
local_path TEXT NOT NULL,
|
|
17645
|
+
metadata_json TEXT,
|
|
17646
|
+
created_at INTEGER NOT NULL,
|
|
17647
|
+
updated_at INTEGER NOT NULL,
|
|
17648
|
+
last_opened_at INTEGER,
|
|
17649
|
+
CHECK (
|
|
17650
|
+
(backend_origin IS NULL AND user_id IS NULL)
|
|
17651
|
+
OR (backend_origin IS NOT NULL AND user_id IS NOT NULL)
|
|
17652
|
+
)
|
|
17653
|
+
);
|
|
17654
|
+
|
|
17655
|
+
CREATE INDEX IF NOT EXISTS local_artifacts_account_kind_idx
|
|
17656
|
+
ON local_artifacts (backend_origin, user_id, kind, updated_at DESC);
|
|
17657
|
+
|
|
17658
|
+
CREATE INDEX IF NOT EXISTS local_artifacts_remote_idx
|
|
17659
|
+
ON local_artifacts (backend_origin, user_id, kind, remote_id);
|
|
17660
|
+
`);
|
|
17661
|
+
if (!hasColumn(this.db, "local_artifacts", "last_opened_at")) {
|
|
17662
|
+
this.db.exec("ALTER TABLE local_artifacts ADD COLUMN last_opened_at INTEGER");
|
|
17663
|
+
}
|
|
17664
|
+
}
|
|
17665
|
+
findLocalArtifact({
|
|
17666
|
+
account,
|
|
17667
|
+
kind,
|
|
17668
|
+
remoteId
|
|
17669
|
+
}) {
|
|
17670
|
+
const row = account ? this.db.prepare(
|
|
17671
|
+
`
|
|
17672
|
+
SELECT * FROM local_artifacts
|
|
17673
|
+
WHERE backend_origin = ? AND user_id = ? AND kind = ? AND remote_id = ?
|
|
17674
|
+
ORDER BY updated_at DESC, id DESC
|
|
17675
|
+
LIMIT 1
|
|
17676
|
+
`
|
|
17677
|
+
).get(account.backendOrigin, account.userId, kind, remoteId) : this.db.prepare(
|
|
17678
|
+
`
|
|
17679
|
+
SELECT * FROM local_artifacts
|
|
17680
|
+
WHERE backend_origin IS NULL AND user_id IS NULL AND kind = ? AND remote_id = ?
|
|
17681
|
+
ORDER BY updated_at DESC, id DESC
|
|
17682
|
+
LIMIT 1
|
|
17683
|
+
`
|
|
17684
|
+
).get(kind, remoteId);
|
|
17685
|
+
return row ? mapArtifactRow(row) : null;
|
|
17686
|
+
}
|
|
17687
|
+
};
|
|
17688
|
+
function parseAccountPartition(input, mode) {
|
|
17689
|
+
const rawOrigin = cleanString(input?.backendOrigin);
|
|
17690
|
+
const rawUserId = cleanString(input?.userId);
|
|
17691
|
+
if (!rawOrigin && !rawUserId) return null;
|
|
17692
|
+
if (!rawOrigin || !rawUserId) {
|
|
17693
|
+
if (mode === "strict") {
|
|
17694
|
+
throw cliError(
|
|
17695
|
+
"usage.invalid_argument",
|
|
17696
|
+
"Account stamp must include both backend origin and user id.",
|
|
17697
|
+
{
|
|
17698
|
+
hint: "Partial account stamps are treated as unattributed when reading legacy local state."
|
|
17699
|
+
}
|
|
17700
|
+
);
|
|
17701
|
+
}
|
|
17702
|
+
return null;
|
|
17703
|
+
}
|
|
17704
|
+
try {
|
|
17705
|
+
return { backendOrigin: validateOrigin(rawOrigin), userId: rawUserId };
|
|
17706
|
+
} catch (error51) {
|
|
17707
|
+
if (mode === "strict") throw error51;
|
|
17708
|
+
return null;
|
|
17709
|
+
}
|
|
17710
|
+
}
|
|
17711
|
+
function cleanString(value) {
|
|
17712
|
+
const trimmed = value?.trim();
|
|
17713
|
+
return trimmed ? trimmed : null;
|
|
17714
|
+
}
|
|
17715
|
+
function mapArtifactRow(row) {
|
|
17716
|
+
const backendOrigin = stringOrNull(row.backend_origin);
|
|
17717
|
+
const userId = stringOrNull(row.user_id);
|
|
17718
|
+
const lastOpenedAt = numberOrNull(row.last_opened_at);
|
|
17719
|
+
return {
|
|
17720
|
+
id: numberValue(row.id),
|
|
17721
|
+
kind: localArtifactKind(row.kind),
|
|
17722
|
+
account: backendOrigin && userId ? { backendOrigin, userId } : null,
|
|
17723
|
+
localPath: stringValue(row.local_path),
|
|
17724
|
+
...stringOrNull(row.remote_id) ? { remoteId: stringOrNull(row.remote_id) ?? void 0 } : {},
|
|
17725
|
+
...typeof row.metadata_json === "string" ? { metadata: JSON.parse(row.metadata_json) } : {},
|
|
17726
|
+
createdAt: numberValue(row.created_at),
|
|
17727
|
+
updatedAt: numberValue(row.updated_at),
|
|
17728
|
+
...lastOpenedAt ? { lastOpenedAt } : {}
|
|
17729
|
+
};
|
|
17730
|
+
}
|
|
17731
|
+
function hasColumn(db, table, column) {
|
|
17732
|
+
return db.prepare(`PRAGMA table_info(${table})`).all().some((row) => row.name === column);
|
|
17733
|
+
}
|
|
17734
|
+
function localArtifactKind(value) {
|
|
17735
|
+
if (value === "recording_session" || value === "download" || value === "live_caption_draft") {
|
|
17736
|
+
return value;
|
|
17737
|
+
}
|
|
17738
|
+
throw cliError("cloud.invalid_response", "CLI store contains an unknown artifact kind.");
|
|
17739
|
+
}
|
|
17740
|
+
function stringValue(value) {
|
|
17741
|
+
if (typeof value === "string") return value;
|
|
17742
|
+
throw cliError("cloud.invalid_response", "CLI store row contained an invalid string value.");
|
|
17743
|
+
}
|
|
17744
|
+
function stringOrNull(value) {
|
|
17745
|
+
return typeof value === "string" ? value : null;
|
|
17746
|
+
}
|
|
17747
|
+
function numberValue(value) {
|
|
17748
|
+
if (typeof value === "number") return value;
|
|
17749
|
+
if (typeof value === "bigint") return Number(value);
|
|
17750
|
+
throw cliError("cloud.invalid_response", "CLI store row contained an invalid number value.");
|
|
17751
|
+
}
|
|
17752
|
+
function numberOrNull(value) {
|
|
17753
|
+
if (typeof value === "number") return value;
|
|
17754
|
+
if (typeof value === "bigint") return Number(value);
|
|
17755
|
+
return null;
|
|
17756
|
+
}
|
|
17757
|
+
|
|
16651
17758
|
// src/api.ts
|
|
16652
17759
|
var RecappiApiClient = class {
|
|
16653
17760
|
constructor(auth, opts = {}) {
|
|
@@ -16655,10 +17762,12 @@ var RecappiApiClient = class {
|
|
|
16655
17762
|
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
16656
17763
|
this.sleep = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
16657
17764
|
this.env = opts.env ?? process.env;
|
|
17765
|
+
this.homeDir = opts.homeDir;
|
|
16658
17766
|
}
|
|
16659
17767
|
fetchImpl;
|
|
16660
17768
|
sleep;
|
|
16661
17769
|
env;
|
|
17770
|
+
homeDir;
|
|
16662
17771
|
async authStatus() {
|
|
16663
17772
|
if (!this.auth.token) {
|
|
16664
17773
|
return { loggedIn: false, origin: this.auth.origin };
|
|
@@ -16806,9 +17915,9 @@ var RecappiApiClient = class {
|
|
|
16806
17915
|
}
|
|
16807
17916
|
const contentType = normalizeContentType(response.headers.get("content-type"));
|
|
16808
17917
|
const contentLength = numberHeader(response.headers.get("content-length"));
|
|
16809
|
-
const dir = opts.directory ?? await fs3.mkdtemp(
|
|
17918
|
+
const dir = opts.directory ?? await fs3.mkdtemp(path4.join(os4.tmpdir(), "recappi-cli-audio-"));
|
|
16810
17919
|
if (opts.directory) await fs3.mkdir(dir, { recursive: true });
|
|
16811
|
-
const filePath =
|
|
17920
|
+
const filePath = path4.join(dir, recordingAudioFileName(recordingId, opts.title, contentType));
|
|
16812
17921
|
try {
|
|
16813
17922
|
await pipeline(
|
|
16814
17923
|
Readable.fromWeb(response.body),
|
|
@@ -16830,6 +17939,47 @@ var RecappiApiClient = class {
|
|
|
16830
17939
|
const parsed = await this.getJson("/api/dashboard/stats");
|
|
16831
17940
|
return mapDashboardStats(parsed, this.auth.origin);
|
|
16832
17941
|
}
|
|
17942
|
+
async billingStatus() {
|
|
17943
|
+
const parsed = await this.getJson("/api/billing/status");
|
|
17944
|
+
return mapBillingStatus(parsed, this.auth.origin);
|
|
17945
|
+
}
|
|
17946
|
+
async accountStatus() {
|
|
17947
|
+
const status = await this.authStatus();
|
|
17948
|
+
const storePath = defaultStorePath(this.homeDir, this.env);
|
|
17949
|
+
let accountScopedArtifacts = 0;
|
|
17950
|
+
let unattributedArtifacts = 0;
|
|
17951
|
+
if (status.loggedIn && status.userId) {
|
|
17952
|
+
const store = openCliStore({
|
|
17953
|
+
dbPath: storePath,
|
|
17954
|
+
env: this.env,
|
|
17955
|
+
homeDir: this.homeDir
|
|
17956
|
+
});
|
|
17957
|
+
try {
|
|
17958
|
+
const account = requireAccountPartition({
|
|
17959
|
+
backendOrigin: this.auth.origin,
|
|
17960
|
+
userId: status.userId
|
|
17961
|
+
});
|
|
17962
|
+
store.recordAccountSeen(account, status.email);
|
|
17963
|
+
accountScopedArtifacts = store.listLocalArtifactsForAccount(account).length;
|
|
17964
|
+
unattributedArtifacts = store.listUnattributedLocalArtifacts().length;
|
|
17965
|
+
} finally {
|
|
17966
|
+
store.close();
|
|
17967
|
+
}
|
|
17968
|
+
}
|
|
17969
|
+
const billing = status.loggedIn ? await this.billingStatus() : void 0;
|
|
17970
|
+
return accountStatusDataSchema.parse({
|
|
17971
|
+
origin: this.auth.origin,
|
|
17972
|
+
loggedIn: status.loggedIn,
|
|
17973
|
+
...status.email ? { email: status.email } : {},
|
|
17974
|
+
...status.userId ? { userId: status.userId } : {},
|
|
17975
|
+
localStore: {
|
|
17976
|
+
path: storePath,
|
|
17977
|
+
accountScopedArtifacts,
|
|
17978
|
+
unattributedArtifacts
|
|
17979
|
+
},
|
|
17980
|
+
...billing ? { billing } : {}
|
|
17981
|
+
});
|
|
17982
|
+
}
|
|
16833
17983
|
async uploadPathBatch(opts) {
|
|
16834
17984
|
const files = await collectAudioFiles(opts.inputPath);
|
|
16835
17985
|
if (files.length === 0) {
|
|
@@ -17010,12 +18160,12 @@ var RecappiApiClient = class {
|
|
|
17010
18160
|
...typeof parsed.language === "string" || parsed.language === null ? { language: parsed.language } : {}
|
|
17011
18161
|
};
|
|
17012
18162
|
}
|
|
17013
|
-
async getJson(
|
|
17014
|
-
const response = await this.request("GET",
|
|
18163
|
+
async getJson(path6) {
|
|
18164
|
+
const response = await this.request("GET", path6);
|
|
17015
18165
|
return await parseJson(response);
|
|
17016
18166
|
}
|
|
17017
|
-
async postJson(
|
|
17018
|
-
const response = await this.request("POST",
|
|
18167
|
+
async postJson(path6, body) {
|
|
18168
|
+
const response = await this.request("POST", path6, JSON.stringify(body), {
|
|
17019
18169
|
headers: { "content-type": "application/json" }
|
|
17020
18170
|
});
|
|
17021
18171
|
return await parseJson(response);
|
|
@@ -17237,9 +18387,9 @@ function parseSummary(row) {
|
|
|
17237
18387
|
function mapJobListItem(row) {
|
|
17238
18388
|
const recording = isRecord2(row.recording) ? row.recording : {};
|
|
17239
18389
|
return {
|
|
17240
|
-
jobId:
|
|
17241
|
-
recordingId:
|
|
17242
|
-
status:
|
|
18390
|
+
jobId: stringValue2(row.jobId) ?? stringValue2(row.id) ?? "",
|
|
18391
|
+
recordingId: stringValue2(row.recordingId) ?? "",
|
|
18392
|
+
status: stringValue2(row.status) ?? "queued",
|
|
17243
18393
|
...typeof row.provider === "string" ? { provider: row.provider } : {},
|
|
17244
18394
|
...typeof row.model === "string" ? { model: row.model } : {},
|
|
17245
18395
|
...typeof row.language === "string" || row.language === null ? { language: row.language } : {},
|
|
@@ -17258,10 +18408,10 @@ function mapJobListItem(row) {
|
|
|
17258
18408
|
};
|
|
17259
18409
|
}
|
|
17260
18410
|
function mapRecording(row, origin) {
|
|
17261
|
-
const recordingId =
|
|
17262
|
-
const status =
|
|
17263
|
-
const createdAt =
|
|
17264
|
-
const updatedAt =
|
|
18411
|
+
const recordingId = stringValue2(row.id) ?? stringValue2(row.recordingId);
|
|
18412
|
+
const status = stringValue2(row.status);
|
|
18413
|
+
const createdAt = numberValue2(row.createdAt);
|
|
18414
|
+
const updatedAt = numberValue2(row.updatedAt);
|
|
17265
18415
|
if (!recordingId) {
|
|
17266
18416
|
throw cliError("cloud.invalid_response", "Recording response was missing id.");
|
|
17267
18417
|
}
|
|
@@ -17300,16 +18450,37 @@ function mapDashboardStats(row, origin) {
|
|
|
17300
18450
|
jobs: mapCountObject(row.jobs, ["active", "queued", "running", "succeeded", "failed"])
|
|
17301
18451
|
});
|
|
17302
18452
|
}
|
|
18453
|
+
function mapBillingStatus(row, origin) {
|
|
18454
|
+
return billingStatusDataSchema.parse({
|
|
18455
|
+
origin,
|
|
18456
|
+
tier: row.tier,
|
|
18457
|
+
periodStart: numberValue2(row.periodStart),
|
|
18458
|
+
periodEnd: numberValue2(row.periodEnd),
|
|
18459
|
+
storageBytes: numberValue2(row.storageBytes) ?? 0,
|
|
18460
|
+
storageCapBytes: nullableCap(row.storageCapBytes),
|
|
18461
|
+
minutesUsed: numberValue2(row.minutesUsed) ?? 0,
|
|
18462
|
+
batchMinutesUsed: numberValue2(row.batchMinutesUsed) ?? 0,
|
|
18463
|
+
realtimeMinutesUsed: numberValue2(row.realtimeMinutesUsed) ?? 0,
|
|
18464
|
+
minutesCap: nullableCap(row.minutesCap),
|
|
18465
|
+
isOverStorage: row.isOverStorage === true,
|
|
18466
|
+
isOverMinutes: row.isOverMinutes === true
|
|
18467
|
+
});
|
|
18468
|
+
}
|
|
17303
18469
|
function mapCountObject(value, keys) {
|
|
17304
18470
|
const source = isRecord2(value) ? value : {};
|
|
17305
|
-
return Object.fromEntries(keys.map((key) => [key,
|
|
18471
|
+
return Object.fromEntries(keys.map((key) => [key, numberValue2(source[key]) ?? 0]));
|
|
17306
18472
|
}
|
|
17307
|
-
function
|
|
18473
|
+
function stringValue2(value) {
|
|
17308
18474
|
return typeof value === "string" ? value : void 0;
|
|
17309
18475
|
}
|
|
17310
|
-
function
|
|
18476
|
+
function numberValue2(value) {
|
|
17311
18477
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
17312
18478
|
}
|
|
18479
|
+
function nullableCap(value) {
|
|
18480
|
+
if (value === null) return null;
|
|
18481
|
+
const number4 = numberValue2(value);
|
|
18482
|
+
return number4 === void 0 ? null : number4;
|
|
18483
|
+
}
|
|
17313
18484
|
function parseSummaryStatus(value) {
|
|
17314
18485
|
const allowed = /* @__PURE__ */ new Set([
|
|
17315
18486
|
"pending",
|
|
@@ -17344,11 +18515,37 @@ function decodeJsonRecord(value) {
|
|
|
17344
18515
|
|
|
17345
18516
|
// src/audio.ts
|
|
17346
18517
|
import { spawn } from "child_process";
|
|
18518
|
+
import { promises as fs4 } from "fs";
|
|
18519
|
+
import path5 from "path";
|
|
17347
18520
|
function createRecordingAudioRuntime(client, deps = {}) {
|
|
18521
|
+
const downloadRecordingAudioFile = async (recordingId, opts) => {
|
|
18522
|
+
const cached2 = await findReusableDownload(recordingId, deps);
|
|
18523
|
+
if (cached2) return cached2;
|
|
18524
|
+
const directory = opts?.directory ?? (deps.account ? defaultDownloadDirectory(deps) : void 0);
|
|
18525
|
+
const download = await client.downloadRecordingAudio(recordingId, {
|
|
18526
|
+
...opts,
|
|
18527
|
+
...directory ? { directory } : {}
|
|
18528
|
+
});
|
|
18529
|
+
const artifact = await rememberDownload(download, deps);
|
|
18530
|
+
return {
|
|
18531
|
+
recordingId: download.recordingId,
|
|
18532
|
+
localPath: download.localPath,
|
|
18533
|
+
reused: false,
|
|
18534
|
+
...artifact ? { artifactId: artifact.id } : {},
|
|
18535
|
+
contentType: download.contentType,
|
|
18536
|
+
...download.contentLength !== void 0 ? { contentLength: download.contentLength } : {},
|
|
18537
|
+
origin: download.origin
|
|
18538
|
+
};
|
|
18539
|
+
};
|
|
17348
18540
|
return {
|
|
17349
|
-
downloadRecordingAudio: async (recordingId, opts) => (await
|
|
18541
|
+
downloadRecordingAudio: async (recordingId, opts) => (await downloadRecordingAudioFile(recordingId, opts)).localPath,
|
|
18542
|
+
downloadRecordingAudioFile,
|
|
17350
18543
|
openPath: (localPath) => openPath(localPath, deps),
|
|
17351
|
-
revealInFinder: (localPath) => revealInFinder(localPath, deps)
|
|
18544
|
+
revealInFinder: (localPath) => revealInFinder(localPath, deps),
|
|
18545
|
+
listDownloads: () => listExistingDownloads(deps),
|
|
18546
|
+
listDownloadedRecordingIds: async () => new Set(
|
|
18547
|
+
(await listExistingDownloads(deps)).map((artifact) => artifact.remoteId).filter((remoteId) => Boolean(remoteId))
|
|
18548
|
+
)
|
|
17352
18549
|
};
|
|
17353
18550
|
}
|
|
17354
18551
|
function openPath(localPath, deps = {}) {
|
|
@@ -17357,6 +18554,80 @@ function openPath(localPath, deps = {}) {
|
|
|
17357
18554
|
function revealInFinder(localPath, deps = {}) {
|
|
17358
18555
|
return runMacOpen(["-R", localPath], deps);
|
|
17359
18556
|
}
|
|
18557
|
+
async function findReusableDownload(recordingId, deps) {
|
|
18558
|
+
return withStore(deps, async (store, account) => {
|
|
18559
|
+
if (!account) return null;
|
|
18560
|
+
const artifact = store.findLocalArtifactForAccount(account, {
|
|
18561
|
+
kind: "download",
|
|
18562
|
+
remoteId: recordingId
|
|
18563
|
+
});
|
|
18564
|
+
if (!artifact || !await isReadableFile(artifact.localPath)) return null;
|
|
18565
|
+
const opened = store.markLocalArtifactOpened(artifact.id);
|
|
18566
|
+
return artifactToDownload(opened, recordingId);
|
|
18567
|
+
});
|
|
18568
|
+
}
|
|
18569
|
+
async function rememberDownload(download, deps) {
|
|
18570
|
+
return withStore(deps, (store, account) => {
|
|
18571
|
+
if (!account) return null;
|
|
18572
|
+
const artifact = store.upsertLocalArtifact({
|
|
18573
|
+
kind: "download",
|
|
18574
|
+
account,
|
|
18575
|
+
remoteId: download.recordingId,
|
|
18576
|
+
localPath: download.localPath,
|
|
18577
|
+
metadata: {
|
|
18578
|
+
resource: "recording_audio",
|
|
18579
|
+
contentType: download.contentType,
|
|
18580
|
+
...download.contentLength !== void 0 ? { contentLength: download.contentLength } : {},
|
|
18581
|
+
origin: download.origin
|
|
18582
|
+
}
|
|
18583
|
+
});
|
|
18584
|
+
return store.markLocalArtifactOpened(artifact.id);
|
|
18585
|
+
});
|
|
18586
|
+
}
|
|
18587
|
+
async function listExistingDownloads(deps) {
|
|
18588
|
+
const artifacts = await withStore(
|
|
18589
|
+
deps,
|
|
18590
|
+
(store, account) => account ? store.listLocalArtifactsForAccount(account, { kind: "download" }) : []
|
|
18591
|
+
);
|
|
18592
|
+
const existing = [];
|
|
18593
|
+
for (const artifact of artifacts) {
|
|
18594
|
+
if (await isReadableFile(artifact.localPath)) existing.push(artifact);
|
|
18595
|
+
}
|
|
18596
|
+
return existing;
|
|
18597
|
+
}
|
|
18598
|
+
async function withStore(deps, run) {
|
|
18599
|
+
const store = deps.store ?? openCliStore({ homeDir: deps.homeDir, env: deps.env });
|
|
18600
|
+
try {
|
|
18601
|
+
return await run(store, deps.account ?? null);
|
|
18602
|
+
} finally {
|
|
18603
|
+
if (!deps.store) store.close();
|
|
18604
|
+
}
|
|
18605
|
+
}
|
|
18606
|
+
function defaultDownloadDirectory(deps) {
|
|
18607
|
+
return path5.join(path5.dirname(defaultStorePath(deps.homeDir, deps.env)), "downloads");
|
|
18608
|
+
}
|
|
18609
|
+
async function isReadableFile(localPath) {
|
|
18610
|
+
try {
|
|
18611
|
+
return (await fs4.stat(localPath)).isFile();
|
|
18612
|
+
} catch {
|
|
18613
|
+
return false;
|
|
18614
|
+
}
|
|
18615
|
+
}
|
|
18616
|
+
function artifactToDownload(artifact, recordingId) {
|
|
18617
|
+
const metadata = isRecord3(artifact.metadata) ? artifact.metadata : {};
|
|
18618
|
+
return {
|
|
18619
|
+
recordingId,
|
|
18620
|
+
localPath: artifact.localPath,
|
|
18621
|
+
reused: true,
|
|
18622
|
+
artifactId: artifact.id,
|
|
18623
|
+
...typeof metadata.contentType === "string" ? { contentType: metadata.contentType } : {},
|
|
18624
|
+
...typeof metadata.contentLength === "number" ? { contentLength: metadata.contentLength } : {},
|
|
18625
|
+
...typeof metadata.origin === "string" ? { origin: metadata.origin } : {}
|
|
18626
|
+
};
|
|
18627
|
+
}
|
|
18628
|
+
function isRecord3(value) {
|
|
18629
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18630
|
+
}
|
|
17360
18631
|
function runMacOpen(args, deps) {
|
|
17361
18632
|
if ((deps.platform ?? process.platform) !== "darwin") {
|
|
17362
18633
|
return Promise.reject(
|
|
@@ -17478,20 +18749,20 @@ function renderEnvelope(envelope, opts) {
|
|
|
17478
18749
|
`);
|
|
17479
18750
|
}
|
|
17480
18751
|
function renderHumanSuccess(command, data, opts) {
|
|
17481
|
-
if (command === "auth login" &&
|
|
18752
|
+
if (command === "auth login" && isRecord4(data)) {
|
|
17482
18753
|
opts.stdout(`Signed in${typeof data.email === "string" ? ` as ${data.email}` : ""}
|
|
17483
18754
|
`);
|
|
17484
18755
|
return;
|
|
17485
18756
|
}
|
|
17486
|
-
if (command === "auth logout" &&
|
|
18757
|
+
if (command === "auth logout" && isRecord4(data)) {
|
|
17487
18758
|
opts.stdout(data.cleared ? "Signed out of Recappi CLI\n" : "No Recappi CLI session to clear\n");
|
|
17488
18759
|
return;
|
|
17489
18760
|
}
|
|
17490
|
-
if (command === "auth import-macos" &&
|
|
18761
|
+
if (command === "auth import-macos" && isRecord4(data)) {
|
|
17491
18762
|
opts.stdout("Imported the Recappi Mini app session into Recappi CLI\n");
|
|
17492
18763
|
return;
|
|
17493
18764
|
}
|
|
17494
|
-
if (command === "auth status" &&
|
|
18765
|
+
if (command === "auth status" && isRecord4(data)) {
|
|
17495
18766
|
if (data.loggedIn) {
|
|
17496
18767
|
opts.stdout(`Signed in${typeof data.email === "string" ? ` as ${data.email}` : ""}
|
|
17497
18768
|
`);
|
|
@@ -17500,17 +18771,50 @@ function renderHumanSuccess(command, data, opts) {
|
|
|
17500
18771
|
opts.stdout("Not logged in\n");
|
|
17501
18772
|
return;
|
|
17502
18773
|
}
|
|
17503
|
-
if (command === "
|
|
18774
|
+
if (command === "account status" && isRecord4(data)) {
|
|
18775
|
+
if (!data.loggedIn) {
|
|
18776
|
+
opts.stdout("Not logged in\n");
|
|
18777
|
+
return;
|
|
18778
|
+
}
|
|
18779
|
+
opts.stdout(`Account: ${typeof data.email === "string" ? data.email : "signed in"}
|
|
18780
|
+
`);
|
|
18781
|
+
if (typeof data.origin === "string") opts.stdout(` origin: ${data.origin}
|
|
18782
|
+
`);
|
|
18783
|
+
if (typeof data.userId === "string") opts.stdout(` userId: ${data.userId}
|
|
18784
|
+
`);
|
|
18785
|
+
const billing = isRecord4(data.billing) ? data.billing : {};
|
|
18786
|
+
if (typeof billing.tier === "string") opts.stdout(` plan: ${billing.tier}
|
|
18787
|
+
`);
|
|
18788
|
+
if (typeof billing.minutesUsed === "number") {
|
|
18789
|
+
const cap = formatNullableCap(billing.minutesCap, "minutes");
|
|
18790
|
+
opts.stdout(` minutes: ${billing.minutesUsed} / ${cap}
|
|
18791
|
+
`);
|
|
18792
|
+
}
|
|
18793
|
+
if (typeof billing.storageBytes === "number") {
|
|
18794
|
+
const cap = formatNullableCap(billing.storageCapBytes, "bytes");
|
|
18795
|
+
opts.stdout(` storage: ${formatBytes(billing.storageBytes)} / ${cap}
|
|
18796
|
+
`);
|
|
18797
|
+
}
|
|
18798
|
+
const localStore = isRecord4(data.localStore) ? data.localStore : {};
|
|
18799
|
+
if (typeof localStore.path === "string") opts.stdout(` localStore: ${localStore.path}
|
|
18800
|
+
`);
|
|
18801
|
+
opts.stdout(
|
|
18802
|
+
` localArtifacts: ${numberText(localStore.accountScopedArtifacts)} current, ${numberText(localStore.unattributedArtifacts)} unattributed
|
|
18803
|
+
`
|
|
18804
|
+
);
|
|
18805
|
+
return;
|
|
18806
|
+
}
|
|
18807
|
+
if (command === "version" && isRecord4(data) && typeof data.version === "string") {
|
|
17504
18808
|
opts.stdout(`${data.version}
|
|
17505
18809
|
`);
|
|
17506
18810
|
return;
|
|
17507
18811
|
}
|
|
17508
|
-
if (command === "doctor" &&
|
|
18812
|
+
if (command === "doctor" && isRecord4(data) && Array.isArray(data.checks)) {
|
|
17509
18813
|
const status = typeof data.status === "string" ? data.status : "unknown";
|
|
17510
18814
|
opts.stdout(`Doctor: ${status}
|
|
17511
18815
|
`);
|
|
17512
18816
|
for (const check2 of data.checks) {
|
|
17513
|
-
if (!
|
|
18817
|
+
if (!isRecord4(check2)) continue;
|
|
17514
18818
|
const checkStatus = typeof check2.status === "string" ? check2.status : "unknown";
|
|
17515
18819
|
const name = typeof check2.name === "string" ? check2.name : "check";
|
|
17516
18820
|
const message = typeof check2.message === "string" ? ` \u2014 ${check2.message}` : "";
|
|
@@ -17521,14 +18825,14 @@ function renderHumanSuccess(command, data, opts) {
|
|
|
17521
18825
|
}
|
|
17522
18826
|
return;
|
|
17523
18827
|
}
|
|
17524
|
-
if (command === "transcript get" &&
|
|
18828
|
+
if (command === "transcript get" && isRecord4(data)) {
|
|
17525
18829
|
renderTranscriptHuman(data, opts);
|
|
17526
18830
|
return;
|
|
17527
18831
|
}
|
|
17528
|
-
if (command === "recordings list" &&
|
|
18832
|
+
if (command === "recordings list" && isRecord4(data) && Array.isArray(data.items)) {
|
|
17529
18833
|
opts.stdout("Recordings:\n");
|
|
17530
18834
|
for (const item of data.items) {
|
|
17531
|
-
if (!
|
|
18835
|
+
if (!isRecord4(item)) continue;
|
|
17532
18836
|
opts.stdout(` ${recordingLabel(item)}
|
|
17533
18837
|
`);
|
|
17534
18838
|
}
|
|
@@ -17540,7 +18844,7 @@ Next cursor: ${data.nextCursor}
|
|
|
17540
18844
|
}
|
|
17541
18845
|
return;
|
|
17542
18846
|
}
|
|
17543
|
-
if (command === "recordings get" &&
|
|
18847
|
+
if (command === "recordings get" && isRecord4(data)) {
|
|
17544
18848
|
opts.stdout(`${recordingTitle(data)}
|
|
17545
18849
|
`);
|
|
17546
18850
|
opts.stdout(` recordingId: ${String(data.recordingId)}
|
|
@@ -17562,9 +18866,9 @@ Next:
|
|
|
17562
18866
|
}
|
|
17563
18867
|
return;
|
|
17564
18868
|
}
|
|
17565
|
-
if (command === "dashboard stats" &&
|
|
17566
|
-
const recordings =
|
|
17567
|
-
const jobs =
|
|
18869
|
+
if (command === "dashboard stats" && isRecord4(data)) {
|
|
18870
|
+
const recordings = isRecord4(data.recordings) ? data.recordings : {};
|
|
18871
|
+
const jobs = isRecord4(data.jobs) ? data.jobs : {};
|
|
17568
18872
|
opts.stdout(
|
|
17569
18873
|
`Recordings: ${numberText(recordings.total)} total, ${numberText(recordings.ready)} ready
|
|
17570
18874
|
`
|
|
@@ -17604,7 +18908,50 @@ Next:
|
|
|
17604
18908
|
}
|
|
17605
18909
|
return;
|
|
17606
18910
|
}
|
|
17607
|
-
if (
|
|
18911
|
+
if (command === "record" && isRecord4(data)) {
|
|
18912
|
+
opts.stdout("Recording complete\n");
|
|
18913
|
+
if (typeof data.recordingId === "string") opts.stdout(` recordingId: ${data.recordingId}
|
|
18914
|
+
`);
|
|
18915
|
+
if (typeof data.sessionId === "string") opts.stdout(` sessionId: ${data.sessionId}
|
|
18916
|
+
`);
|
|
18917
|
+
if (typeof data.localSessionRef === "string") {
|
|
18918
|
+
opts.stdout(` localSessionRef: ${data.localSessionRef}
|
|
18919
|
+
`);
|
|
18920
|
+
}
|
|
18921
|
+
if (Array.isArray(data.artifacts) && data.artifacts.length > 0) {
|
|
18922
|
+
opts.stdout(" artifacts:\n");
|
|
18923
|
+
for (const artifact of data.artifacts) {
|
|
18924
|
+
if (!isRecord4(artifact)) continue;
|
|
18925
|
+
const kind = typeof artifact.kind === "string" ? artifact.kind : "artifact";
|
|
18926
|
+
const localPath = typeof artifact.localPath === "string" ? artifact.localPath : "";
|
|
18927
|
+
opts.stdout(` - ${kind}: ${localPath}
|
|
18928
|
+
`);
|
|
18929
|
+
}
|
|
18930
|
+
}
|
|
18931
|
+
if (typeof data.recordingId === "string") {
|
|
18932
|
+
opts.stdout(`
|
|
18933
|
+
Next:
|
|
18934
|
+
recappi recordings get ${data.recordingId}
|
|
18935
|
+
`);
|
|
18936
|
+
}
|
|
18937
|
+
return;
|
|
18938
|
+
}
|
|
18939
|
+
if (command === "audio" && isRecord4(data)) {
|
|
18940
|
+
const action = typeof data.action === "string" ? data.action : "download";
|
|
18941
|
+
opts.stdout(
|
|
18942
|
+
action === "open" ? "Audio opened\n" : action === "reveal" ? "Audio revealed\n" : "Audio ready\n"
|
|
18943
|
+
);
|
|
18944
|
+
if (typeof data.recordingId === "string") opts.stdout(` recordingId: ${data.recordingId}
|
|
18945
|
+
`);
|
|
18946
|
+
if (typeof data.localPath === "string") opts.stdout(` localPath: ${data.localPath}
|
|
18947
|
+
`);
|
|
18948
|
+
if (typeof data.reused === "boolean") {
|
|
18949
|
+
opts.stdout(` source: ${data.reused ? "local cache" : "downloaded"}
|
|
18950
|
+
`);
|
|
18951
|
+
}
|
|
18952
|
+
return;
|
|
18953
|
+
}
|
|
18954
|
+
if ((command === "jobs wait" || command === "upload") && isRecord4(data)) {
|
|
17608
18955
|
if (typeof data.transcriptId === "string") {
|
|
17609
18956
|
opts.stdout("Transcription ready\n");
|
|
17610
18957
|
opts.stdout(` transcriptId: ${data.transcriptId}
|
|
@@ -17625,10 +18972,10 @@ Next:
|
|
|
17625
18972
|
}
|
|
17626
18973
|
return;
|
|
17627
18974
|
}
|
|
17628
|
-
if (command === "schema" &&
|
|
18975
|
+
if (command === "schema" && isRecord4(data) && Array.isArray(data.commands)) {
|
|
17629
18976
|
opts.stdout("Commands:\n");
|
|
17630
18977
|
for (const entry of data.commands) {
|
|
17631
|
-
if (!
|
|
18978
|
+
if (!isRecord4(entry) || typeof entry.name !== "string") continue;
|
|
17632
18979
|
const summary = typeof entry.summary === "string" ? ` \u2014 ${entry.summary}` : "";
|
|
17633
18980
|
opts.stdout(` ${entry.name}${summary}
|
|
17634
18981
|
`);
|
|
@@ -17718,7 +19065,7 @@ function renderTranscriptHuman(data, opts) {
|
|
|
17718
19065
|
const segments = Array.isArray(data.segments) ? data.segments : [];
|
|
17719
19066
|
let printedBody = false;
|
|
17720
19067
|
for (const segment of segments) {
|
|
17721
|
-
if (!
|
|
19068
|
+
if (!isRecord4(segment) || typeof segment.text !== "string") continue;
|
|
17722
19069
|
const clock = typeof segment.startMs === "number" ? `[${formatClock(segment.startMs / 1e3)}] ` : "";
|
|
17723
19070
|
const speaker = typeof segment.speaker === "string" ? `${segment.speaker}: ` : "";
|
|
17724
19071
|
opts.stdout(`${clock}${speaker}${segment.text}
|
|
@@ -17730,7 +19077,7 @@ function renderTranscriptHuman(data, opts) {
|
|
|
17730
19077
|
`);
|
|
17731
19078
|
printedBody = true;
|
|
17732
19079
|
}
|
|
17733
|
-
const summary =
|
|
19080
|
+
const summary = isRecord4(data.summary) ? data.summary : void 0;
|
|
17734
19081
|
if (!summary || summary.status !== "succeeded") return;
|
|
17735
19082
|
if (typeof summary.tldr === "string" && summary.tldr.length > 0) {
|
|
17736
19083
|
opts.stdout(`
|
|
@@ -17748,7 +19095,7 @@ Summary:
|
|
|
17748
19095
|
if (Array.isArray(summary.actionItems) && summary.actionItems.length > 0) {
|
|
17749
19096
|
opts.stdout("\nAction items:\n");
|
|
17750
19097
|
for (const item of summary.actionItems) {
|
|
17751
|
-
if (!
|
|
19098
|
+
if (!isRecord4(item) || typeof item.what !== "string") continue;
|
|
17752
19099
|
const who = typeof item.who === "string" ? `${item.who}: ` : "";
|
|
17753
19100
|
opts.stdout(` - ${who}${item.what}
|
|
17754
19101
|
`);
|
|
@@ -17786,6 +19133,11 @@ function formatBytes(bytes) {
|
|
|
17786
19133
|
}
|
|
17787
19134
|
return `${value >= 10 ? value.toFixed(0) : value.toFixed(1)} ${unit}`;
|
|
17788
19135
|
}
|
|
19136
|
+
function formatNullableCap(value, unit) {
|
|
19137
|
+
if (value === null || value === void 0) return "Unlimited";
|
|
19138
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return "Unlimited";
|
|
19139
|
+
return unit === "bytes" ? formatBytes(value) : String(value);
|
|
19140
|
+
}
|
|
17789
19141
|
function formatClock(seconds) {
|
|
17790
19142
|
const total = Math.max(0, Math.floor(seconds));
|
|
17791
19143
|
const hours = Math.floor(total / 3600);
|
|
@@ -17814,7 +19166,7 @@ function applyFields(command, data, fields, compact) {
|
|
|
17814
19166
|
};
|
|
17815
19167
|
return compact ? compactData(filtered2) : filtered2;
|
|
17816
19168
|
}
|
|
17817
|
-
if (!
|
|
19169
|
+
if (!isRecord4(data)) return data;
|
|
17818
19170
|
const allowed = new Set(Object.keys(data));
|
|
17819
19171
|
assertKnownFields(fields, allowed);
|
|
17820
19172
|
const filtered = pickFields(data, fields);
|
|
@@ -17839,7 +19191,7 @@ function compactData(value) {
|
|
|
17839
19191
|
if (Array.isArray(value)) {
|
|
17840
19192
|
return value.map(compactData).filter((item) => item !== void 0);
|
|
17841
19193
|
}
|
|
17842
|
-
if (
|
|
19194
|
+
if (isRecord4(value)) {
|
|
17843
19195
|
const out = {};
|
|
17844
19196
|
for (const [key, child] of Object.entries(value)) {
|
|
17845
19197
|
const compacted = compactData(child);
|
|
@@ -17857,16 +19209,16 @@ function stableStringify(value, compact) {
|
|
|
17857
19209
|
}
|
|
17858
19210
|
function sortKeys(value) {
|
|
17859
19211
|
if (Array.isArray(value)) return value.map(sortKeys);
|
|
17860
|
-
if (!
|
|
19212
|
+
if (!isRecord4(value)) return value;
|
|
17861
19213
|
return Object.fromEntries(
|
|
17862
19214
|
Object.keys(value).sort().map((key) => [key, sortKeys(value[key])])
|
|
17863
19215
|
);
|
|
17864
19216
|
}
|
|
17865
|
-
function
|
|
19217
|
+
function isRecord4(value) {
|
|
17866
19218
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17867
19219
|
}
|
|
17868
19220
|
function isUploadBatch(value) {
|
|
17869
|
-
return
|
|
19221
|
+
return isRecord4(value) && Array.isArray(value.successes) && Array.isArray(value.failures);
|
|
17870
19222
|
}
|
|
17871
19223
|
|
|
17872
19224
|
// src/schema.ts
|
|
@@ -17875,9 +19227,12 @@ var COMMAND_DATA_SCHEMAS = {
|
|
|
17875
19227
|
"auth logout": authLogoutDataSchema,
|
|
17876
19228
|
"auth import-macos": authImportDataSchema,
|
|
17877
19229
|
"auth status": authStatusDataSchema,
|
|
19230
|
+
"account status": accountStatusDataSchema,
|
|
19231
|
+
audio: audioCommandDataSchema,
|
|
17878
19232
|
doctor: doctorDataSchema,
|
|
17879
19233
|
"dashboard stats": dashboardStatsDataSchema,
|
|
17880
19234
|
upload: uploadBatchDataSchema,
|
|
19235
|
+
record: recordCommandDataSchema,
|
|
17881
19236
|
"recordings get": recordingDataSchema,
|
|
17882
19237
|
"recordings list": recordingListDataSchema,
|
|
17883
19238
|
"jobs list": jobListDataSchema,
|
|
@@ -17914,11 +19269,11 @@ function buildSchemaDocument(program) {
|
|
|
17914
19269
|
event: toJsonSchema(operationEventSchema)
|
|
17915
19270
|
};
|
|
17916
19271
|
}
|
|
17917
|
-
function walkCommands(command,
|
|
19272
|
+
function walkCommands(command, path6, out) {
|
|
17918
19273
|
for (const sub of subcommandsOf(command)) {
|
|
17919
19274
|
const name = sub.name();
|
|
17920
19275
|
if (name === "help") continue;
|
|
17921
|
-
const fullPath = [...
|
|
19276
|
+
const fullPath = [...path6, name];
|
|
17922
19277
|
const children = subcommandsOf(sub).filter((child) => child.name() !== "help");
|
|
17923
19278
|
if (children.length === 0) {
|
|
17924
19279
|
out.push(leafCommandDoc(sub, fullPath.join(" ")));
|
|
@@ -17977,6 +19332,414 @@ function readCliVersion() {
|
|
|
17977
19332
|
}
|
|
17978
19333
|
var CLI_VERSION = readCliVersion();
|
|
17979
19334
|
|
|
19335
|
+
// src/record.tsx
|
|
19336
|
+
import { render, useInput as useInput2 } from "ink";
|
|
19337
|
+
|
|
19338
|
+
// src/sidecar.ts
|
|
19339
|
+
import { spawn as spawn2 } from "child_process";
|
|
19340
|
+
import { createInterface } from "readline";
|
|
19341
|
+
var MiniSidecarClient = class {
|
|
19342
|
+
input;
|
|
19343
|
+
requestTimeoutMs;
|
|
19344
|
+
pending = /* @__PURE__ */ new Map();
|
|
19345
|
+
eventListeners = /* @__PURE__ */ new Set();
|
|
19346
|
+
lineReader;
|
|
19347
|
+
nextId = 1;
|
|
19348
|
+
closed = false;
|
|
19349
|
+
constructor(opts) {
|
|
19350
|
+
this.input = opts.input;
|
|
19351
|
+
this.requestTimeoutMs = opts.requestTimeoutMs ?? 1e4;
|
|
19352
|
+
this.lineReader = createInterface({ input: opts.output });
|
|
19353
|
+
this.lineReader.on("line", (line) => this.handleLine(line));
|
|
19354
|
+
this.lineReader.on("close", () => this.rejectAll("Sidecar output closed."));
|
|
19355
|
+
}
|
|
19356
|
+
onEvent(listener) {
|
|
19357
|
+
this.eventListeners.add(listener);
|
|
19358
|
+
return () => {
|
|
19359
|
+
this.eventListeners.delete(listener);
|
|
19360
|
+
};
|
|
19361
|
+
}
|
|
19362
|
+
handshake(params) {
|
|
19363
|
+
return this.request(
|
|
19364
|
+
"recappi.handshake",
|
|
19365
|
+
sidecarHandshakeParamsSchema.parse(params),
|
|
19366
|
+
sidecarHandshakeResultSchema
|
|
19367
|
+
);
|
|
19368
|
+
}
|
|
19369
|
+
startRecording(params) {
|
|
19370
|
+
return this.request(
|
|
19371
|
+
"recappi.recording.start",
|
|
19372
|
+
sidecarRecordingStartParamsSchema.parse(params),
|
|
19373
|
+
sidecarRecordingStartResultSchema
|
|
19374
|
+
);
|
|
19375
|
+
}
|
|
19376
|
+
stopRecording(params) {
|
|
19377
|
+
return this.request(
|
|
19378
|
+
"recappi.recording.stop",
|
|
19379
|
+
sidecarSessionParamsSchema.parse(params),
|
|
19380
|
+
sidecarRecordingStopResultSchema
|
|
19381
|
+
);
|
|
19382
|
+
}
|
|
19383
|
+
cancelRecording(params) {
|
|
19384
|
+
return this.request(
|
|
19385
|
+
"recappi.recording.cancel",
|
|
19386
|
+
sidecarSessionParamsSchema.parse(params),
|
|
19387
|
+
sidecarRecordingStopResultSchema
|
|
19388
|
+
);
|
|
19389
|
+
}
|
|
19390
|
+
getRecordingStatus(params) {
|
|
19391
|
+
return this.request(
|
|
19392
|
+
"recappi.recording.status",
|
|
19393
|
+
sidecarSessionParamsSchema.parse(params),
|
|
19394
|
+
sidecarRecordingStatusResultSchema
|
|
19395
|
+
);
|
|
19396
|
+
}
|
|
19397
|
+
close() {
|
|
19398
|
+
if (this.closed) return;
|
|
19399
|
+
this.closed = true;
|
|
19400
|
+
this.lineReader.close();
|
|
19401
|
+
this.rejectAll("Sidecar client closed.");
|
|
19402
|
+
}
|
|
19403
|
+
request(method, params, resultSchema) {
|
|
19404
|
+
if (this.closed) {
|
|
19405
|
+
return Promise.reject(
|
|
19406
|
+
cliError("internal.unexpected", "Sidecar client is already closed.", {
|
|
19407
|
+
hint: "Start a new sidecar session and retry."
|
|
19408
|
+
})
|
|
19409
|
+
);
|
|
19410
|
+
}
|
|
19411
|
+
const id = this.nextId;
|
|
19412
|
+
this.nextId += 1;
|
|
19413
|
+
const payload = {
|
|
19414
|
+
jsonrpc: "2.0",
|
|
19415
|
+
id,
|
|
19416
|
+
method,
|
|
19417
|
+
params
|
|
19418
|
+
};
|
|
19419
|
+
return new Promise((resolve, reject) => {
|
|
19420
|
+
const timer = setTimeout(() => {
|
|
19421
|
+
this.pending.delete(id);
|
|
19422
|
+
reject(
|
|
19423
|
+
cliError("internal.unexpected", `Sidecar request timed out: ${method}.`, {
|
|
19424
|
+
retryable: true
|
|
19425
|
+
})
|
|
19426
|
+
);
|
|
19427
|
+
}, this.requestTimeoutMs);
|
|
19428
|
+
this.pending.set(id, {
|
|
19429
|
+
resolve: (value) => {
|
|
19430
|
+
try {
|
|
19431
|
+
const parsed = resultSchema.parse(value);
|
|
19432
|
+
resolve(parsed);
|
|
19433
|
+
} catch (error51) {
|
|
19434
|
+
reject(toCliError(error51));
|
|
19435
|
+
}
|
|
19436
|
+
},
|
|
19437
|
+
reject,
|
|
19438
|
+
timer
|
|
19439
|
+
});
|
|
19440
|
+
this.input.write(`${JSON.stringify(payload)}
|
|
19441
|
+
`, (error51) => {
|
|
19442
|
+
if (!error51) return;
|
|
19443
|
+
clearTimeout(timer);
|
|
19444
|
+
this.pending.delete(id);
|
|
19445
|
+
reject(cliError("internal.unexpected", `Could not write to sidecar: ${error51.message}`));
|
|
19446
|
+
});
|
|
19447
|
+
});
|
|
19448
|
+
}
|
|
19449
|
+
handleLine(line) {
|
|
19450
|
+
const trimmed = line.trim();
|
|
19451
|
+
if (!trimmed) return;
|
|
19452
|
+
let raw;
|
|
19453
|
+
try {
|
|
19454
|
+
raw = JSON.parse(trimmed);
|
|
19455
|
+
} catch {
|
|
19456
|
+
this.rejectAll("Sidecar wrote invalid JSON.");
|
|
19457
|
+
return;
|
|
19458
|
+
}
|
|
19459
|
+
const maybeNotification = sidecarNotificationSchema.safeParse(raw);
|
|
19460
|
+
if (maybeNotification.success) {
|
|
19461
|
+
const event = sidecarEventSchema.parse(maybeNotification.data.params);
|
|
19462
|
+
for (const listener of this.eventListeners) listener(event);
|
|
19463
|
+
return;
|
|
19464
|
+
}
|
|
19465
|
+
const response = sidecarResponseSchema.safeParse(raw);
|
|
19466
|
+
if (!response.success) {
|
|
19467
|
+
this.rejectAll("Sidecar wrote an invalid JSON-RPC message.");
|
|
19468
|
+
return;
|
|
19469
|
+
}
|
|
19470
|
+
const id = sidecarJsonRpcIdSchema.parse(response.data.id);
|
|
19471
|
+
const pending = this.pending.get(id);
|
|
19472
|
+
if (!pending) return;
|
|
19473
|
+
this.pending.delete(id);
|
|
19474
|
+
clearTimeout(pending.timer);
|
|
19475
|
+
if ("error" in response.data) {
|
|
19476
|
+
pending.reject(
|
|
19477
|
+
cliError("internal.unexpected", response.data.error.message, {
|
|
19478
|
+
data: response.data.error,
|
|
19479
|
+
retryable: response.data.error.code >= -32099 && response.data.error.code <= -32e3
|
|
19480
|
+
})
|
|
19481
|
+
);
|
|
19482
|
+
return;
|
|
19483
|
+
}
|
|
19484
|
+
pending.resolve(response.data.result);
|
|
19485
|
+
}
|
|
19486
|
+
rejectAll(message) {
|
|
19487
|
+
for (const [id, pending] of this.pending) {
|
|
19488
|
+
this.pending.delete(id);
|
|
19489
|
+
clearTimeout(pending.timer);
|
|
19490
|
+
pending.reject(cliError("internal.unexpected", message));
|
|
19491
|
+
}
|
|
19492
|
+
}
|
|
19493
|
+
};
|
|
19494
|
+
function spawnMiniSidecar(opts) {
|
|
19495
|
+
const spawnProcess = opts.spawnProcess ?? spawn2;
|
|
19496
|
+
const child = spawnProcess(opts.command, opts.args ?? [], {
|
|
19497
|
+
env: opts.env,
|
|
19498
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
19499
|
+
});
|
|
19500
|
+
const client = new MiniSidecarClient({
|
|
19501
|
+
input: child.stdin,
|
|
19502
|
+
output: child.stdout,
|
|
19503
|
+
requestTimeoutMs: opts.requestTimeoutMs
|
|
19504
|
+
});
|
|
19505
|
+
return {
|
|
19506
|
+
client,
|
|
19507
|
+
kill: () => {
|
|
19508
|
+
client.close();
|
|
19509
|
+
child.kill();
|
|
19510
|
+
}
|
|
19511
|
+
};
|
|
19512
|
+
}
|
|
19513
|
+
function defaultSidecarHandshakeParams(params) {
|
|
19514
|
+
return {
|
|
19515
|
+
protocolVersion: SIDECAR_PROTOCOL_VERSION,
|
|
19516
|
+
...params
|
|
19517
|
+
};
|
|
19518
|
+
}
|
|
19519
|
+
|
|
19520
|
+
// src/record.tsx
|
|
19521
|
+
init_LiveCaptionsScreen();
|
|
19522
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
19523
|
+
var SIDECAR_COMMAND_ENV = "RECAPPI_MINI_SIDECAR";
|
|
19524
|
+
async function recordViaSidecar(opts) {
|
|
19525
|
+
let liveRenderer;
|
|
19526
|
+
let session;
|
|
19527
|
+
try {
|
|
19528
|
+
session = await startRecordSession(opts);
|
|
19529
|
+
if (opts.renderLive) {
|
|
19530
|
+
liveRenderer = opts.runtime?.createLiveRenderer?.(session.source) ?? createInkLiveRenderer({
|
|
19531
|
+
source: session.source,
|
|
19532
|
+
renderApp: opts.runtime?.renderApp,
|
|
19533
|
+
now: opts.runtime?.now
|
|
19534
|
+
});
|
|
19535
|
+
}
|
|
19536
|
+
if (liveRenderer) {
|
|
19537
|
+
await liveRenderer.waitUntilStop();
|
|
19538
|
+
} else {
|
|
19539
|
+
await (opts.runtime?.waitForStop ?? waitForStopSignal)();
|
|
19540
|
+
}
|
|
19541
|
+
return await session.stop();
|
|
19542
|
+
} catch (error51) {
|
|
19543
|
+
if (session) {
|
|
19544
|
+
try {
|
|
19545
|
+
await session.cancel();
|
|
19546
|
+
} catch {
|
|
19547
|
+
}
|
|
19548
|
+
}
|
|
19549
|
+
throw error51;
|
|
19550
|
+
} finally {
|
|
19551
|
+
liveRenderer?.close();
|
|
19552
|
+
session?.close();
|
|
19553
|
+
}
|
|
19554
|
+
}
|
|
19555
|
+
async function startLiveRecordSession(opts) {
|
|
19556
|
+
const session = await startRecordSession({ ...opts, live: true });
|
|
19557
|
+
return {
|
|
19558
|
+
source: session.source,
|
|
19559
|
+
stop: async () => {
|
|
19560
|
+
await session.stop();
|
|
19561
|
+
}
|
|
19562
|
+
};
|
|
19563
|
+
}
|
|
19564
|
+
async function startRecordSession(opts) {
|
|
19565
|
+
const command = resolveSidecarCommand(opts);
|
|
19566
|
+
const sidecarArgs = opts.sidecarArgs ?? [];
|
|
19567
|
+
const spawnSidecar = opts.runtime?.spawnSidecar ?? spawnMiniSidecar;
|
|
19568
|
+
const sidecar = spawnSidecar({ command, args: sidecarArgs, env: opts.env });
|
|
19569
|
+
const account = requireAccountPartition(opts.account);
|
|
19570
|
+
const artifacts = [];
|
|
19571
|
+
let handshake;
|
|
19572
|
+
let sessionId;
|
|
19573
|
+
let latestState;
|
|
19574
|
+
let recordingId;
|
|
19575
|
+
let localSessionRef;
|
|
19576
|
+
let stopPromise;
|
|
19577
|
+
let closed = false;
|
|
19578
|
+
const unsubscribe = sidecar.client.onEvent((event) => {
|
|
19579
|
+
if (event.type === "recording.state") {
|
|
19580
|
+
latestState = event.state;
|
|
19581
|
+
if (event.recordingId) recordingId = event.recordingId;
|
|
19582
|
+
if (event.localSessionRef) localSessionRef = event.localSessionRef;
|
|
19583
|
+
}
|
|
19584
|
+
if (event.type === "local_artifact.upserted") {
|
|
19585
|
+
artifacts.push(event.artifact);
|
|
19586
|
+
}
|
|
19587
|
+
});
|
|
19588
|
+
const close = () => {
|
|
19589
|
+
if (closed) return;
|
|
19590
|
+
closed = true;
|
|
19591
|
+
unsubscribe();
|
|
19592
|
+
sidecar.kill();
|
|
19593
|
+
};
|
|
19594
|
+
const cancel = async () => {
|
|
19595
|
+
if (sessionId && latestState && latestState !== "completed" && latestState !== "cancelled") {
|
|
19596
|
+
try {
|
|
19597
|
+
await sidecar.client.cancelRecording({ sessionId });
|
|
19598
|
+
} catch {
|
|
19599
|
+
}
|
|
19600
|
+
}
|
|
19601
|
+
close();
|
|
19602
|
+
};
|
|
19603
|
+
try {
|
|
19604
|
+
handshake = await sidecar.client.handshake(
|
|
19605
|
+
defaultSidecarHandshakeParams({
|
|
19606
|
+
client: { name: "recappi-cli", version: opts.cliVersion },
|
|
19607
|
+
account: opts.account,
|
|
19608
|
+
capabilities: opts.live ? ["recording.capture", "recording.upload", "live_captions.stream"] : ["recording.capture", "recording.upload"]
|
|
19609
|
+
})
|
|
19610
|
+
);
|
|
19611
|
+
const started = await sidecar.client.startRecording({
|
|
19612
|
+
account,
|
|
19613
|
+
options: {
|
|
19614
|
+
includeSystemAudio: opts.includeSystemAudio ?? true,
|
|
19615
|
+
includeMicrophone: opts.includeMicrophone ?? true,
|
|
19616
|
+
liveCaptions: opts.live === true,
|
|
19617
|
+
...opts.translationLanguage ? { translationLanguage: opts.translationLanguage } : {},
|
|
19618
|
+
...opts.transcriptionLanguage ? { transcriptionLanguage: opts.transcriptionLanguage } : {},
|
|
19619
|
+
...opts.title ? { title: opts.title } : {}
|
|
19620
|
+
}
|
|
19621
|
+
});
|
|
19622
|
+
sessionId = started.sessionId;
|
|
19623
|
+
latestState = started.state;
|
|
19624
|
+
localSessionRef = started.localSessionRef;
|
|
19625
|
+
return {
|
|
19626
|
+
source: sidecar.client,
|
|
19627
|
+
stop: () => {
|
|
19628
|
+
stopPromise ??= (async () => {
|
|
19629
|
+
try {
|
|
19630
|
+
const stopped = await sidecar.client.stopRecording({ sessionId });
|
|
19631
|
+
latestState = stopped.state;
|
|
19632
|
+
recordingId = stopped.recordingId ?? recordingId;
|
|
19633
|
+
localSessionRef = stopped.localSessionRef ?? localSessionRef;
|
|
19634
|
+
artifacts.push(...stopped.artifacts ?? []);
|
|
19635
|
+
const uniqueArtifacts = dedupeArtifacts(artifacts);
|
|
19636
|
+
persistArtifacts(uniqueArtifacts, account, opts);
|
|
19637
|
+
return recordCommandDataSchema.parse({
|
|
19638
|
+
origin: account.backendOrigin,
|
|
19639
|
+
userId: account.userId,
|
|
19640
|
+
live: opts.live === true,
|
|
19641
|
+
sessionId: stopped.sessionId,
|
|
19642
|
+
state: stopped.state,
|
|
19643
|
+
...recordingId ? { recordingId } : {},
|
|
19644
|
+
...localSessionRef ? { localSessionRef } : {},
|
|
19645
|
+
...handshake?.sidecar ? { sidecar: handshake.sidecar } : {},
|
|
19646
|
+
artifacts: uniqueArtifacts
|
|
19647
|
+
});
|
|
19648
|
+
} finally {
|
|
19649
|
+
close();
|
|
19650
|
+
}
|
|
19651
|
+
})();
|
|
19652
|
+
return stopPromise;
|
|
19653
|
+
},
|
|
19654
|
+
cancel,
|
|
19655
|
+
close
|
|
19656
|
+
};
|
|
19657
|
+
} catch (error51) {
|
|
19658
|
+
await cancel();
|
|
19659
|
+
throw error51;
|
|
19660
|
+
}
|
|
19661
|
+
}
|
|
19662
|
+
function resolveSidecarCommand(opts) {
|
|
19663
|
+
const command = opts.sidecarCommand?.trim() || opts.env?.[SIDECAR_COMMAND_ENV]?.trim();
|
|
19664
|
+
if (!command) {
|
|
19665
|
+
throw cliError("usage.invalid_argument", "Missing Recappi Mini sidecar command.", {
|
|
19666
|
+
hint: `Pass --sidecar-command, or set ${SIDECAR_COMMAND_ENV} to the Mini sidecar executable.`
|
|
19667
|
+
});
|
|
19668
|
+
}
|
|
19669
|
+
return command;
|
|
19670
|
+
}
|
|
19671
|
+
function persistArtifacts(artifacts, account, opts) {
|
|
19672
|
+
if (artifacts.length === 0) return;
|
|
19673
|
+
const store = openCliStore({ homeDir: opts.homeDir, env: opts.env });
|
|
19674
|
+
try {
|
|
19675
|
+
for (const artifact of artifacts) {
|
|
19676
|
+
store.addLocalArtifact({
|
|
19677
|
+
kind: artifact.kind,
|
|
19678
|
+
account,
|
|
19679
|
+
localPath: artifact.localPath,
|
|
19680
|
+
remoteId: artifact.remoteId,
|
|
19681
|
+
metadata: artifact.metadata
|
|
19682
|
+
});
|
|
19683
|
+
}
|
|
19684
|
+
} finally {
|
|
19685
|
+
store.close();
|
|
19686
|
+
}
|
|
19687
|
+
}
|
|
19688
|
+
function dedupeArtifacts(artifacts) {
|
|
19689
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19690
|
+
const out = [];
|
|
19691
|
+
for (const artifact of artifacts) {
|
|
19692
|
+
const key = [artifact.kind, artifact.localPath, artifact.remoteId ?? ""].join("\0");
|
|
19693
|
+
if (seen.has(key)) continue;
|
|
19694
|
+
seen.add(key);
|
|
19695
|
+
out.push(artifact);
|
|
19696
|
+
}
|
|
19697
|
+
return out;
|
|
19698
|
+
}
|
|
19699
|
+
function waitForStopSignal() {
|
|
19700
|
+
return new Promise((resolve) => {
|
|
19701
|
+
const stop = () => {
|
|
19702
|
+
process.off("SIGINT", stop);
|
|
19703
|
+
process.off("SIGTERM", stop);
|
|
19704
|
+
resolve();
|
|
19705
|
+
};
|
|
19706
|
+
process.once("SIGINT", stop);
|
|
19707
|
+
process.once("SIGTERM", stop);
|
|
19708
|
+
});
|
|
19709
|
+
}
|
|
19710
|
+
function createInkLiveRenderer(opts) {
|
|
19711
|
+
let resolveStop;
|
|
19712
|
+
const stopped = new Promise((resolve) => {
|
|
19713
|
+
resolveStop = resolve;
|
|
19714
|
+
});
|
|
19715
|
+
const renderApp = opts.renderApp ?? render;
|
|
19716
|
+
const app = renderApp(
|
|
19717
|
+
/* @__PURE__ */ jsx3(
|
|
19718
|
+
RecordLiveScreen,
|
|
19719
|
+
{
|
|
19720
|
+
source: opts.source,
|
|
19721
|
+
onStop: () => resolveStop?.(),
|
|
19722
|
+
now: opts.now ?? Date.now
|
|
19723
|
+
}
|
|
19724
|
+
),
|
|
19725
|
+
{ alternateScreen: true, interactive: true }
|
|
19726
|
+
);
|
|
19727
|
+
return {
|
|
19728
|
+
waitUntilStop: () => stopped,
|
|
19729
|
+
close: () => app.unmount()
|
|
19730
|
+
};
|
|
19731
|
+
}
|
|
19732
|
+
function RecordLiveScreen({
|
|
19733
|
+
source,
|
|
19734
|
+
onStop,
|
|
19735
|
+
now
|
|
19736
|
+
}) {
|
|
19737
|
+
useInput2((input, key) => {
|
|
19738
|
+
if (input === "q" || key.escape || key.leftArrow) onStop();
|
|
19739
|
+
});
|
|
19740
|
+
return /* @__PURE__ */ jsx3(LiveCaptionsScreen, { source, now });
|
|
19741
|
+
}
|
|
19742
|
+
|
|
17980
19743
|
// src/cli.ts
|
|
17981
19744
|
var DASHBOARD_RECORDINGS_PAGE_SIZE = 50;
|
|
17982
19745
|
async function runCli(deps = {}) {
|
|
@@ -17992,7 +19755,7 @@ async function runCli(deps = {}) {
|
|
|
17992
19755
|
return 0;
|
|
17993
19756
|
}
|
|
17994
19757
|
const mode = parsed.options.mode ?? (isTTY ? "human" : "json");
|
|
17995
|
-
const
|
|
19758
|
+
const render3 = {
|
|
17996
19759
|
mode,
|
|
17997
19760
|
compact: parsed.options.compact,
|
|
17998
19761
|
fields: parsed.options.fields,
|
|
@@ -18001,11 +19764,11 @@ async function runCli(deps = {}) {
|
|
|
18001
19764
|
progress: createHumanProgressState(mode === "human" && isTTY)
|
|
18002
19765
|
};
|
|
18003
19766
|
if (parsed.kind === "schema") {
|
|
18004
|
-
renderSuccess("schema", parsed.document,
|
|
19767
|
+
renderSuccess("schema", parsed.document, render3);
|
|
18005
19768
|
return 0;
|
|
18006
19769
|
}
|
|
18007
19770
|
if (parsed.kind === "version") {
|
|
18008
|
-
renderSuccess("version", { version: CLI_VERSION },
|
|
19771
|
+
renderSuccess("version", { version: CLI_VERSION }, render3);
|
|
18009
19772
|
return 0;
|
|
18010
19773
|
}
|
|
18011
19774
|
const auth = await resolveAuthContext({
|
|
@@ -18016,23 +19779,58 @@ async function runCli(deps = {}) {
|
|
|
18016
19779
|
const client = new RecappiApiClient(auth, {
|
|
18017
19780
|
fetchImpl: deps.fetchImpl,
|
|
18018
19781
|
sleep: deps.sleep,
|
|
18019
|
-
env: deps.env
|
|
19782
|
+
env: deps.env,
|
|
19783
|
+
homeDir: deps.homeDir
|
|
18020
19784
|
});
|
|
18021
19785
|
if (parsed.kind === "dashboard") {
|
|
19786
|
+
const status = await client.authStatus();
|
|
19787
|
+
const account = status.loggedIn && status.userId ? { backendOrigin: auth.origin, userId: status.userId } : null;
|
|
19788
|
+
const recordingAudio = createRecordingAudioRuntime(client, {
|
|
19789
|
+
account,
|
|
19790
|
+
env: deps.env,
|
|
19791
|
+
homeDir: deps.homeDir
|
|
19792
|
+
});
|
|
18022
19793
|
const runDashboard2 = deps.runDashboard ?? (await Promise.resolve().then(() => (init_tui(), tui_exports))).runDashboard;
|
|
18023
19794
|
await runDashboard2({
|
|
18024
19795
|
fetchJobs: () => client.listJobs({ status: "active", limit: 20 }),
|
|
18025
19796
|
fetchRecordings: ({ cursor, limit = DASHBOARD_RECORDINGS_PAGE_SIZE } = {}) => client.listRecordings({ limit, cursor }),
|
|
18026
19797
|
fetchDashboardStats: () => client.dashboardStats(),
|
|
19798
|
+
fetchAccountStatus: () => client.accountStatus(),
|
|
18027
19799
|
fetchTranscript: (transcriptId) => client.getTranscript(transcriptId),
|
|
18028
|
-
recordingAudio
|
|
19800
|
+
recordingAudio,
|
|
19801
|
+
listDownloadedRecordingIds: () => recordingAudio.listDownloadedRecordingIds(),
|
|
19802
|
+
listDownloads: () => recordingAudio.listDownloads(),
|
|
19803
|
+
startLiveRecord: async () => {
|
|
19804
|
+
const liveStatus = await client.authStatus();
|
|
19805
|
+
if (!liveStatus.loggedIn || !liveStatus.userId) {
|
|
19806
|
+
throw cliError("auth.not_logged_in", "Sign in before starting a sidecar recording.", {
|
|
19807
|
+
hint: "Run recappi auth login, or import the Recappi Mini session with recappi auth import-macos."
|
|
19808
|
+
});
|
|
19809
|
+
}
|
|
19810
|
+
return startLiveRecordSession({
|
|
19811
|
+
account: {
|
|
19812
|
+
backendOrigin: auth.origin,
|
|
19813
|
+
userId: liveStatus.userId,
|
|
19814
|
+
...liveStatus.email ? { email: liveStatus.email } : {}
|
|
19815
|
+
},
|
|
19816
|
+
cliVersion: CLI_VERSION,
|
|
19817
|
+
env: deps.env,
|
|
19818
|
+
homeDir: deps.homeDir,
|
|
19819
|
+
runtime: deps.recordRuntime
|
|
19820
|
+
});
|
|
19821
|
+
},
|
|
18029
19822
|
initialView: parsed.initialView
|
|
18030
19823
|
});
|
|
18031
19824
|
return 0;
|
|
18032
19825
|
}
|
|
18033
19826
|
if (parsed.kind === "auth-status") {
|
|
18034
19827
|
const data = await client.authStatus();
|
|
18035
|
-
renderSuccess("auth status", data,
|
|
19828
|
+
renderSuccess("auth status", data, render3);
|
|
19829
|
+
return data.loggedIn ? 0 : 3;
|
|
19830
|
+
}
|
|
19831
|
+
if (parsed.kind === "account-status") {
|
|
19832
|
+
const data = await client.accountStatus();
|
|
19833
|
+
renderSuccess("account status", data, render3);
|
|
18036
19834
|
return data.loggedIn ? 0 : 3;
|
|
18037
19835
|
}
|
|
18038
19836
|
if (parsed.kind === "auth-login") {
|
|
@@ -18047,12 +19845,12 @@ async function runCli(deps = {}) {
|
|
|
18047
19845
|
sleep: deps.sleep
|
|
18048
19846
|
}
|
|
18049
19847
|
});
|
|
18050
|
-
renderSuccess("auth login", data,
|
|
19848
|
+
renderSuccess("auth login", data, render3);
|
|
18051
19849
|
return 0;
|
|
18052
19850
|
}
|
|
18053
19851
|
if (parsed.kind === "auth-logout") {
|
|
18054
|
-
const cleared = await clearAuthConfig(deps.homeDir ??
|
|
18055
|
-
renderSuccess("auth logout", { loggedIn: false, origin: auth.origin, cleared },
|
|
19852
|
+
const cleared = await clearAuthConfig(deps.homeDir ?? os5.homedir());
|
|
19853
|
+
renderSuccess("auth logout", { loggedIn: false, origin: auth.origin, cleared }, render3);
|
|
18056
19854
|
return 0;
|
|
18057
19855
|
}
|
|
18058
19856
|
if (parsed.kind === "auth-import-macos") {
|
|
@@ -18062,20 +19860,20 @@ async function runCli(deps = {}) {
|
|
|
18062
19860
|
hint: keychain.hint ?? "Run recappi auth login instead."
|
|
18063
19861
|
});
|
|
18064
19862
|
}
|
|
18065
|
-
await saveAuthConfig(deps.homeDir ??
|
|
19863
|
+
await saveAuthConfig(deps.homeDir ?? os5.homedir(), {
|
|
18066
19864
|
origin: auth.origin,
|
|
18067
19865
|
token: keychain.token
|
|
18068
19866
|
});
|
|
18069
19867
|
renderSuccess(
|
|
18070
19868
|
"auth import-macos",
|
|
18071
19869
|
{ imported: true, origin: auth.origin, source: "macos-keychain" },
|
|
18072
|
-
|
|
19870
|
+
render3
|
|
18073
19871
|
);
|
|
18074
19872
|
return 0;
|
|
18075
19873
|
}
|
|
18076
19874
|
if (parsed.kind === "doctor") {
|
|
18077
19875
|
const data = await client.doctor();
|
|
18078
|
-
renderSuccess("doctor", data,
|
|
19876
|
+
renderSuccess("doctor", data, render3);
|
|
18079
19877
|
if (data.status !== "error") return 0;
|
|
18080
19878
|
return data.checks.some((check2) => check2.name.startsWith("auth.")) ? 3 : 1;
|
|
18081
19879
|
}
|
|
@@ -18089,7 +19887,7 @@ async function runCli(deps = {}) {
|
|
|
18089
19887
|
provider: parsed.provider,
|
|
18090
19888
|
prompt: parsed.prompt,
|
|
18091
19889
|
force: parsed.force,
|
|
18092
|
-
onEvent: (event) => renderEvent(event,
|
|
19890
|
+
onEvent: (event) => renderEvent(event, render3)
|
|
18093
19891
|
});
|
|
18094
19892
|
if (data.failures.length > 0) {
|
|
18095
19893
|
const worst = data.failures.reduce((max, item) => Math.max(max, item.error.exitCode), 1);
|
|
@@ -18100,26 +19898,95 @@ async function runCli(deps = {}) {
|
|
|
18100
19898
|
message: `${data.failures.length} of ${data.totalCount} upload(s) failed.`,
|
|
18101
19899
|
hint: "Inspect data.failures[].error for per-file codes; retry only the failed files."
|
|
18102
19900
|
};
|
|
18103
|
-
renderFailure("upload", descriptor,
|
|
19901
|
+
renderFailure("upload", descriptor, render3, data);
|
|
18104
19902
|
return worst;
|
|
18105
19903
|
}
|
|
18106
|
-
renderSuccess("upload", data,
|
|
19904
|
+
renderSuccess("upload", data, render3);
|
|
19905
|
+
return 0;
|
|
19906
|
+
}
|
|
19907
|
+
if (parsed.kind === "record") {
|
|
19908
|
+
const status = await client.authStatus();
|
|
19909
|
+
if (!status.loggedIn || !status.userId) {
|
|
19910
|
+
throw cliError("auth.not_logged_in", "Sign in before starting a sidecar recording.", {
|
|
19911
|
+
hint: "Run recappi auth login, or import the Recappi Mini session with recappi auth import-macos."
|
|
19912
|
+
});
|
|
19913
|
+
}
|
|
19914
|
+
if (mode === "human" && isTTY && !parsed.live) {
|
|
19915
|
+
stderr("Recording\u2026 press Ctrl-C to stop.\n");
|
|
19916
|
+
}
|
|
19917
|
+
const data = await recordViaSidecar({
|
|
19918
|
+
account: {
|
|
19919
|
+
backendOrigin: auth.origin,
|
|
19920
|
+
userId: status.userId,
|
|
19921
|
+
...status.email ? { email: status.email } : {}
|
|
19922
|
+
},
|
|
19923
|
+
cliVersion: CLI_VERSION,
|
|
19924
|
+
env: deps.env,
|
|
19925
|
+
homeDir: deps.homeDir,
|
|
19926
|
+
title: parsed.title,
|
|
19927
|
+
live: parsed.live,
|
|
19928
|
+
includeSystemAudio: parsed.includeSystemAudio,
|
|
19929
|
+
includeMicrophone: parsed.includeMicrophone,
|
|
19930
|
+
translationLanguage: parsed.translationLanguage,
|
|
19931
|
+
transcriptionLanguage: parsed.transcriptionLanguage,
|
|
19932
|
+
sidecarCommand: parsed.sidecarCommand,
|
|
19933
|
+
renderLive: parsed.live === true && mode === "human" && isTTY,
|
|
19934
|
+
runtime: deps.recordRuntime
|
|
19935
|
+
});
|
|
19936
|
+
renderSuccess("record", data, render3);
|
|
19937
|
+
return 0;
|
|
19938
|
+
}
|
|
19939
|
+
if (parsed.kind === "audio") {
|
|
19940
|
+
const status = await client.authStatus();
|
|
19941
|
+
if (!status.loggedIn || !status.userId) {
|
|
19942
|
+
throw cliError("auth.not_logged_in", "Sign in before using local audio actions.", {
|
|
19943
|
+
hint: "Run recappi auth login, or import the Recappi Mini session with recappi auth import-macos."
|
|
19944
|
+
});
|
|
19945
|
+
}
|
|
19946
|
+
const recordingAudio = createRecordingAudioRuntime(client, {
|
|
19947
|
+
account: { backendOrigin: auth.origin, userId: status.userId },
|
|
19948
|
+
env: deps.env,
|
|
19949
|
+
homeDir: deps.homeDir
|
|
19950
|
+
});
|
|
19951
|
+
const download = await recordingAudio.downloadRecordingAudioFile(
|
|
19952
|
+
parsed.recordingId,
|
|
19953
|
+
parsed.outputDir ? { directory: parsed.outputDir } : void 0
|
|
19954
|
+
);
|
|
19955
|
+
if (parsed.action === "open") {
|
|
19956
|
+
await recordingAudio.openPath(download.localPath);
|
|
19957
|
+
} else if (parsed.action === "reveal") {
|
|
19958
|
+
await recordingAudio.revealInFinder(download.localPath);
|
|
19959
|
+
}
|
|
19960
|
+
renderSuccess(
|
|
19961
|
+
"audio",
|
|
19962
|
+
{
|
|
19963
|
+
origin: auth.origin,
|
|
19964
|
+
recordingId: parsed.recordingId,
|
|
19965
|
+
localPath: download.localPath,
|
|
19966
|
+
action: parsed.action,
|
|
19967
|
+
reused: download.reused,
|
|
19968
|
+
...download.artifactId !== void 0 ? { artifactId: download.artifactId } : {},
|
|
19969
|
+
...download.contentType ? { contentType: download.contentType } : {},
|
|
19970
|
+
...download.contentLength !== void 0 ? { contentLength: download.contentLength } : {}
|
|
19971
|
+
},
|
|
19972
|
+
render3
|
|
19973
|
+
);
|
|
18107
19974
|
return 0;
|
|
18108
19975
|
}
|
|
18109
19976
|
if (parsed.kind === "jobs-wait") {
|
|
18110
19977
|
const parsedOptions = parsed.options;
|
|
18111
19978
|
const data = await client.waitForJob(parsed.jobId, {
|
|
18112
19979
|
onEvent: (event) => renderEvent(event, {
|
|
18113
|
-
...
|
|
19980
|
+
...render3,
|
|
18114
19981
|
mode: parsedOptions.mode === "jsonl" ? "jsonl" : "human"
|
|
18115
19982
|
})
|
|
18116
19983
|
});
|
|
18117
|
-
renderSuccess("jobs wait", data,
|
|
19984
|
+
renderSuccess("jobs wait", data, render3);
|
|
18118
19985
|
return 0;
|
|
18119
19986
|
}
|
|
18120
19987
|
if (parsed.kind === "jobs-list") {
|
|
18121
19988
|
const data = await client.listJobs({ status: parsed.status, limit: parsed.limit });
|
|
18122
|
-
renderSuccess("jobs list", data,
|
|
19989
|
+
renderSuccess("jobs list", data, render3);
|
|
18123
19990
|
return 0;
|
|
18124
19991
|
}
|
|
18125
19992
|
if (parsed.kind === "recordings-list") {
|
|
@@ -18128,22 +19995,22 @@ async function runCli(deps = {}) {
|
|
|
18128
19995
|
cursor: parsed.cursor,
|
|
18129
19996
|
search: parsed.search
|
|
18130
19997
|
});
|
|
18131
|
-
renderSuccess("recordings list", data,
|
|
19998
|
+
renderSuccess("recordings list", data, render3);
|
|
18132
19999
|
return 0;
|
|
18133
20000
|
}
|
|
18134
20001
|
if (parsed.kind === "recordings-get") {
|
|
18135
20002
|
const data = await client.getRecording(parsed.recordingId);
|
|
18136
|
-
renderSuccess("recordings get", data,
|
|
20003
|
+
renderSuccess("recordings get", data, render3);
|
|
18137
20004
|
return 0;
|
|
18138
20005
|
}
|
|
18139
20006
|
if (parsed.kind === "dashboard-stats") {
|
|
18140
20007
|
const data = await client.dashboardStats();
|
|
18141
|
-
renderSuccess("dashboard stats", data,
|
|
20008
|
+
renderSuccess("dashboard stats", data, render3);
|
|
18142
20009
|
return 0;
|
|
18143
20010
|
}
|
|
18144
20011
|
if (parsed.kind === "transcript-get") {
|
|
18145
20012
|
const data = await client.getTranscript(parsed.transcriptId);
|
|
18146
|
-
renderSuccess("transcript get", data,
|
|
20013
|
+
renderSuccess("transcript get", data, render3);
|
|
18147
20014
|
return 0;
|
|
18148
20015
|
}
|
|
18149
20016
|
throw cliError("usage.invalid_argument", "Unknown command.");
|
|
@@ -18335,6 +20202,17 @@ Agent mode:
|
|
|
18335
20202
|
commandName: "doctor"
|
|
18336
20203
|
});
|
|
18337
20204
|
});
|
|
20205
|
+
const account = program.command("account").description("Account and quota commands");
|
|
20206
|
+
addCommonOptions(account);
|
|
20207
|
+
const accountStatus = account.command("status").description("Show account, quota, and local state");
|
|
20208
|
+
addCommonOptions(accountStatus);
|
|
20209
|
+
accountStatus.action((_options, command) => {
|
|
20210
|
+
onSelect({
|
|
20211
|
+
kind: "account-status",
|
|
20212
|
+
options: collectGlobalOptions(command),
|
|
20213
|
+
commandName: "account status"
|
|
20214
|
+
});
|
|
20215
|
+
});
|
|
18338
20216
|
const upload = program.command("upload <file-or-dir>").description("Upload an audio file or directory to Recappi Cloud").option("--title <title>", "recording title", parseStringOption("--title")).option("--transcribe", "start transcription after upload").option("--wait", "wait for the transcription job to reach a terminal state").option("--language <lang>", "transcription language hint", parseStringOption("--language")).option("--provider <name>", "transcription provider", parseStringOption("--provider")).option("--prompt <text>", "transcription prompt/context", parseStringOption("--prompt")).option("--force", "force upload if a conflict is retryable");
|
|
18339
20217
|
addCommonOptions(upload);
|
|
18340
20218
|
upload.action((inputPath, opts, command) => {
|
|
@@ -18352,6 +20230,55 @@ Agent mode:
|
|
|
18352
20230
|
...opts.force === true ? { force: true } : {}
|
|
18353
20231
|
});
|
|
18354
20232
|
});
|
|
20233
|
+
const record2 = program.command("record").description("Start a Recappi Mini sidecar recording").option("--title <title>", "recording title", parseStringOption("--title")).option("--live", "show live captions while recording").option("--no-system-audio", "record microphone only").option("--no-microphone", "record system audio only").option(
|
|
20234
|
+
"--translation-language <lang>",
|
|
20235
|
+
"live caption translation language",
|
|
20236
|
+
parseStringOption("--translation-language")
|
|
20237
|
+
).option(
|
|
20238
|
+
"--transcription-language <lang>",
|
|
20239
|
+
"recording/transcription language hint",
|
|
20240
|
+
parseStringOption("--transcription-language")
|
|
20241
|
+
).option(
|
|
20242
|
+
"--sidecar-command <path>",
|
|
20243
|
+
"Recappi Mini sidecar executable",
|
|
20244
|
+
parseStringOption("--sidecar-command")
|
|
20245
|
+
);
|
|
20246
|
+
addCommonOptions(record2);
|
|
20247
|
+
record2.action((opts, command) => {
|
|
20248
|
+
if (opts.systemAudio === false && opts.microphone === false) {
|
|
20249
|
+
throw cliError("usage.invalid_argument", "Choose at least one recording input.", {
|
|
20250
|
+
hint: "Use system audio, microphone, or both."
|
|
20251
|
+
});
|
|
20252
|
+
}
|
|
20253
|
+
onSelect({
|
|
20254
|
+
kind: "record",
|
|
20255
|
+
options: collectGlobalOptions(command),
|
|
20256
|
+
commandName: "record",
|
|
20257
|
+
...typeof opts.title === "string" ? { title: opts.title } : {},
|
|
20258
|
+
...opts.live === true ? { live: true } : {},
|
|
20259
|
+
...opts.systemAudio === false ? { includeSystemAudio: false } : {},
|
|
20260
|
+
...opts.microphone === false ? { includeMicrophone: false } : {},
|
|
20261
|
+
...typeof opts.translationLanguage === "string" ? { translationLanguage: opts.translationLanguage } : {},
|
|
20262
|
+
...typeof opts.transcriptionLanguage === "string" ? { transcriptionLanguage: opts.transcriptionLanguage } : {},
|
|
20263
|
+
...typeof opts.sidecarCommand === "string" ? { sidecarCommand: opts.sidecarCommand } : {}
|
|
20264
|
+
});
|
|
20265
|
+
});
|
|
20266
|
+
const audio = program.command("audio").description("Download or open a recording audio file").argument("<recording-id>", "recording id").option("--download", "download audio and print the local path").option("--open", "download if needed, then open the audio file").option("--reveal", "download if needed, then reveal the audio file in Finder").option(
|
|
20267
|
+
"--output-dir <dir>",
|
|
20268
|
+
"directory for downloaded audio",
|
|
20269
|
+
parseStringOption("--output-dir")
|
|
20270
|
+
);
|
|
20271
|
+
addCommonOptions(audio);
|
|
20272
|
+
audio.action((recordingId, opts, command) => {
|
|
20273
|
+
onSelect({
|
|
20274
|
+
kind: "audio",
|
|
20275
|
+
options: collectGlobalOptions(command),
|
|
20276
|
+
commandName: "audio",
|
|
20277
|
+
recordingId,
|
|
20278
|
+
action: audioAction(opts),
|
|
20279
|
+
...opts.outputDir ? { outputDir: opts.outputDir } : {}
|
|
20280
|
+
});
|
|
20281
|
+
});
|
|
18355
20282
|
const schema = program.command("schema").description("Print the machine-readable CLI contract (commands, error codes, JSON Schemas)");
|
|
18356
20283
|
addCommonOptions(schema);
|
|
18357
20284
|
schema.action((_options, command) => {
|
|
@@ -18448,6 +20375,17 @@ Agent mode:
|
|
|
18448
20375
|
function addCommonOptions(command) {
|
|
18449
20376
|
command.option("--json", "write one JSON envelope to stdout").option("--jsonl", "write JSONL operation events to stdout").option("--human", "write human-readable output").option("--fields <list>", "comma-separated data fields to keep", parseFieldsOption).option("--compact", "omit empty optional data and print compact JSON").option("--origin <url>", "Recappi Cloud origin", parseStringOption("--origin"));
|
|
18450
20377
|
}
|
|
20378
|
+
function audioAction(opts) {
|
|
20379
|
+
const selected = [
|
|
20380
|
+
opts.download ? "download" : null,
|
|
20381
|
+
opts.open ? "open" : null,
|
|
20382
|
+
opts.reveal ? "reveal" : null
|
|
20383
|
+
].filter((action) => action !== null);
|
|
20384
|
+
if (selected.length > 1) {
|
|
20385
|
+
throw cliError("usage.invalid_argument", "Choose only one of --download, --open, or --reveal.");
|
|
20386
|
+
}
|
|
20387
|
+
return selected[0] ?? "download";
|
|
20388
|
+
}
|
|
18451
20389
|
function parseStringOption(flag) {
|
|
18452
20390
|
return (value) => {
|
|
18453
20391
|
if (!value || value.startsWith("-")) {
|
|
@@ -18513,6 +20451,9 @@ var VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
|
18513
20451
|
"--language",
|
|
18514
20452
|
"--provider",
|
|
18515
20453
|
"--prompt",
|
|
20454
|
+
"--translation-language",
|
|
20455
|
+
"--transcription-language",
|
|
20456
|
+
"--sidecar-command",
|
|
18516
20457
|
"--status",
|
|
18517
20458
|
"--limit",
|
|
18518
20459
|
"--cursor",
|