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 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) void runAudio(rec.recordingId, "download");
2756
- else if (input === "f" && rec) void runAudio(rec.recordingId, "finder");
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 os5 from "os";
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, path6) {
3800
- if (!path6)
3862
+ function getElementAtPath(obj, path7) {
3863
+ if (!path7)
3801
3864
  return obj;
3802
- return path6.reduce((acc, key) => acc?.[key], obj);
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(path6, issues) {
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(path6);
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, path6 = []) => {
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 }, [...path6, ...issue2.path]));
4428
+ issue2.errors.map((issues) => processError({ issues }, [...path7, ...issue2.path]));
4366
4429
  } else if (issue2.code === "invalid_key") {
4367
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
4430
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
4368
4431
  } else if (issue2.code === "invalid_element") {
4369
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
4432
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
4370
4433
  } else {
4371
- const fullpath = [...path6, ...issue2.path];
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, path6 = []) => {
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 }, [...path6, ...issue2.path]));
4465
+ issue2.errors.map((issues) => processError({ issues }, [...path7, ...issue2.path]));
4403
4466
  } else if (issue2.code === "invalid_key") {
4404
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
4467
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
4405
4468
  } else if (issue2.code === "invalid_element") {
4406
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
4469
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
4407
4470
  } else {
4408
- const fullpath = [...path6, ...issue2.path];
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 path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
4441
- for (const seg of path6) {
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 path6 = ref.slice(1).split("/").filter(Boolean);
17134
- if (path6.length === 0) {
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 (path6[0] === defsKey) {
17139
- const key = path6[1];
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(dir, recordingAudioFileName(recordingId, opts.title, contentType));
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(path6) {
19704
- const response = await this.request("GET", path6);
19794
+ async getJson(path7) {
19795
+ const response = await this.request("GET", path7);
19705
19796
  return await parseJson(response);
19706
19797
  }
19707
- async postJson(path6, body) {
19708
- const response = await this.request("POST", path6, JSON.stringify(body), {
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, path6, out) {
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 = [...path6, name];
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(path6) {
21875
- const result = spawnSync("mkfifo", [path6], { encoding: "utf8" });
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(path6, opts = {}) {
22500
- if (process.platform === "darwin" && path6.endsWith(".app")) {
22501
- const stableApp = ensureStableDarwinHelperApp(path6, opts);
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 path6;
22512
- ensureExecutableMode(path6);
22513
- return path6;
23052
+ if (process.platform === "win32") return path7;
23053
+ ensureExecutableMode(path7);
23054
+ return path7;
22514
23055
  }
22515
- function ensureExecutableMode(path6) {
22516
- const mode = statSync(path6).mode;
23056
+ function ensureExecutableMode(path7) {
23057
+ const mode = statSync(path7).mode;
22517
23058
  if ((mode & 73) !== 0) return;
22518
23059
  try {
22519
- chmodSync(path6, mode | 493);
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 ${path6}: ${message}. Reinstall recappi, or set ${SIDECAR_COMMAND_ENV} to a compatible helper.`
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(path6) {
23109
+ function fileDigest(path7) {
22569
23110
  const hash2 = createHash("sha256");
22570
- hash2.update(readFileSync2(path6));
23111
+ hash2.update(readFileSync2(path7));
22571
23112
  return hash2.digest("hex");
22572
23113
  }
22573
- function readTextIfExists(path6) {
23114
+ function readTextIfExists(path7) {
22574
23115
  try {
22575
- return readFileSync2(path6, "utf8");
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 ?? os5.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 ?? os5.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;