recappi 0.1.74 → 0.1.76
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 +654 -55
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1252,7 +1252,7 @@ function RecordingDetailView({
|
|
|
1252
1252
|
scrollable ? " \xB7 \u2191\u2193 scroll" : "",
|
|
1253
1253
|
ready ? " \xB7 " : "",
|
|
1254
1254
|
`o open \xB7 d download \xB7 f finder`,
|
|
1255
|
-
" \xB7 T re-transcribe \xB7 s re-summarize",
|
|
1255
|
+
" \xB7 T re-transcribe \xB7 s re-summarize \xB7 e export",
|
|
1256
1256
|
item.activeTranscriptId ? " \xB7 t full" : "",
|
|
1257
1257
|
links.webUrl ? " \xB7 w web" : "",
|
|
1258
1258
|
" \xB7 r refresh \xB7 esc back"
|
|
@@ -2005,6 +2005,9 @@ function AppShell({
|
|
|
2005
2005
|
transcribeRecordingArtifact,
|
|
2006
2006
|
onRetranscribe,
|
|
2007
2007
|
onResummarize,
|
|
2008
|
+
onSyncRecordingText,
|
|
2009
|
+
onSyncRecordingAudio,
|
|
2010
|
+
onExportRecording,
|
|
2008
2011
|
initialView = "overview",
|
|
2009
2012
|
openUrl: openUrl2,
|
|
2010
2013
|
copyText: copyText2,
|
|
@@ -2458,6 +2461,54 @@ function AppShell({
|
|
|
2458
2461
|
},
|
|
2459
2462
|
[onResummarize, refresh, recordings, now, refetchTranscript]
|
|
2460
2463
|
);
|
|
2464
|
+
const syncedTextRef = useRef3(/* @__PURE__ */ new Set());
|
|
2465
|
+
const syncRecordingText2 = useCallback(
|
|
2466
|
+
async (recordingId, opts = {}) => {
|
|
2467
|
+
if (!onSyncRecordingText) return;
|
|
2468
|
+
if (opts.manual) setNotice("Syncing text locally\u2026");
|
|
2469
|
+
try {
|
|
2470
|
+
const data = await onSyncRecordingText(recordingId);
|
|
2471
|
+
syncedTextRef.current.add(recordingId);
|
|
2472
|
+
if (opts.manual) setNotice(`Text synced \xB7 ${data.sessionDir}`);
|
|
2473
|
+
} catch (error51) {
|
|
2474
|
+
if (opts.manual) setNotice(transcribeHandoffErrorCopy(error51));
|
|
2475
|
+
}
|
|
2476
|
+
},
|
|
2477
|
+
[onSyncRecordingText]
|
|
2478
|
+
);
|
|
2479
|
+
const syncRecordingAudio2 = useCallback(
|
|
2480
|
+
async (recordingId) => {
|
|
2481
|
+
if (!onSyncRecordingAudio) {
|
|
2482
|
+
setNotice("Audio sync is not available in this CLI session.");
|
|
2483
|
+
return;
|
|
2484
|
+
}
|
|
2485
|
+
setNotice("Downloading audio\u2026");
|
|
2486
|
+
try {
|
|
2487
|
+
const data = await onSyncRecordingAudio(recordingId);
|
|
2488
|
+
setNotice(`Audio saved \xB7 ${data.audioPath}`);
|
|
2489
|
+
} catch (error51) {
|
|
2490
|
+
setNotice(transcribeHandoffErrorCopy(error51));
|
|
2491
|
+
}
|
|
2492
|
+
},
|
|
2493
|
+
[onSyncRecordingAudio]
|
|
2494
|
+
);
|
|
2495
|
+
const exportRecordingForAgent = useCallback(
|
|
2496
|
+
async (recordingId) => {
|
|
2497
|
+
if (!onExportRecording) {
|
|
2498
|
+
setNotice("Export is not available in this CLI session.");
|
|
2499
|
+
return;
|
|
2500
|
+
}
|
|
2501
|
+
setNotice("Exporting\u2026");
|
|
2502
|
+
try {
|
|
2503
|
+
const data = await onExportRecording(recordingId);
|
|
2504
|
+
copyText2?.(data.textPath);
|
|
2505
|
+
setNotice(`Exported \xB7 ${data.textPath} (path copied)`);
|
|
2506
|
+
} catch (error51) {
|
|
2507
|
+
setNotice(transcribeHandoffErrorCopy(error51));
|
|
2508
|
+
}
|
|
2509
|
+
},
|
|
2510
|
+
[onExportRecording, copyText2]
|
|
2511
|
+
);
|
|
2461
2512
|
useEffect4(() => {
|
|
2462
2513
|
if (liveRecord?.kind !== "stopped") return;
|
|
2463
2514
|
const artifact = liveRecord.artifact;
|
|
@@ -2602,6 +2653,11 @@ function AppShell({
|
|
|
2602
2653
|
cancelled = true;
|
|
2603
2654
|
};
|
|
2604
2655
|
}, [detailTranscriptId, fetchTranscript]);
|
|
2656
|
+
const detailRecordingId = screen.kind === "recordingDetail" ? screen.recordingId : void 0;
|
|
2657
|
+
useEffect4(() => {
|
|
2658
|
+
if (!detailRecordingId || syncedTextRef.current.has(detailRecordingId)) return;
|
|
2659
|
+
void syncRecordingText2(detailRecordingId);
|
|
2660
|
+
}, [detailRecordingId, syncRecordingText2]);
|
|
2605
2661
|
const setAudio = (recordingId, action) => setAudioCache((m) => new Map(m).set(recordingId, action));
|
|
2606
2662
|
const runAudio = useCallback(
|
|
2607
2663
|
async (recordingId, mode) => {
|
|
@@ -2708,6 +2764,7 @@ function AppShell({
|
|
|
2708
2764
|
if (screen.kind === "recordingDetail") {
|
|
2709
2765
|
const detailRec = recordings.find((r) => r.recordingId === screen.recordingId);
|
|
2710
2766
|
if (detailRec?.activeTranscriptId) refetchTranscript(detailRec.activeTranscriptId);
|
|
2767
|
+
void syncRecordingText2(screen.recordingId, { manual: true });
|
|
2711
2768
|
}
|
|
2712
2769
|
return;
|
|
2713
2770
|
}
|
|
@@ -2750,10 +2807,13 @@ function AppShell({
|
|
|
2750
2807
|
const links = rec ? resolveRecordingLinks(rec.recordingId, rec.origin) : {};
|
|
2751
2808
|
if (input === "T" && rec) void retranscribeExistingRecording(rec.recordingId);
|
|
2752
2809
|
else if ((input === "s" || input === "S") && rec) void resummarizeExistingRecording(rec.recordingId);
|
|
2810
|
+
else if (input === "e" && rec) void exportRecordingForAgent(rec.recordingId);
|
|
2753
2811
|
else if (input === "t" && rec?.activeTranscriptId) void openTranscript(rec.activeTranscriptId);
|
|
2754
2812
|
else if (input === "o" && rec) void runAudio(rec.recordingId, "open");
|
|
2755
|
-
else if (input === "d" && rec)
|
|
2756
|
-
|
|
2813
|
+
else if (input === "d" && rec) {
|
|
2814
|
+
if (onSyncRecordingAudio) void syncRecordingAudio2(rec.recordingId);
|
|
2815
|
+
else void runAudio(rec.recordingId, "download");
|
|
2816
|
+
} else if (input === "f" && rec) void runAudio(rec.recordingId, "finder");
|
|
2757
2817
|
else if (input === "w" && links.webUrl) openUrl2?.(links.webUrl);
|
|
2758
2818
|
else if (input === "c" && links.webUrl) {
|
|
2759
2819
|
copyText2?.(links.webUrl);
|
|
@@ -3001,6 +3061,9 @@ async function runDashboard(deps) {
|
|
|
3001
3061
|
transcribeRecordingArtifact: deps.transcribeRecordingArtifact,
|
|
3002
3062
|
onRetranscribe: deps.retranscribeRecording,
|
|
3003
3063
|
onResummarize: deps.resummarizeRecording,
|
|
3064
|
+
onSyncRecordingText: deps.syncRecordingText,
|
|
3065
|
+
onSyncRecordingAudio: deps.syncRecordingAudio,
|
|
3066
|
+
onExportRecording: deps.exportRecording,
|
|
3004
3067
|
initialView: deps.initialView ?? "overview",
|
|
3005
3068
|
openUrl,
|
|
3006
3069
|
copyText
|
|
@@ -3028,7 +3091,7 @@ var init_tui = __esm({
|
|
|
3028
3091
|
|
|
3029
3092
|
// src/cli.ts
|
|
3030
3093
|
import { Command, CommanderError, InvalidArgumentError } from "commander/esm.mjs";
|
|
3031
|
-
import
|
|
3094
|
+
import os6 from "os";
|
|
3032
3095
|
|
|
3033
3096
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
3034
3097
|
var external_exports = {};
|
|
@@ -3796,10 +3859,10 @@ function mergeDefs(...defs) {
|
|
|
3796
3859
|
function cloneDef(schema) {
|
|
3797
3860
|
return mergeDefs(schema._zod.def);
|
|
3798
3861
|
}
|
|
3799
|
-
function getElementAtPath(obj,
|
|
3800
|
-
if (!
|
|
3862
|
+
function getElementAtPath(obj, path7) {
|
|
3863
|
+
if (!path7)
|
|
3801
3864
|
return obj;
|
|
3802
|
-
return
|
|
3865
|
+
return path7.reduce((acc, key) => acc?.[key], obj);
|
|
3803
3866
|
}
|
|
3804
3867
|
function promiseAllObject(promisesObj) {
|
|
3805
3868
|
const keys = Object.keys(promisesObj);
|
|
@@ -4208,11 +4271,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
4208
4271
|
}
|
|
4209
4272
|
return false;
|
|
4210
4273
|
}
|
|
4211
|
-
function prefixIssues(
|
|
4274
|
+
function prefixIssues(path7, issues) {
|
|
4212
4275
|
return issues.map((iss) => {
|
|
4213
4276
|
var _a3;
|
|
4214
4277
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
4215
|
-
iss.path.unshift(
|
|
4278
|
+
iss.path.unshift(path7);
|
|
4216
4279
|
return iss;
|
|
4217
4280
|
});
|
|
4218
4281
|
}
|
|
@@ -4359,16 +4422,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
|
|
|
4359
4422
|
}
|
|
4360
4423
|
function formatError(error51, mapper = (issue2) => issue2.message) {
|
|
4361
4424
|
const fieldErrors = { _errors: [] };
|
|
4362
|
-
const processError = (error52,
|
|
4425
|
+
const processError = (error52, path7 = []) => {
|
|
4363
4426
|
for (const issue2 of error52.issues) {
|
|
4364
4427
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
4365
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
4428
|
+
issue2.errors.map((issues) => processError({ issues }, [...path7, ...issue2.path]));
|
|
4366
4429
|
} else if (issue2.code === "invalid_key") {
|
|
4367
|
-
processError({ issues: issue2.issues }, [...
|
|
4430
|
+
processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
|
|
4368
4431
|
} else if (issue2.code === "invalid_element") {
|
|
4369
|
-
processError({ issues: issue2.issues }, [...
|
|
4432
|
+
processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
|
|
4370
4433
|
} else {
|
|
4371
|
-
const fullpath = [...
|
|
4434
|
+
const fullpath = [...path7, ...issue2.path];
|
|
4372
4435
|
if (fullpath.length === 0) {
|
|
4373
4436
|
fieldErrors._errors.push(mapper(issue2));
|
|
4374
4437
|
} else {
|
|
@@ -4395,17 +4458,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
|
|
|
4395
4458
|
}
|
|
4396
4459
|
function treeifyError(error51, mapper = (issue2) => issue2.message) {
|
|
4397
4460
|
const result = { errors: [] };
|
|
4398
|
-
const processError = (error52,
|
|
4461
|
+
const processError = (error52, path7 = []) => {
|
|
4399
4462
|
var _a3, _b;
|
|
4400
4463
|
for (const issue2 of error52.issues) {
|
|
4401
4464
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
4402
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
4465
|
+
issue2.errors.map((issues) => processError({ issues }, [...path7, ...issue2.path]));
|
|
4403
4466
|
} else if (issue2.code === "invalid_key") {
|
|
4404
|
-
processError({ issues: issue2.issues }, [...
|
|
4467
|
+
processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
|
|
4405
4468
|
} else if (issue2.code === "invalid_element") {
|
|
4406
|
-
processError({ issues: issue2.issues }, [...
|
|
4469
|
+
processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
|
|
4407
4470
|
} else {
|
|
4408
|
-
const fullpath = [...
|
|
4471
|
+
const fullpath = [...path7, ...issue2.path];
|
|
4409
4472
|
if (fullpath.length === 0) {
|
|
4410
4473
|
result.errors.push(mapper(issue2));
|
|
4411
4474
|
continue;
|
|
@@ -4437,8 +4500,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
|
|
|
4437
4500
|
}
|
|
4438
4501
|
function toDotPath(_path) {
|
|
4439
4502
|
const segs = [];
|
|
4440
|
-
const
|
|
4441
|
-
for (const seg of
|
|
4503
|
+
const path7 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
4504
|
+
for (const seg of path7) {
|
|
4442
4505
|
if (typeof seg === "number")
|
|
4443
4506
|
segs.push(`[${seg}]`);
|
|
4444
4507
|
else if (typeof seg === "symbol")
|
|
@@ -17130,13 +17193,13 @@ function resolveRef(ref, ctx) {
|
|
|
17130
17193
|
if (!ref.startsWith("#")) {
|
|
17131
17194
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
17132
17195
|
}
|
|
17133
|
-
const
|
|
17134
|
-
if (
|
|
17196
|
+
const path7 = ref.slice(1).split("/").filter(Boolean);
|
|
17197
|
+
if (path7.length === 0) {
|
|
17135
17198
|
return ctx.rootSchema;
|
|
17136
17199
|
}
|
|
17137
17200
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
17138
|
-
if (
|
|
17139
|
-
const key =
|
|
17201
|
+
if (path7[0] === defsKey) {
|
|
17202
|
+
const key = path7[1];
|
|
17140
17203
|
if (!key || !ctx.defs[key]) {
|
|
17141
17204
|
throw new Error(`Reference not found: ${ref}`);
|
|
17142
17205
|
}
|
|
@@ -18139,6 +18202,31 @@ var transcriptDataSchema = external_exports.object({
|
|
|
18139
18202
|
segments: external_exports.array(transcriptSegmentSchema),
|
|
18140
18203
|
summary: transcriptSummarySchema
|
|
18141
18204
|
});
|
|
18205
|
+
var recordingExportDataSchema = external_exports.object({
|
|
18206
|
+
origin: external_exports.string(),
|
|
18207
|
+
recordingId: external_exports.string(),
|
|
18208
|
+
exportDir: external_exports.string(),
|
|
18209
|
+
textPath: external_exports.string(),
|
|
18210
|
+
manifestPath: external_exports.string(),
|
|
18211
|
+
remoteManifestPath: external_exports.string(),
|
|
18212
|
+
sessionMetadataPath: external_exports.string(),
|
|
18213
|
+
recordingJsonPath: external_exports.string(),
|
|
18214
|
+
subscriptionPath: external_exports.string(),
|
|
18215
|
+
subscriptionJsonPath: external_exports.string(),
|
|
18216
|
+
audioPath: external_exports.string(),
|
|
18217
|
+
transcriptId: external_exports.string().nullable().optional(),
|
|
18218
|
+
transcriptPath: external_exports.string().optional(),
|
|
18219
|
+
transcriptJsonPath: external_exports.string().optional(),
|
|
18220
|
+
summaryPath: external_exports.string().optional(),
|
|
18221
|
+
summaryJsonPath: external_exports.string().optional(),
|
|
18222
|
+
actionItemsPath: external_exports.string().optional(),
|
|
18223
|
+
summaryStatus: summaryStatusSchema.optional(),
|
|
18224
|
+
audio: external_exports.object({
|
|
18225
|
+
contentType: external_exports.string().optional(),
|
|
18226
|
+
contentLength: external_exports.number().int().nonnegative().optional(),
|
|
18227
|
+
reused: external_exports.boolean().optional()
|
|
18228
|
+
}).optional()
|
|
18229
|
+
});
|
|
18142
18230
|
var doctorCheckStatusSchema = external_exports.enum(["ok", "warn", "error"]);
|
|
18143
18231
|
var doctorCheckSchema = external_exports.object({
|
|
18144
18232
|
name: external_exports.string(),
|
|
@@ -19401,7 +19489,10 @@ var RecappiApiClient = class {
|
|
|
19401
19489
|
const contentLength = numberHeader(response.headers.get("content-length"));
|
|
19402
19490
|
const dir = opts.directory ?? await fs3.mkdtemp(path4.join(os4.tmpdir(), "recappi-cli-audio-"));
|
|
19403
19491
|
if (opts.directory) await fs3.mkdir(dir, { recursive: true });
|
|
19404
|
-
const filePath = path4.join(
|
|
19492
|
+
const filePath = path4.join(
|
|
19493
|
+
dir,
|
|
19494
|
+
recordingAudioFileName(recordingId, opts.title, contentType, opts.filenameStem)
|
|
19495
|
+
);
|
|
19405
19496
|
try {
|
|
19406
19497
|
await pipeline(
|
|
19407
19498
|
Readable.fromWeb(response.body),
|
|
@@ -19700,12 +19791,12 @@ var RecappiApiClient = class {
|
|
|
19700
19791
|
} : {}
|
|
19701
19792
|
};
|
|
19702
19793
|
}
|
|
19703
|
-
async getJson(
|
|
19704
|
-
const response = await this.request("GET",
|
|
19794
|
+
async getJson(path7) {
|
|
19795
|
+
const response = await this.request("GET", path7);
|
|
19705
19796
|
return await parseJson(response);
|
|
19706
19797
|
}
|
|
19707
|
-
async postJson(
|
|
19708
|
-
const response = await this.request("POST",
|
|
19798
|
+
async postJson(path7, body) {
|
|
19799
|
+
const response = await this.request("POST", path7, JSON.stringify(body), {
|
|
19709
19800
|
headers: { "content-type": "application/json" }
|
|
19710
19801
|
});
|
|
19711
19802
|
return await parseJson(response);
|
|
@@ -19799,7 +19890,10 @@ function audioExtensionForContentType(contentType) {
|
|
|
19799
19890
|
return "wav";
|
|
19800
19891
|
}
|
|
19801
19892
|
}
|
|
19802
|
-
function recordingAudioFileName(recordingId, title, contentType) {
|
|
19893
|
+
function recordingAudioFileName(recordingId, title, contentType, filenameStem) {
|
|
19894
|
+
if (filenameStem && filenameStem.trim()) {
|
|
19895
|
+
return `${truncateFileStem(safeFileStem(filenameStem), 96)}.${audioExtensionForContentType(contentType)}`;
|
|
19896
|
+
}
|
|
19803
19897
|
const idStem = truncateFileStem(safeFileStem(recordingId), 48);
|
|
19804
19898
|
const titleStem = title ? truncateFileStem(safeFileStem(title), 80) : "";
|
|
19805
19899
|
const stem = titleStem ? `${titleStem}-${idStem}` : idStem;
|
|
@@ -20093,7 +20187,7 @@ import { promises as fs4 } from "fs";
|
|
|
20093
20187
|
import path5 from "path";
|
|
20094
20188
|
function createRecordingAudioRuntime(client, deps = {}) {
|
|
20095
20189
|
const downloadRecordingAudioFile = async (recordingId, opts) => {
|
|
20096
|
-
const cached2 = await findReusableDownload(recordingId, deps);
|
|
20190
|
+
const cached2 = opts?.directory ? null : await findReusableDownload(recordingId, deps);
|
|
20097
20191
|
if (cached2) return cached2;
|
|
20098
20192
|
const directory = opts?.directory ?? (deps.account ? defaultDownloadDirectory(deps) : void 0);
|
|
20099
20193
|
const download = await client.downloadRecordingAudio(recordingId, {
|
|
@@ -20249,6 +20343,7 @@ var COMMON_TASKS = [
|
|
|
20249
20343
|
{ label: "Transcribe a local file", command: "recappi upload <file> --transcribe --wait" },
|
|
20250
20344
|
{ label: "Re-transcribe a recording", command: "recappi recordings retranscribe <recordingId> --wait" },
|
|
20251
20345
|
{ label: "Re-summarize a recording", command: "recappi recordings resummarize <recordingId>" },
|
|
20346
|
+
{ label: "Export recording bundle", command: "recappi recordings export <recordingId> --dir ./bundle" },
|
|
20252
20347
|
{ label: "List / find recordings", command: "recappi recordings list" },
|
|
20253
20348
|
{ label: "Read a transcript", command: "recappi transcript get <transcriptId>" },
|
|
20254
20349
|
{ label: "Download / open audio", command: "recappi audio <recordingId> --open" },
|
|
@@ -20355,7 +20450,24 @@ var COMMAND_METADATA = {
|
|
|
20355
20450
|
"recordings get": {
|
|
20356
20451
|
capabilities: ["Fetch one recording's metadata and status by id"],
|
|
20357
20452
|
examples: [{ description: "Fetch one recording", command: "recappi recordings get <recordingId>" }],
|
|
20358
|
-
relatedCommands: ["recordings list", "transcript get", "audio"]
|
|
20453
|
+
relatedCommands: ["recordings list", "recordings export", "transcript get", "audio"]
|
|
20454
|
+
},
|
|
20455
|
+
"recordings export": {
|
|
20456
|
+
capabilities: [
|
|
20457
|
+
"Write a plain-text Markdown handoff file for agents",
|
|
20458
|
+
"Export raw audio, transcript, summary, action items, subscription/account data, and JSON sidecars"
|
|
20459
|
+
],
|
|
20460
|
+
examples: [
|
|
20461
|
+
{
|
|
20462
|
+
description: "Export a recording bundle into a chosen directory",
|
|
20463
|
+
command: "recappi recordings export <recordingId> --dir ./recappi-export"
|
|
20464
|
+
},
|
|
20465
|
+
{
|
|
20466
|
+
description: "Export and return machine-readable paths",
|
|
20467
|
+
command: "recappi recordings export <recordingId> --json --compact"
|
|
20468
|
+
}
|
|
20469
|
+
],
|
|
20470
|
+
relatedCommands: ["recordings get", "transcript get", "audio", "account status"]
|
|
20359
20471
|
},
|
|
20360
20472
|
"recordings list": {
|
|
20361
20473
|
capabilities: ["List recent recordings", "Search recordings and transcripts", "Find a recordingId"],
|
|
@@ -20363,7 +20475,7 @@ var COMMAND_METADATA = {
|
|
|
20363
20475
|
{ description: "List recent recordings", command: "recappi recordings list" },
|
|
20364
20476
|
{ description: "Search recordings and transcripts", command: "recappi recordings list --search <query>" }
|
|
20365
20477
|
],
|
|
20366
|
-
relatedCommands: ["recordings get", "audio", "transcript get"]
|
|
20478
|
+
relatedCommands: ["recordings get", "recordings export", "audio", "transcript get"]
|
|
20367
20479
|
},
|
|
20368
20480
|
"recordings retranscribe": {
|
|
20369
20481
|
capabilities: ["Re-transcribe an existing recording", "Re-transcribe with new language/prompt/scene/model"],
|
|
@@ -20440,6 +20552,408 @@ ${lines.join("\n")}
|
|
|
20440
20552
|
` : "";
|
|
20441
20553
|
}
|
|
20442
20554
|
|
|
20555
|
+
// src/export.ts
|
|
20556
|
+
import { promises as fs5 } from "fs";
|
|
20557
|
+
import os5 from "os";
|
|
20558
|
+
import path6 from "path";
|
|
20559
|
+
async function syncRecordingText(opts) {
|
|
20560
|
+
const context = await loadRecordingBundleContext(opts);
|
|
20561
|
+
const sessionDir = await resolveRecordingSessionDir(context.recording, opts);
|
|
20562
|
+
return writeRecordingTextFiles(context, sessionDir, {
|
|
20563
|
+
now: opts.now
|
|
20564
|
+
});
|
|
20565
|
+
}
|
|
20566
|
+
async function syncRecordingAudio(opts) {
|
|
20567
|
+
const context = await loadRecordingBundleContext(opts);
|
|
20568
|
+
const sessionDir = await resolveRecordingSessionDir(context.recording, opts);
|
|
20569
|
+
const audio = await downloadRecordingAudioToDir(context.recording, opts.recordingAudio, sessionDir);
|
|
20570
|
+
const text = await writeRecordingTextFiles(context, sessionDir, {
|
|
20571
|
+
now: opts.now,
|
|
20572
|
+
uploadFilename: path6.basename(audio.localPath)
|
|
20573
|
+
});
|
|
20574
|
+
return { ...text, audioPath: audio.localPath, audio: audioMetadata(audio) };
|
|
20575
|
+
}
|
|
20576
|
+
async function exportRecording(opts) {
|
|
20577
|
+
const context = await loadRecordingBundleContext(opts);
|
|
20578
|
+
const exportDir = await resolveRecordingSessionDir(context.recording, opts);
|
|
20579
|
+
const audio = await downloadRecordingAudioToDir(context.recording, opts.recordingAudio, exportDir);
|
|
20580
|
+
const textFiles = await writeRecordingTextFiles(context, exportDir, {
|
|
20581
|
+
now: opts.now,
|
|
20582
|
+
uploadFilename: path6.basename(audio.localPath)
|
|
20583
|
+
});
|
|
20584
|
+
const { recording, subscription, transcript } = context;
|
|
20585
|
+
const subscriptionPath = path6.join(exportDir, "subscription.md");
|
|
20586
|
+
const subscriptionJsonPath = path6.join(exportDir, "subscription.json");
|
|
20587
|
+
await fs5.writeFile(subscriptionPath, renderSubscriptionMarkdown(subscription), "utf8");
|
|
20588
|
+
await writeJson(subscriptionJsonPath, subscription);
|
|
20589
|
+
const textPath = path6.join(exportDir, "handoff.md");
|
|
20590
|
+
const manifestPath = path6.join(exportDir, "manifest.json");
|
|
20591
|
+
const data = recordingExportDataSchema.parse({
|
|
20592
|
+
origin: recording.origin,
|
|
20593
|
+
recordingId: recording.recordingId,
|
|
20594
|
+
exportDir,
|
|
20595
|
+
textPath,
|
|
20596
|
+
manifestPath,
|
|
20597
|
+
remoteManifestPath: textFiles.remoteManifestPath,
|
|
20598
|
+
sessionMetadataPath: textFiles.sessionMetadataPath,
|
|
20599
|
+
recordingJsonPath: textFiles.recordingJsonPath,
|
|
20600
|
+
subscriptionPath,
|
|
20601
|
+
subscriptionJsonPath,
|
|
20602
|
+
audioPath: audio.localPath,
|
|
20603
|
+
...textFiles.transcriptId !== void 0 ? { transcriptId: textFiles.transcriptId } : {},
|
|
20604
|
+
...textFiles.transcriptPath ? { transcriptPath: textFiles.transcriptPath } : {},
|
|
20605
|
+
...textFiles.transcriptJsonPath ? { transcriptJsonPath: textFiles.transcriptJsonPath } : {},
|
|
20606
|
+
...textFiles.summaryPath ? { summaryPath: textFiles.summaryPath } : {},
|
|
20607
|
+
...textFiles.summaryJsonPath ? { summaryJsonPath: textFiles.summaryJsonPath } : {},
|
|
20608
|
+
...textFiles.actionItemsPath ? { actionItemsPath: textFiles.actionItemsPath } : {},
|
|
20609
|
+
...textFiles.summaryStatus ? { summaryStatus: textFiles.summaryStatus } : {},
|
|
20610
|
+
audio: audioMetadata(audio)
|
|
20611
|
+
});
|
|
20612
|
+
await fs5.writeFile(
|
|
20613
|
+
textPath,
|
|
20614
|
+
renderHandoffMarkdown(recording, subscription, audio, data, transcript),
|
|
20615
|
+
"utf8"
|
|
20616
|
+
);
|
|
20617
|
+
await writeJson(manifestPath, {
|
|
20618
|
+
exportedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
20619
|
+
command: "recordings export",
|
|
20620
|
+
data
|
|
20621
|
+
});
|
|
20622
|
+
return data;
|
|
20623
|
+
}
|
|
20624
|
+
async function loadRecordingBundleContext(opts) {
|
|
20625
|
+
const recording = await opts.client.getRecording(opts.recordingId);
|
|
20626
|
+
const subscription = await opts.client.accountStatus();
|
|
20627
|
+
const transcript = recording.activeTranscriptId ? await opts.client.getTranscript(recording.activeTranscriptId) : void 0;
|
|
20628
|
+
return {
|
|
20629
|
+
recording,
|
|
20630
|
+
subscription,
|
|
20631
|
+
...transcript ? { transcript } : {},
|
|
20632
|
+
transcriptId: transcript?.transcriptId ?? recording.activeTranscriptId ?? void 0
|
|
20633
|
+
};
|
|
20634
|
+
}
|
|
20635
|
+
async function downloadRecordingAudioToDir(recording, recordingAudio, directory) {
|
|
20636
|
+
return recordingAudio.downloadRecordingAudioFile(recording.recordingId, {
|
|
20637
|
+
directory,
|
|
20638
|
+
filenameStem: "recording",
|
|
20639
|
+
title: recording.title ?? recording.summaryTitle ?? recording.recordingId
|
|
20640
|
+
});
|
|
20641
|
+
}
|
|
20642
|
+
async function writeRecordingTextFiles(context, sessionDir, opts) {
|
|
20643
|
+
const { recording, transcript, subscription } = context;
|
|
20644
|
+
await fs5.mkdir(sessionDir, { recursive: true });
|
|
20645
|
+
const recordingJsonPath = path6.join(sessionDir, "recording.json");
|
|
20646
|
+
const sessionMetadataPath = path6.join(sessionDir, "session-metadata.json");
|
|
20647
|
+
const remoteManifestPath = path6.join(sessionDir, "remote-session.json");
|
|
20648
|
+
const existingManifest = await readJsonRecord(remoteManifestPath);
|
|
20649
|
+
const uploadFilename = opts.uploadFilename ?? (typeof existingManifest?.uploadFilename === "string" ? existingManifest.uploadFilename : void 0);
|
|
20650
|
+
await writeJson(recordingJsonPath, recording);
|
|
20651
|
+
await writeJson(sessionMetadataPath, renderSessionMetadata(recording));
|
|
20652
|
+
await writeJson(
|
|
20653
|
+
remoteManifestPath,
|
|
20654
|
+
renderRemoteSessionManifest(recording, uploadFilename, transcript, subscription, opts.now)
|
|
20655
|
+
);
|
|
20656
|
+
let transcriptPath;
|
|
20657
|
+
let transcriptJsonPath;
|
|
20658
|
+
let summaryPath;
|
|
20659
|
+
let summaryJsonPath;
|
|
20660
|
+
let actionItemsPath;
|
|
20661
|
+
let summaryStatus;
|
|
20662
|
+
if (transcript) {
|
|
20663
|
+
transcriptPath = path6.join(sessionDir, "transcript.md");
|
|
20664
|
+
transcriptJsonPath = path6.join(sessionDir, "transcript.json");
|
|
20665
|
+
summaryPath = path6.join(sessionDir, "summary.md");
|
|
20666
|
+
summaryJsonPath = path6.join(sessionDir, "summary.json");
|
|
20667
|
+
actionItemsPath = path6.join(sessionDir, "action-items.md");
|
|
20668
|
+
summaryStatus = transcript.summary.status;
|
|
20669
|
+
await fs5.writeFile(transcriptPath, renderTranscriptMarkdown(transcript), "utf8");
|
|
20670
|
+
await writeJson(transcriptJsonPath, transcript);
|
|
20671
|
+
await writeJson(summaryJsonPath, transcript.summary);
|
|
20672
|
+
await fs5.writeFile(summaryPath, renderSummaryMarkdown(recording, transcript), "utf8");
|
|
20673
|
+
await writeOptionalText(actionItemsPath, renderActionItemsMarkdown(transcript.summary));
|
|
20674
|
+
}
|
|
20675
|
+
return {
|
|
20676
|
+
recordingId: recording.recordingId,
|
|
20677
|
+
sessionDir,
|
|
20678
|
+
remoteManifestPath,
|
|
20679
|
+
sessionMetadataPath,
|
|
20680
|
+
recordingJsonPath,
|
|
20681
|
+
...context.transcriptId !== void 0 ? { transcriptId: context.transcriptId } : {},
|
|
20682
|
+
...transcriptPath ? { transcriptPath } : {},
|
|
20683
|
+
...transcriptJsonPath ? { transcriptJsonPath } : {},
|
|
20684
|
+
...summaryPath ? { summaryPath } : {},
|
|
20685
|
+
...summaryJsonPath ? { summaryJsonPath } : {},
|
|
20686
|
+
...actionItemsPath ? { actionItemsPath } : {},
|
|
20687
|
+
...summaryStatus ? { summaryStatus } : {}
|
|
20688
|
+
};
|
|
20689
|
+
}
|
|
20690
|
+
async function resolveRecordingSessionDir(recording, opts) {
|
|
20691
|
+
if (opts.directory) return path6.resolve(opts.directory);
|
|
20692
|
+
const existing = await findExistingRecordingSessionDir(recording.recordingId, opts.homeDir, opts.env);
|
|
20693
|
+
if (existing) return existing;
|
|
20694
|
+
return createRecordingSessionDir(recording, opts.homeDir, opts.env);
|
|
20695
|
+
}
|
|
20696
|
+
async function findExistingRecordingSessionDir(recordingId, homeDir, env) {
|
|
20697
|
+
const base = recordingSessionBaseDirectory(homeDir, env);
|
|
20698
|
+
let entries;
|
|
20699
|
+
try {
|
|
20700
|
+
entries = await fs5.readdir(base, { withFileTypes: true });
|
|
20701
|
+
} catch {
|
|
20702
|
+
return void 0;
|
|
20703
|
+
}
|
|
20704
|
+
for (const entry of entries) {
|
|
20705
|
+
if (!entry.isDirectory()) continue;
|
|
20706
|
+
const dir = path6.join(base, entry.name);
|
|
20707
|
+
const manifest = await readJsonRecord(path6.join(dir, "remote-session.json"));
|
|
20708
|
+
if (manifest?.recordingId === recordingId) return dir;
|
|
20709
|
+
}
|
|
20710
|
+
return void 0;
|
|
20711
|
+
}
|
|
20712
|
+
async function createRecordingSessionDir(recording, homeDir, env) {
|
|
20713
|
+
const base = recordingSessionBaseDirectory(homeDir, env);
|
|
20714
|
+
await fs5.mkdir(base, { recursive: true });
|
|
20715
|
+
const stem = formatSessionDirectoryDate(new Date(recording.createdAt));
|
|
20716
|
+
let candidate = path6.join(base, stem);
|
|
20717
|
+
let suffix = 2;
|
|
20718
|
+
while (await pathExists(candidate)) {
|
|
20719
|
+
candidate = path6.join(base, `${stem}-cloud-${suffix}`);
|
|
20720
|
+
suffix += 1;
|
|
20721
|
+
}
|
|
20722
|
+
await fs5.mkdir(candidate, { recursive: true });
|
|
20723
|
+
return candidate;
|
|
20724
|
+
}
|
|
20725
|
+
function recordingSessionBaseDirectory(homeDir = os5.homedir(), env = process.env) {
|
|
20726
|
+
const explicit = env.RECAPPI_LOCAL_SESSIONS_DIR?.trim();
|
|
20727
|
+
if (explicit) return explicit;
|
|
20728
|
+
return path6.join(homeDir, "Documents", "Recappi Mini");
|
|
20729
|
+
}
|
|
20730
|
+
async function readJsonRecord(filePath) {
|
|
20731
|
+
try {
|
|
20732
|
+
const parsed = JSON.parse(await fs5.readFile(filePath, "utf8"));
|
|
20733
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : void 0;
|
|
20734
|
+
} catch {
|
|
20735
|
+
return void 0;
|
|
20736
|
+
}
|
|
20737
|
+
}
|
|
20738
|
+
async function pathExists(filePath) {
|
|
20739
|
+
try {
|
|
20740
|
+
await fs5.access(filePath);
|
|
20741
|
+
return true;
|
|
20742
|
+
} catch {
|
|
20743
|
+
return false;
|
|
20744
|
+
}
|
|
20745
|
+
}
|
|
20746
|
+
function formatSessionDirectoryDate(date5) {
|
|
20747
|
+
const year = date5.getFullYear();
|
|
20748
|
+
const month = String(date5.getMonth() + 1).padStart(2, "0");
|
|
20749
|
+
const day = String(date5.getDate()).padStart(2, "0");
|
|
20750
|
+
const hour = String(date5.getHours()).padStart(2, "0");
|
|
20751
|
+
const minute = String(date5.getMinutes()).padStart(2, "0");
|
|
20752
|
+
const second = String(date5.getSeconds()).padStart(2, "0");
|
|
20753
|
+
return `${year}-${month}-${day}_${hour}${minute}${second}`;
|
|
20754
|
+
}
|
|
20755
|
+
async function writeJson(filePath, value) {
|
|
20756
|
+
await fs5.writeFile(filePath, `${JSON.stringify(value, null, 2)}
|
|
20757
|
+
`, "utf8");
|
|
20758
|
+
}
|
|
20759
|
+
async function writeOptionalText(filePath, value) {
|
|
20760
|
+
if (value && value.trim()) {
|
|
20761
|
+
await fs5.writeFile(filePath, value, "utf8");
|
|
20762
|
+
return;
|
|
20763
|
+
}
|
|
20764
|
+
await fs5.rm(filePath, { force: true });
|
|
20765
|
+
}
|
|
20766
|
+
function renderHandoffMarkdown(recording, subscription, audio, data, transcript) {
|
|
20767
|
+
const lines = [];
|
|
20768
|
+
lines.push(`# ${recording.title ?? recording.summaryTitle ?? "Recappi Recording"}`);
|
|
20769
|
+
lines.push("");
|
|
20770
|
+
lines.push("## Files", "");
|
|
20771
|
+
lines.push(`- Audio: ${audio.localPath}`);
|
|
20772
|
+
lines.push(`- Subscription: ${data.subscriptionPath}`);
|
|
20773
|
+
if (data.summaryPath) lines.push(`- Summary: ${data.summaryPath}`);
|
|
20774
|
+
if (data.transcriptPath) lines.push(`- Transcript: ${data.transcriptPath}`);
|
|
20775
|
+
if (data.actionItemsPath) lines.push(`- Action items: ${data.actionItemsPath}`);
|
|
20776
|
+
lines.push(`- Remote session manifest: ${data.remoteManifestPath}`);
|
|
20777
|
+
lines.push(`- Session metadata: ${data.sessionMetadataPath}`);
|
|
20778
|
+
lines.push(`- Manifest: ${data.manifestPath}`);
|
|
20779
|
+
lines.push("");
|
|
20780
|
+
lines.push("## Recording", "");
|
|
20781
|
+
lines.push(`- recordingId: ${recording.recordingId}`);
|
|
20782
|
+
lines.push(`- status: ${recording.status}`);
|
|
20783
|
+
if (recording.durationMs !== void 0 && recording.durationMs !== null) {
|
|
20784
|
+
lines.push(`- duration: ${formatTimestamp(recording.durationMs)}`);
|
|
20785
|
+
}
|
|
20786
|
+
if (recording.sizeBytes !== void 0 && recording.sizeBytes !== null) {
|
|
20787
|
+
lines.push(`- sizeBytes: ${recording.sizeBytes}`);
|
|
20788
|
+
}
|
|
20789
|
+
if (transcript) {
|
|
20790
|
+
lines.push(`- transcriptId: ${transcript.transcriptId}`);
|
|
20791
|
+
lines.push(`- summaryStatus: ${transcript.summary.status}`);
|
|
20792
|
+
} else {
|
|
20793
|
+
lines.push("- transcript: not available");
|
|
20794
|
+
}
|
|
20795
|
+
lines.push("");
|
|
20796
|
+
lines.push("## Subscription", "");
|
|
20797
|
+
appendSubscriptionLines(lines, subscription);
|
|
20798
|
+
lines.push("");
|
|
20799
|
+
if (transcript) {
|
|
20800
|
+
lines.push(renderSummaryMarkdown(recording, transcript).trimEnd());
|
|
20801
|
+
lines.push("");
|
|
20802
|
+
lines.push(renderTranscriptMarkdown(transcript).trimEnd());
|
|
20803
|
+
lines.push("");
|
|
20804
|
+
}
|
|
20805
|
+
return `${lines.join("\n").trimEnd()}
|
|
20806
|
+
`;
|
|
20807
|
+
}
|
|
20808
|
+
function renderSubscriptionMarkdown(subscription) {
|
|
20809
|
+
const lines = ["# Subscription", ""];
|
|
20810
|
+
appendSubscriptionLines(lines, subscription);
|
|
20811
|
+
return `${lines.join("\n").trimEnd()}
|
|
20812
|
+
`;
|
|
20813
|
+
}
|
|
20814
|
+
function renderSessionMetadata(recording) {
|
|
20815
|
+
const sourceTitle = recording.title ?? recording.summaryTitle ?? "Recappi Cloud";
|
|
20816
|
+
return stripUndefined({
|
|
20817
|
+
summaryTitle: recording.summaryTitle ?? recording.title ?? void 0,
|
|
20818
|
+
sourceTitle,
|
|
20819
|
+
sourceAppName: void 0,
|
|
20820
|
+
sourceBundleID: void 0,
|
|
20821
|
+
startedAt: new Date(recording.createdAt).toISOString(),
|
|
20822
|
+
sceneTemplate: void 0,
|
|
20823
|
+
extraPrompt: void 0,
|
|
20824
|
+
includesMicrophoneAudio: void 0
|
|
20825
|
+
});
|
|
20826
|
+
}
|
|
20827
|
+
function renderRemoteSessionManifest(recording, uploadFilename, transcript, subscription, now) {
|
|
20828
|
+
return stripUndefined({
|
|
20829
|
+
recordingId: recording.recordingId,
|
|
20830
|
+
jobId: transcript?.jobId,
|
|
20831
|
+
transcriptId: transcript?.transcriptId ?? recording.activeTranscriptId ?? void 0,
|
|
20832
|
+
stage: transcript ? "done" : "synced",
|
|
20833
|
+
errorMessage: void 0,
|
|
20834
|
+
uploadFilename,
|
|
20835
|
+
provider: transcript?.provider,
|
|
20836
|
+
model: transcript?.model,
|
|
20837
|
+
updatedAt: (now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
20838
|
+
accountUserId: subscription.userId,
|
|
20839
|
+
accountBackendOrigin: subscription.origin
|
|
20840
|
+
});
|
|
20841
|
+
}
|
|
20842
|
+
function stripUndefined(value) {
|
|
20843
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
|
|
20844
|
+
}
|
|
20845
|
+
function appendSubscriptionLines(lines, subscription) {
|
|
20846
|
+
lines.push(`- origin: ${subscription.origin}`);
|
|
20847
|
+
lines.push(`- loggedIn: ${subscription.loggedIn}`);
|
|
20848
|
+
if (subscription.email) lines.push(`- email: ${subscription.email}`);
|
|
20849
|
+
if (subscription.userId) lines.push(`- userId: ${subscription.userId}`);
|
|
20850
|
+
if (subscription.billing) {
|
|
20851
|
+
lines.push(`- plan: ${subscription.billing.tier}`);
|
|
20852
|
+
lines.push(`- minutesUsed: ${subscription.billing.minutesUsed}`);
|
|
20853
|
+
lines.push(`- minutesCap: ${subscription.billing.minutesCap ?? "unlimited"}`);
|
|
20854
|
+
lines.push(`- batchMinutesUsed: ${subscription.billing.batchMinutesUsed}`);
|
|
20855
|
+
lines.push(`- realtimeMinutesUsed: ${subscription.billing.realtimeMinutesUsed}`);
|
|
20856
|
+
lines.push(`- storageBytes: ${subscription.billing.storageBytes}`);
|
|
20857
|
+
lines.push(`- storageCapBytes: ${subscription.billing.storageCapBytes ?? "unlimited"}`);
|
|
20858
|
+
lines.push(`- periodStart: ${subscription.billing.periodStart}`);
|
|
20859
|
+
lines.push(`- periodEnd: ${subscription.billing.periodEnd}`);
|
|
20860
|
+
}
|
|
20861
|
+
}
|
|
20862
|
+
function renderTranscriptMarkdown(transcript) {
|
|
20863
|
+
return `# Transcript
|
|
20864
|
+
|
|
20865
|
+
${renderTranscriptLines(transcript)}
|
|
20866
|
+
`;
|
|
20867
|
+
}
|
|
20868
|
+
function renderTranscriptLines(transcript) {
|
|
20869
|
+
const lines = transcript.segments.map((segment) => {
|
|
20870
|
+
const speaker = segment.speaker ? `${segment.speaker}: ` : "";
|
|
20871
|
+
return `[${formatTimestamp(segment.startMs)}] ${speaker}${segment.text}`;
|
|
20872
|
+
});
|
|
20873
|
+
if (lines.length > 0) return lines.join("\n");
|
|
20874
|
+
return transcript.text;
|
|
20875
|
+
}
|
|
20876
|
+
function renderSummaryMarkdown(recording, transcript) {
|
|
20877
|
+
const summary = transcript.summary;
|
|
20878
|
+
const lines = [];
|
|
20879
|
+
lines.push(`# ${summary.title ?? recording.title ?? recording.summaryTitle ?? "Summary"}`);
|
|
20880
|
+
lines.push("");
|
|
20881
|
+
lines.push(`- recordingId: ${recording.recordingId}`);
|
|
20882
|
+
lines.push(`- transcriptId: ${transcript.transcriptId}`);
|
|
20883
|
+
lines.push(`- summaryStatus: ${summary.status}`);
|
|
20884
|
+
if (summary.error) lines.push(`- error: ${summary.error}`);
|
|
20885
|
+
lines.push("");
|
|
20886
|
+
if (summary.tldr) {
|
|
20887
|
+
lines.push("## TL;DR", "");
|
|
20888
|
+
lines.push(summary.tldr, "");
|
|
20889
|
+
}
|
|
20890
|
+
appendStringList(lines, "Key Points", summary.keyPoints);
|
|
20891
|
+
appendStringList(lines, "Topics", summary.topics);
|
|
20892
|
+
appendStringList(lines, "Decisions", summary.decisions);
|
|
20893
|
+
appendActionItems(lines, summary.actionItems);
|
|
20894
|
+
appendTimeline(lines, summary.timeline);
|
|
20895
|
+
appendQuotes(lines, summary.quotes);
|
|
20896
|
+
if (lines[lines.length - 1] !== "") lines.push("");
|
|
20897
|
+
return lines.join("\n");
|
|
20898
|
+
}
|
|
20899
|
+
function renderActionItemsMarkdown(summary) {
|
|
20900
|
+
if (!summary.actionItems || summary.actionItems.length === 0) return null;
|
|
20901
|
+
const lines = ["# Action Items", ""];
|
|
20902
|
+
for (const item of summary.actionItems) {
|
|
20903
|
+
lines.push(`- ${item.who ? `${item.who} - ` : ""}${item.what}`);
|
|
20904
|
+
}
|
|
20905
|
+
lines.push("");
|
|
20906
|
+
return lines.join("\n");
|
|
20907
|
+
}
|
|
20908
|
+
function appendStringList(lines, title, values) {
|
|
20909
|
+
if (!values || values.length === 0) return;
|
|
20910
|
+
lines.push(`## ${title}`, "");
|
|
20911
|
+
for (const value of values) lines.push(`- ${value}`);
|
|
20912
|
+
lines.push("");
|
|
20913
|
+
}
|
|
20914
|
+
function appendActionItems(lines, values) {
|
|
20915
|
+
if (!values || values.length === 0) return;
|
|
20916
|
+
lines.push("## Action Items", "");
|
|
20917
|
+
for (const item of values) {
|
|
20918
|
+
lines.push(`- ${item.who ? `${item.who}: ` : ""}${item.what}`);
|
|
20919
|
+
}
|
|
20920
|
+
lines.push("");
|
|
20921
|
+
}
|
|
20922
|
+
function appendTimeline(lines, values) {
|
|
20923
|
+
if (!values || values.length === 0) return;
|
|
20924
|
+
lines.push("## Timeline", "");
|
|
20925
|
+
for (const item of values) {
|
|
20926
|
+
lines.push(
|
|
20927
|
+
`- ${formatTimestamp(item.startMs)}-${formatTimestamp(item.endMs)}: ${item.title} - ${item.summary}`
|
|
20928
|
+
);
|
|
20929
|
+
}
|
|
20930
|
+
lines.push("");
|
|
20931
|
+
}
|
|
20932
|
+
function appendQuotes(lines, values) {
|
|
20933
|
+
if (!values || values.length === 0) return;
|
|
20934
|
+
lines.push("## Quotes", "");
|
|
20935
|
+
for (const item of values) {
|
|
20936
|
+
lines.push(`- ${item.speaker ? `${item.speaker}: ` : ""}"${item.text}"`);
|
|
20937
|
+
}
|
|
20938
|
+
lines.push("");
|
|
20939
|
+
}
|
|
20940
|
+
function audioMetadata(audio) {
|
|
20941
|
+
return {
|
|
20942
|
+
...audio.contentType ? { contentType: audio.contentType } : {},
|
|
20943
|
+
...audio.contentLength !== void 0 ? { contentLength: audio.contentLength } : {},
|
|
20944
|
+
reused: audio.reused
|
|
20945
|
+
};
|
|
20946
|
+
}
|
|
20947
|
+
function formatTimestamp(ms) {
|
|
20948
|
+
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
20949
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
20950
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
20951
|
+
const seconds = totalSeconds % 60;
|
|
20952
|
+
const mm = String(minutes).padStart(2, "0");
|
|
20953
|
+
const ss = String(seconds).padStart(2, "0");
|
|
20954
|
+
return hours > 0 ? `${hours}:${mm}:${ss}` : `${mm}:${ss}`;
|
|
20955
|
+
}
|
|
20956
|
+
|
|
20443
20957
|
// src/progressStepper.ts
|
|
20444
20958
|
var STEP_DEFS = [
|
|
20445
20959
|
{ key: "check", label: "Check" },
|
|
@@ -20934,6 +21448,28 @@ Next:
|
|
|
20934
21448
|
}
|
|
20935
21449
|
return;
|
|
20936
21450
|
}
|
|
21451
|
+
if (command === "recordings export" && isRecord4(data)) {
|
|
21452
|
+
opts.stdout("Recording export ready\n");
|
|
21453
|
+
printStringField(opts, data, "recordingId");
|
|
21454
|
+
printStringField(opts, data, "exportDir");
|
|
21455
|
+
printStringField(opts, data, "textPath");
|
|
21456
|
+
printStringField(opts, data, "audioPath");
|
|
21457
|
+
printStringField(opts, data, "transcriptPath");
|
|
21458
|
+
printStringField(opts, data, "summaryPath");
|
|
21459
|
+
printStringField(opts, data, "actionItemsPath");
|
|
21460
|
+
printStringField(opts, data, "subscriptionPath");
|
|
21461
|
+
printStringField(opts, data, "transcriptJsonPath");
|
|
21462
|
+
printStringField(opts, data, "summaryJsonPath");
|
|
21463
|
+
printStringField(opts, data, "subscriptionJsonPath");
|
|
21464
|
+
printStringField(opts, data, "recordingJsonPath");
|
|
21465
|
+
printStringField(opts, data, "remoteManifestPath");
|
|
21466
|
+
printStringField(opts, data, "sessionMetadataPath");
|
|
21467
|
+
printStringField(opts, data, "manifestPath");
|
|
21468
|
+
if (typeof data.transcriptPath !== "string") {
|
|
21469
|
+
opts.stdout(" transcript: no active transcript\n");
|
|
21470
|
+
}
|
|
21471
|
+
return;
|
|
21472
|
+
}
|
|
20937
21473
|
if (command === "recordings retranscribe" && isRecord4(data)) {
|
|
20938
21474
|
opts.stdout("Transcription started\n");
|
|
20939
21475
|
if (typeof data.recordingId === "string") opts.stdout(` recordingId: ${data.recordingId}
|
|
@@ -21311,6 +21847,10 @@ function recordingTitle(item) {
|
|
|
21311
21847
|
function numberText(value) {
|
|
21312
21848
|
return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US") : "0";
|
|
21313
21849
|
}
|
|
21850
|
+
function printStringField(opts, data, field) {
|
|
21851
|
+
if (typeof data[field] === "string") opts.stdout(` ${field}: ${data[field]}
|
|
21852
|
+
`);
|
|
21853
|
+
}
|
|
21314
21854
|
function formatDurationMs(ms) {
|
|
21315
21855
|
return formatClock(ms / 1e3);
|
|
21316
21856
|
}
|
|
@@ -21427,6 +21967,7 @@ var COMMAND_DATA_SCHEMAS = {
|
|
|
21427
21967
|
upload: uploadBatchDataSchema,
|
|
21428
21968
|
record: recordCommandDataSchema,
|
|
21429
21969
|
"recordings get": recordingDataSchema,
|
|
21970
|
+
"recordings export": recordingExportDataSchema,
|
|
21430
21971
|
"recordings list": recordingListDataSchema,
|
|
21431
21972
|
"recordings retranscribe": recordingTranscribeDataSchema,
|
|
21432
21973
|
"recordings resummarize": recordingSummarizeDataSchema,
|
|
@@ -21465,11 +22006,11 @@ function buildSchemaDocument(program) {
|
|
|
21465
22006
|
event: toJsonSchema(operationEventSchema)
|
|
21466
22007
|
};
|
|
21467
22008
|
}
|
|
21468
|
-
function walkCommands(command,
|
|
22009
|
+
function walkCommands(command, path7, out) {
|
|
21469
22010
|
for (const sub of subcommandsOf(command)) {
|
|
21470
22011
|
const name = sub.name();
|
|
21471
22012
|
if (name === "help") continue;
|
|
21472
|
-
const fullPath = [...
|
|
22013
|
+
const fullPath = [...path7, name];
|
|
21473
22014
|
const children = subcommandsOf(sub).filter((child) => child.name() !== "help");
|
|
21474
22015
|
if (children.length === 0) {
|
|
21475
22016
|
out.push(leafCommandDoc(sub, fullPath.join(" ")));
|
|
@@ -21871,8 +22412,8 @@ function requestLaunchServicesSidecarShutdown(input) {
|
|
|
21871
22412
|
} catch {
|
|
21872
22413
|
}
|
|
21873
22414
|
}
|
|
21874
|
-
function createFifo(
|
|
21875
|
-
const result = spawnSync("mkfifo", [
|
|
22415
|
+
function createFifo(path7) {
|
|
22416
|
+
const result = spawnSync("mkfifo", [path7], { encoding: "utf8" });
|
|
21876
22417
|
if (result.status !== 0) {
|
|
21877
22418
|
throw cliError("record.helper_unavailable", "Recappi recording helper could not start.", {
|
|
21878
22419
|
hint: result.stderr || "Could not create the local recorder pipes. Try again."
|
|
@@ -22496,9 +23037,9 @@ function resolveSidecarCommand(opts) {
|
|
|
22496
23037
|
hint: `No bundled helper is registered for ${platform}. Set ${SIDECAR_COMMAND_ENV} to a compatible helper when one is available.`
|
|
22497
23038
|
});
|
|
22498
23039
|
}
|
|
22499
|
-
function ensureBundledHelperExecutable(
|
|
22500
|
-
if (process.platform === "darwin" &&
|
|
22501
|
-
const stableApp = ensureStableDarwinHelperApp(
|
|
23040
|
+
function ensureBundledHelperExecutable(path7, opts = {}) {
|
|
23041
|
+
if (process.platform === "darwin" && path7.endsWith(".app")) {
|
|
23042
|
+
const stableApp = ensureStableDarwinHelperApp(path7, opts);
|
|
22502
23043
|
const executable = darwinAppExecutablePath(stableApp);
|
|
22503
23044
|
if (!existsSync(executable)) {
|
|
22504
23045
|
throw cliError("record.helper_unavailable", "Recappi recording helper is not available.", {
|
|
@@ -22508,19 +23049,19 @@ function ensureBundledHelperExecutable(path6, opts = {}) {
|
|
|
22508
23049
|
ensureExecutableMode(executable);
|
|
22509
23050
|
return stableApp;
|
|
22510
23051
|
}
|
|
22511
|
-
if (process.platform === "win32") return
|
|
22512
|
-
ensureExecutableMode(
|
|
22513
|
-
return
|
|
23052
|
+
if (process.platform === "win32") return path7;
|
|
23053
|
+
ensureExecutableMode(path7);
|
|
23054
|
+
return path7;
|
|
22514
23055
|
}
|
|
22515
|
-
function ensureExecutableMode(
|
|
22516
|
-
const mode = statSync(
|
|
23056
|
+
function ensureExecutableMode(path7) {
|
|
23057
|
+
const mode = statSync(path7).mode;
|
|
22517
23058
|
if ((mode & 73) !== 0) return;
|
|
22518
23059
|
try {
|
|
22519
|
-
chmodSync(
|
|
23060
|
+
chmodSync(path7, mode | 493);
|
|
22520
23061
|
} catch (error51) {
|
|
22521
23062
|
const message = error51 instanceof Error ? error51.message : String(error51);
|
|
22522
23063
|
throw cliError("record.helper_unavailable", "Recappi recording helper is not executable.", {
|
|
22523
|
-
hint: `Could not make bundled helper executable at ${
|
|
23064
|
+
hint: `Could not make bundled helper executable at ${path7}: ${message}. Reinstall recappi, or set ${SIDECAR_COMMAND_ENV} to a compatible helper.`
|
|
22524
23065
|
});
|
|
22525
23066
|
}
|
|
22526
23067
|
}
|
|
@@ -22565,14 +23106,14 @@ function helperSourceSignature(sourceApp) {
|
|
|
22565
23106
|
codeSignature: existsSync(signaturePath) ? fileDigest(signaturePath) : null
|
|
22566
23107
|
});
|
|
22567
23108
|
}
|
|
22568
|
-
function fileDigest(
|
|
23109
|
+
function fileDigest(path7) {
|
|
22569
23110
|
const hash2 = createHash("sha256");
|
|
22570
|
-
hash2.update(readFileSync2(
|
|
23111
|
+
hash2.update(readFileSync2(path7));
|
|
22571
23112
|
return hash2.digest("hex");
|
|
22572
23113
|
}
|
|
22573
|
-
function readTextIfExists(
|
|
23114
|
+
function readTextIfExists(path7) {
|
|
22574
23115
|
try {
|
|
22575
|
-
return readFileSync2(
|
|
23116
|
+
return readFileSync2(path7, "utf8");
|
|
22576
23117
|
} catch {
|
|
22577
23118
|
return null;
|
|
22578
23119
|
}
|
|
@@ -22935,6 +23476,26 @@ async function runCli(deps = {}) {
|
|
|
22935
23476
|
},
|
|
22936
23477
|
retranscribeRecording: (recordingId, options = {}) => client.transcribeRecording({ recordingId, ...options }),
|
|
22937
23478
|
resummarizeRecording: (recordingId) => client.summarizeRecording({ recordingId }),
|
|
23479
|
+
syncRecordingText: (recordingId) => syncRecordingText({
|
|
23480
|
+
recordingId,
|
|
23481
|
+
client,
|
|
23482
|
+
env: deps.env,
|
|
23483
|
+
homeDir: deps.homeDir
|
|
23484
|
+
}),
|
|
23485
|
+
syncRecordingAudio: (recordingId) => syncRecordingAudio({
|
|
23486
|
+
recordingId,
|
|
23487
|
+
client,
|
|
23488
|
+
recordingAudio,
|
|
23489
|
+
env: deps.env,
|
|
23490
|
+
homeDir: deps.homeDir
|
|
23491
|
+
}),
|
|
23492
|
+
exportRecording: (recordingId) => exportRecording({
|
|
23493
|
+
recordingId,
|
|
23494
|
+
client,
|
|
23495
|
+
recordingAudio,
|
|
23496
|
+
env: deps.env,
|
|
23497
|
+
homeDir: deps.homeDir
|
|
23498
|
+
}),
|
|
22938
23499
|
initialView: parsed.initialView
|
|
22939
23500
|
});
|
|
22940
23501
|
return 0;
|
|
@@ -22965,7 +23526,7 @@ async function runCli(deps = {}) {
|
|
|
22965
23526
|
return 0;
|
|
22966
23527
|
}
|
|
22967
23528
|
if (parsed.kind === "auth-logout") {
|
|
22968
|
-
const cleared = await clearAuthConfig(deps.homeDir ??
|
|
23529
|
+
const cleared = await clearAuthConfig(deps.homeDir ?? os6.homedir());
|
|
22969
23530
|
renderSuccess("auth logout", { loggedIn: false, origin: auth.origin, cleared }, render3);
|
|
22970
23531
|
return 0;
|
|
22971
23532
|
}
|
|
@@ -22976,7 +23537,7 @@ async function runCli(deps = {}) {
|
|
|
22976
23537
|
hint: keychain.hint ?? "Run recappi auth login instead."
|
|
22977
23538
|
});
|
|
22978
23539
|
}
|
|
22979
|
-
await saveAuthConfig(deps.homeDir ??
|
|
23540
|
+
await saveAuthConfig(deps.homeDir ?? os6.homedir(), {
|
|
22980
23541
|
origin: auth.origin,
|
|
22981
23542
|
token: keychain.token
|
|
22982
23543
|
});
|
|
@@ -23125,6 +23686,29 @@ async function runCli(deps = {}) {
|
|
|
23125
23686
|
renderSuccess("recordings get", data, render3);
|
|
23126
23687
|
return 0;
|
|
23127
23688
|
}
|
|
23689
|
+
if (parsed.kind === "recordings-export") {
|
|
23690
|
+
const status = await client.authStatus();
|
|
23691
|
+
if (!status.loggedIn || !status.userId) {
|
|
23692
|
+
throw cliError("auth.not_logged_in", "Sign in before exporting a recording bundle.", {
|
|
23693
|
+
hint: "Run recappi auth login, or import the Recappi Mini session with recappi auth import-macos."
|
|
23694
|
+
});
|
|
23695
|
+
}
|
|
23696
|
+
const recordingAudio = createRecordingAudioRuntime(client, {
|
|
23697
|
+
account: { backendOrigin: auth.origin, userId: status.userId },
|
|
23698
|
+
env: deps.env,
|
|
23699
|
+
homeDir: deps.homeDir
|
|
23700
|
+
});
|
|
23701
|
+
const data = await exportRecording({
|
|
23702
|
+
recordingId: parsed.recordingId,
|
|
23703
|
+
...parsed.directory ? { directory: parsed.directory } : {},
|
|
23704
|
+
client,
|
|
23705
|
+
recordingAudio,
|
|
23706
|
+
env: deps.env,
|
|
23707
|
+
homeDir: deps.homeDir
|
|
23708
|
+
});
|
|
23709
|
+
renderSuccess("recordings export", data, render3);
|
|
23710
|
+
return 0;
|
|
23711
|
+
}
|
|
23128
23712
|
if (parsed.kind === "recordings-retranscribe") {
|
|
23129
23713
|
const eventMode = parsed.options.mode === "jsonl" ? "jsonl" : "human";
|
|
23130
23714
|
const data = await client.transcribeRecording({
|
|
@@ -23480,6 +24064,20 @@ Agent mode:
|
|
|
23480
24064
|
});
|
|
23481
24065
|
}
|
|
23482
24066
|
);
|
|
24067
|
+
const recordingsExport = recordings.command("export <recordingId>").description("Export audio, transcript, summary, and subscription/account files for agents").option("--dir <dir>", "directory for exported files", parseStringOption("--dir")).addHelpText("after", commandMetadataHelpText("recordings export"));
|
|
24068
|
+
addCommonOptions(recordingsExport);
|
|
24069
|
+
recordingsExport.action(
|
|
24070
|
+
(recordingId, _options, command) => {
|
|
24071
|
+
const opts = command.opts();
|
|
24072
|
+
onSelect({
|
|
24073
|
+
kind: "recordings-export",
|
|
24074
|
+
options: collectGlobalOptions(command),
|
|
24075
|
+
commandName: "recordings export",
|
|
24076
|
+
recordingId,
|
|
24077
|
+
...typeof opts.dir === "string" ? { directory: opts.dir } : {}
|
|
24078
|
+
});
|
|
24079
|
+
}
|
|
24080
|
+
);
|
|
23483
24081
|
const recordingsRetranscribe = recordings.command("retranscribe <recordingId>").description("Start a fresh transcription job for an existing recording").option("--language <lang>", "transcription language hint", parseStringOption("--language")).option("--provider <name>", "transcription provider", parseStringOption("--provider")).option("--model <name>", "transcription model", parseStringOption("--model")).option("--prompt <text>", "custom transcription prompt/context", parseStringOption("--prompt")).option("--scene <id>", "transcription scene preset", parseStringOption("--scene")).option("--wait", "wait for the transcription job to reach a terminal state").addHelpText("after", commandMetadataHelpText("recordings retranscribe"));
|
|
23484
24082
|
addCommonOptions(recordingsRetranscribe);
|
|
23485
24083
|
recordingsRetranscribe.action(
|
|
@@ -23654,7 +24252,8 @@ var VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
|
23654
24252
|
"--status",
|
|
23655
24253
|
"--limit",
|
|
23656
24254
|
"--cursor",
|
|
23657
|
-
"--search"
|
|
24255
|
+
"--search",
|
|
24256
|
+
"--dir"
|
|
23658
24257
|
]);
|
|
23659
24258
|
function hasCommandToken(argv) {
|
|
23660
24259
|
return commandTokens(argv).length > 0;
|