recappi 0.1.73 → 0.1.75

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
@@ -1255,7 +1255,7 @@ function RecordingDetailView({
1255
1255
  " \xB7 T re-transcribe \xB7 s re-summarize",
1256
1256
  item.activeTranscriptId ? " \xB7 t full" : "",
1257
1257
  links.webUrl ? " \xB7 w web" : "",
1258
- " \xB7 esc back"
1258
+ " \xB7 r refresh \xB7 esc back"
1259
1259
  ] }) })
1260
1260
  ] });
1261
1261
  }
@@ -2034,6 +2034,7 @@ function AppShell({
2034
2034
  );
2035
2035
  const [audioCache, setAudioCache] = useState8(() => /* @__PURE__ */ new Map());
2036
2036
  const [downloadedIds, setDownloadedIds] = useState8(() => /* @__PURE__ */ new Set());
2037
+ const summaryRefreshRef = useRef3(null);
2037
2038
  const [liveRecord, setLiveRecord] = useState8(void 0);
2038
2039
  const [recordSetupInputs, setRecordSetupInputs] = useState8({
2039
2040
  sources: DEFAULT_RECORDING_SOURCES,
@@ -2423,6 +2424,18 @@ function AppShell({
2423
2424
  },
2424
2425
  [onRetranscribe, refresh]
2425
2426
  );
2427
+ const refetchTranscript = useCallback(
2428
+ (transcriptId) => {
2429
+ setTranscriptCache((m) => new Map(m).set(transcriptId, "loading"));
2430
+ setSummaryCache((m) => {
2431
+ const next = new Map(m);
2432
+ next.delete(transcriptId);
2433
+ return next;
2434
+ });
2435
+ fetchTranscript(transcriptId).then((tr) => setTranscriptCache((m) => new Map(m).set(transcriptId, tr))).catch(() => setTranscriptCache((m) => new Map(m).set(transcriptId, "error")));
2436
+ },
2437
+ [fetchTranscript]
2438
+ );
2426
2439
  const resummarizeExistingRecording = useCallback(
2427
2440
  async (recordingId) => {
2428
2441
  if (!onResummarize) {
@@ -2431,14 +2444,19 @@ function AppShell({
2431
2444
  }
2432
2445
  setNotice("Re-summarize started\u2026");
2433
2446
  try {
2434
- await onResummarize(recordingId);
2435
- setNotice("Re-summarize started \u2014 the summary will refresh shortly.");
2447
+ const data = await onResummarize(recordingId);
2448
+ setNotice("Re-summarize started \u2014 the summary updates here as it finishes.");
2436
2449
  await refresh({ resetRecordings: true });
2450
+ const transcriptId = data.transcriptId ?? recordings.find((r) => r.recordingId === recordingId)?.activeTranscriptId;
2451
+ if (transcriptId) {
2452
+ summaryRefreshRef.current = { transcriptId, until: now() + 12e4 };
2453
+ refetchTranscript(transcriptId);
2454
+ }
2437
2455
  } catch (error51) {
2438
2456
  setNotice(transcribeHandoffErrorCopy(error51));
2439
2457
  }
2440
2458
  },
2441
- [onResummarize, refresh]
2459
+ [onResummarize, refresh, recordings, now, refetchTranscript]
2442
2460
  );
2443
2461
  useEffect4(() => {
2444
2462
  if (liveRecord?.kind !== "stopped") return;
@@ -2481,6 +2499,26 @@ function AppShell({
2481
2499
  const id = setInterval(() => void refresh(), pollMs);
2482
2500
  return () => clearInterval(id);
2483
2501
  }, [refresh, pollMs]);
2502
+ const transcriptCacheRef = useRef3(transcriptCache);
2503
+ transcriptCacheRef.current = transcriptCache;
2504
+ useEffect4(() => {
2505
+ const id = setInterval(() => {
2506
+ const pending = summaryRefreshRef.current;
2507
+ if (!pending) return;
2508
+ if (now() >= pending.until) {
2509
+ summaryRefreshRef.current = null;
2510
+ return;
2511
+ }
2512
+ const cached2 = transcriptCacheRef.current.get(pending.transcriptId);
2513
+ const summary = cached2 && cached2 !== "loading" && cached2 !== "error" ? cached2.summary : void 0;
2514
+ if (summary?.status === "succeeded") {
2515
+ summaryRefreshRef.current = null;
2516
+ return;
2517
+ }
2518
+ refetchTranscript(pending.transcriptId);
2519
+ }, 3e3);
2520
+ return () => clearInterval(id);
2521
+ }, [now, refetchTranscript]);
2484
2522
  const hasRunning = jobs.some((item) => item.status === "running");
2485
2523
  useEffect4(() => {
2486
2524
  if (!hasRunning && loaded) return;
@@ -2665,14 +2703,21 @@ function AppShell({
2665
2703
  }
2666
2704
  return;
2667
2705
  }
2668
- if (input === "r") return void refresh({ resetRecordings: true });
2706
+ if (input === "r") {
2707
+ void refresh({ resetRecordings: true });
2708
+ if (screen.kind === "recordingDetail") {
2709
+ const detailRec = recordings.find((r) => r.recordingId === screen.recordingId);
2710
+ if (detailRec?.activeTranscriptId) refetchTranscript(detailRec.activeTranscriptId);
2711
+ }
2712
+ return;
2713
+ }
2669
2714
  if (screen.kind === "overview") {
2670
2715
  if (input === "g") setSelected(0);
2671
2716
  if (input === "G") setSelected(Math.max(0, recordings.length - 1));
2672
2717
  if (key.upArrow || input === "k") setSelected((i) => Math.max(0, i - 1));
2673
2718
  if (key.downArrow || input === "j") setSelected((i) => Math.min(recordings.length - 1, i + 1));
2674
2719
  const rec = recordings[selected];
2675
- if (key.return && rec)
2720
+ if ((key.return || key.rightArrow) && rec)
2676
2721
  setStack((st) => [...st, { kind: "recordingDetail", recordingId: rec.recordingId }]);
2677
2722
  if (input === "t" && rec?.activeTranscriptId) void openTranscript(rec.activeTranscriptId);
2678
2723
  return;
@@ -2683,7 +2728,8 @@ function AppShell({
2683
2728
  if (key.upArrow || input === "k") setSelected((i) => Math.max(0, i - 1));
2684
2729
  if (key.downArrow || input === "j") setSelected((i) => Math.min(jobs.length - 1, i + 1));
2685
2730
  const job = jobs[selected];
2686
- if (key.return && job) setStack((st) => [...st, { kind: "jobDetail", jobId: job.jobId }]);
2731
+ if ((key.return || key.rightArrow) && job)
2732
+ setStack((st) => [...st, { kind: "jobDetail", jobId: job.jobId }]);
2687
2733
  if (input === "t" && job?.transcriptId) void openTranscript(job.transcriptId);
2688
2734
  return;
2689
2735
  }
@@ -2955,6 +3001,9 @@ async function runDashboard(deps) {
2955
3001
  transcribeRecordingArtifact: deps.transcribeRecordingArtifact,
2956
3002
  onRetranscribe: deps.retranscribeRecording,
2957
3003
  onResummarize: deps.resummarizeRecording,
3004
+ onSyncRecordingText: deps.syncRecordingText,
3005
+ onSyncRecordingAudio: deps.syncRecordingAudio,
3006
+ onExportRecording: deps.exportRecording,
2958
3007
  initialView: deps.initialView ?? "overview",
2959
3008
  openUrl,
2960
3009
  copyText
@@ -2982,7 +3031,7 @@ var init_tui = __esm({
2982
3031
 
2983
3032
  // src/cli.ts
2984
3033
  import { Command, CommanderError, InvalidArgumentError } from "commander/esm.mjs";
2985
- import os5 from "os";
3034
+ import os6 from "os";
2986
3035
 
2987
3036
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
2988
3037
  var external_exports = {};
@@ -3750,10 +3799,10 @@ function mergeDefs(...defs) {
3750
3799
  function cloneDef(schema) {
3751
3800
  return mergeDefs(schema._zod.def);
3752
3801
  }
3753
- function getElementAtPath(obj, path6) {
3754
- if (!path6)
3802
+ function getElementAtPath(obj, path7) {
3803
+ if (!path7)
3755
3804
  return obj;
3756
- return path6.reduce((acc, key) => acc?.[key], obj);
3805
+ return path7.reduce((acc, key) => acc?.[key], obj);
3757
3806
  }
3758
3807
  function promiseAllObject(promisesObj) {
3759
3808
  const keys = Object.keys(promisesObj);
@@ -4162,11 +4211,11 @@ function explicitlyAborted(x, startIndex = 0) {
4162
4211
  }
4163
4212
  return false;
4164
4213
  }
4165
- function prefixIssues(path6, issues) {
4214
+ function prefixIssues(path7, issues) {
4166
4215
  return issues.map((iss) => {
4167
4216
  var _a3;
4168
4217
  (_a3 = iss).path ?? (_a3.path = []);
4169
- iss.path.unshift(path6);
4218
+ iss.path.unshift(path7);
4170
4219
  return iss;
4171
4220
  });
4172
4221
  }
@@ -4313,16 +4362,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
4313
4362
  }
4314
4363
  function formatError(error51, mapper = (issue2) => issue2.message) {
4315
4364
  const fieldErrors = { _errors: [] };
4316
- const processError = (error52, path6 = []) => {
4365
+ const processError = (error52, path7 = []) => {
4317
4366
  for (const issue2 of error52.issues) {
4318
4367
  if (issue2.code === "invalid_union" && issue2.errors.length) {
4319
- issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
4368
+ issue2.errors.map((issues) => processError({ issues }, [...path7, ...issue2.path]));
4320
4369
  } else if (issue2.code === "invalid_key") {
4321
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
4370
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
4322
4371
  } else if (issue2.code === "invalid_element") {
4323
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
4372
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
4324
4373
  } else {
4325
- const fullpath = [...path6, ...issue2.path];
4374
+ const fullpath = [...path7, ...issue2.path];
4326
4375
  if (fullpath.length === 0) {
4327
4376
  fieldErrors._errors.push(mapper(issue2));
4328
4377
  } else {
@@ -4349,17 +4398,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
4349
4398
  }
4350
4399
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
4351
4400
  const result = { errors: [] };
4352
- const processError = (error52, path6 = []) => {
4401
+ const processError = (error52, path7 = []) => {
4353
4402
  var _a3, _b;
4354
4403
  for (const issue2 of error52.issues) {
4355
4404
  if (issue2.code === "invalid_union" && issue2.errors.length) {
4356
- issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
4405
+ issue2.errors.map((issues) => processError({ issues }, [...path7, ...issue2.path]));
4357
4406
  } else if (issue2.code === "invalid_key") {
4358
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
4407
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
4359
4408
  } else if (issue2.code === "invalid_element") {
4360
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
4409
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
4361
4410
  } else {
4362
- const fullpath = [...path6, ...issue2.path];
4411
+ const fullpath = [...path7, ...issue2.path];
4363
4412
  if (fullpath.length === 0) {
4364
4413
  result.errors.push(mapper(issue2));
4365
4414
  continue;
@@ -4391,8 +4440,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
4391
4440
  }
4392
4441
  function toDotPath(_path) {
4393
4442
  const segs = [];
4394
- const path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
4395
- for (const seg of path6) {
4443
+ const path7 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
4444
+ for (const seg of path7) {
4396
4445
  if (typeof seg === "number")
4397
4446
  segs.push(`[${seg}]`);
4398
4447
  else if (typeof seg === "symbol")
@@ -17084,13 +17133,13 @@ function resolveRef(ref, ctx) {
17084
17133
  if (!ref.startsWith("#")) {
17085
17134
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
17086
17135
  }
17087
- const path6 = ref.slice(1).split("/").filter(Boolean);
17088
- if (path6.length === 0) {
17136
+ const path7 = ref.slice(1).split("/").filter(Boolean);
17137
+ if (path7.length === 0) {
17089
17138
  return ctx.rootSchema;
17090
17139
  }
17091
17140
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
17092
- if (path6[0] === defsKey) {
17093
- const key = path6[1];
17141
+ if (path7[0] === defsKey) {
17142
+ const key = path7[1];
17094
17143
  if (!key || !ctx.defs[key]) {
17095
17144
  throw new Error(`Reference not found: ${ref}`);
17096
17145
  }
@@ -18093,6 +18142,31 @@ var transcriptDataSchema = external_exports.object({
18093
18142
  segments: external_exports.array(transcriptSegmentSchema),
18094
18143
  summary: transcriptSummarySchema
18095
18144
  });
18145
+ var recordingExportDataSchema = external_exports.object({
18146
+ origin: external_exports.string(),
18147
+ recordingId: external_exports.string(),
18148
+ exportDir: external_exports.string(),
18149
+ textPath: external_exports.string(),
18150
+ manifestPath: external_exports.string(),
18151
+ remoteManifestPath: external_exports.string(),
18152
+ sessionMetadataPath: external_exports.string(),
18153
+ recordingJsonPath: external_exports.string(),
18154
+ subscriptionPath: external_exports.string(),
18155
+ subscriptionJsonPath: external_exports.string(),
18156
+ audioPath: external_exports.string(),
18157
+ transcriptId: external_exports.string().nullable().optional(),
18158
+ transcriptPath: external_exports.string().optional(),
18159
+ transcriptJsonPath: external_exports.string().optional(),
18160
+ summaryPath: external_exports.string().optional(),
18161
+ summaryJsonPath: external_exports.string().optional(),
18162
+ actionItemsPath: external_exports.string().optional(),
18163
+ summaryStatus: summaryStatusSchema.optional(),
18164
+ audio: external_exports.object({
18165
+ contentType: external_exports.string().optional(),
18166
+ contentLength: external_exports.number().int().nonnegative().optional(),
18167
+ reused: external_exports.boolean().optional()
18168
+ }).optional()
18169
+ });
18096
18170
  var doctorCheckStatusSchema = external_exports.enum(["ok", "warn", "error"]);
18097
18171
  var doctorCheckSchema = external_exports.object({
18098
18172
  name: external_exports.string(),
@@ -19355,7 +19429,10 @@ var RecappiApiClient = class {
19355
19429
  const contentLength = numberHeader(response.headers.get("content-length"));
19356
19430
  const dir = opts.directory ?? await fs3.mkdtemp(path4.join(os4.tmpdir(), "recappi-cli-audio-"));
19357
19431
  if (opts.directory) await fs3.mkdir(dir, { recursive: true });
19358
- const filePath = path4.join(dir, recordingAudioFileName(recordingId, opts.title, contentType));
19432
+ const filePath = path4.join(
19433
+ dir,
19434
+ recordingAudioFileName(recordingId, opts.title, contentType, opts.filenameStem)
19435
+ );
19359
19436
  try {
19360
19437
  await pipeline(
19361
19438
  Readable.fromWeb(response.body),
@@ -19654,12 +19731,12 @@ var RecappiApiClient = class {
19654
19731
  } : {}
19655
19732
  };
19656
19733
  }
19657
- async getJson(path6) {
19658
- const response = await this.request("GET", path6);
19734
+ async getJson(path7) {
19735
+ const response = await this.request("GET", path7);
19659
19736
  return await parseJson(response);
19660
19737
  }
19661
- async postJson(path6, body) {
19662
- const response = await this.request("POST", path6, JSON.stringify(body), {
19738
+ async postJson(path7, body) {
19739
+ const response = await this.request("POST", path7, JSON.stringify(body), {
19663
19740
  headers: { "content-type": "application/json" }
19664
19741
  });
19665
19742
  return await parseJson(response);
@@ -19753,7 +19830,10 @@ function audioExtensionForContentType(contentType) {
19753
19830
  return "wav";
19754
19831
  }
19755
19832
  }
19756
- function recordingAudioFileName(recordingId, title, contentType) {
19833
+ function recordingAudioFileName(recordingId, title, contentType, filenameStem) {
19834
+ if (filenameStem && filenameStem.trim()) {
19835
+ return `${truncateFileStem(safeFileStem(filenameStem), 96)}.${audioExtensionForContentType(contentType)}`;
19836
+ }
19757
19837
  const idStem = truncateFileStem(safeFileStem(recordingId), 48);
19758
19838
  const titleStem = title ? truncateFileStem(safeFileStem(title), 80) : "";
19759
19839
  const stem = titleStem ? `${titleStem}-${idStem}` : idStem;
@@ -20047,7 +20127,7 @@ import { promises as fs4 } from "fs";
20047
20127
  import path5 from "path";
20048
20128
  function createRecordingAudioRuntime(client, deps = {}) {
20049
20129
  const downloadRecordingAudioFile = async (recordingId, opts) => {
20050
- const cached2 = await findReusableDownload(recordingId, deps);
20130
+ const cached2 = opts?.directory ? null : await findReusableDownload(recordingId, deps);
20051
20131
  if (cached2) return cached2;
20052
20132
  const directory = opts?.directory ?? (deps.account ? defaultDownloadDirectory(deps) : void 0);
20053
20133
  const download = await client.downloadRecordingAudio(recordingId, {
@@ -20203,6 +20283,7 @@ var COMMON_TASKS = [
20203
20283
  { label: "Transcribe a local file", command: "recappi upload <file> --transcribe --wait" },
20204
20284
  { label: "Re-transcribe a recording", command: "recappi recordings retranscribe <recordingId> --wait" },
20205
20285
  { label: "Re-summarize a recording", command: "recappi recordings resummarize <recordingId>" },
20286
+ { label: "Export recording bundle", command: "recappi recordings export <recordingId> --dir ./bundle" },
20206
20287
  { label: "List / find recordings", command: "recappi recordings list" },
20207
20288
  { label: "Read a transcript", command: "recappi transcript get <transcriptId>" },
20208
20289
  { label: "Download / open audio", command: "recappi audio <recordingId> --open" },
@@ -20309,7 +20390,24 @@ var COMMAND_METADATA = {
20309
20390
  "recordings get": {
20310
20391
  capabilities: ["Fetch one recording's metadata and status by id"],
20311
20392
  examples: [{ description: "Fetch one recording", command: "recappi recordings get <recordingId>" }],
20312
- relatedCommands: ["recordings list", "transcript get", "audio"]
20393
+ relatedCommands: ["recordings list", "recordings export", "transcript get", "audio"]
20394
+ },
20395
+ "recordings export": {
20396
+ capabilities: [
20397
+ "Write a plain-text Markdown handoff file for agents",
20398
+ "Export raw audio, transcript, summary, action items, subscription/account data, and JSON sidecars"
20399
+ ],
20400
+ examples: [
20401
+ {
20402
+ description: "Export a recording bundle into a chosen directory",
20403
+ command: "recappi recordings export <recordingId> --dir ./recappi-export"
20404
+ },
20405
+ {
20406
+ description: "Export and return machine-readable paths",
20407
+ command: "recappi recordings export <recordingId> --json --compact"
20408
+ }
20409
+ ],
20410
+ relatedCommands: ["recordings get", "transcript get", "audio", "account status"]
20313
20411
  },
20314
20412
  "recordings list": {
20315
20413
  capabilities: ["List recent recordings", "Search recordings and transcripts", "Find a recordingId"],
@@ -20317,7 +20415,7 @@ var COMMAND_METADATA = {
20317
20415
  { description: "List recent recordings", command: "recappi recordings list" },
20318
20416
  { description: "Search recordings and transcripts", command: "recappi recordings list --search <query>" }
20319
20417
  ],
20320
- relatedCommands: ["recordings get", "audio", "transcript get"]
20418
+ relatedCommands: ["recordings get", "recordings export", "audio", "transcript get"]
20321
20419
  },
20322
20420
  "recordings retranscribe": {
20323
20421
  capabilities: ["Re-transcribe an existing recording", "Re-transcribe with new language/prompt/scene/model"],
@@ -20394,6 +20492,408 @@ ${lines.join("\n")}
20394
20492
  ` : "";
20395
20493
  }
20396
20494
 
20495
+ // src/export.ts
20496
+ import { promises as fs5 } from "fs";
20497
+ import os5 from "os";
20498
+ import path6 from "path";
20499
+ async function syncRecordingText(opts) {
20500
+ const context = await loadRecordingBundleContext(opts);
20501
+ const sessionDir = await resolveRecordingSessionDir(context.recording, opts);
20502
+ return writeRecordingTextFiles(context, sessionDir, {
20503
+ now: opts.now
20504
+ });
20505
+ }
20506
+ async function syncRecordingAudio(opts) {
20507
+ const context = await loadRecordingBundleContext(opts);
20508
+ const sessionDir = await resolveRecordingSessionDir(context.recording, opts);
20509
+ const audio = await downloadRecordingAudioToDir(context.recording, opts.recordingAudio, sessionDir);
20510
+ const text = await writeRecordingTextFiles(context, sessionDir, {
20511
+ now: opts.now,
20512
+ uploadFilename: path6.basename(audio.localPath)
20513
+ });
20514
+ return { ...text, audioPath: audio.localPath, audio: audioMetadata(audio) };
20515
+ }
20516
+ async function exportRecording(opts) {
20517
+ const context = await loadRecordingBundleContext(opts);
20518
+ const exportDir = await resolveRecordingSessionDir(context.recording, opts);
20519
+ const audio = await downloadRecordingAudioToDir(context.recording, opts.recordingAudio, exportDir);
20520
+ const textFiles = await writeRecordingTextFiles(context, exportDir, {
20521
+ now: opts.now,
20522
+ uploadFilename: path6.basename(audio.localPath)
20523
+ });
20524
+ const { recording, subscription, transcript } = context;
20525
+ const subscriptionPath = path6.join(exportDir, "subscription.md");
20526
+ const subscriptionJsonPath = path6.join(exportDir, "subscription.json");
20527
+ await fs5.writeFile(subscriptionPath, renderSubscriptionMarkdown(subscription), "utf8");
20528
+ await writeJson(subscriptionJsonPath, subscription);
20529
+ const textPath = path6.join(exportDir, "handoff.md");
20530
+ const manifestPath = path6.join(exportDir, "manifest.json");
20531
+ const data = recordingExportDataSchema.parse({
20532
+ origin: recording.origin,
20533
+ recordingId: recording.recordingId,
20534
+ exportDir,
20535
+ textPath,
20536
+ manifestPath,
20537
+ remoteManifestPath: textFiles.remoteManifestPath,
20538
+ sessionMetadataPath: textFiles.sessionMetadataPath,
20539
+ recordingJsonPath: textFiles.recordingJsonPath,
20540
+ subscriptionPath,
20541
+ subscriptionJsonPath,
20542
+ audioPath: audio.localPath,
20543
+ ...textFiles.transcriptId !== void 0 ? { transcriptId: textFiles.transcriptId } : {},
20544
+ ...textFiles.transcriptPath ? { transcriptPath: textFiles.transcriptPath } : {},
20545
+ ...textFiles.transcriptJsonPath ? { transcriptJsonPath: textFiles.transcriptJsonPath } : {},
20546
+ ...textFiles.summaryPath ? { summaryPath: textFiles.summaryPath } : {},
20547
+ ...textFiles.summaryJsonPath ? { summaryJsonPath: textFiles.summaryJsonPath } : {},
20548
+ ...textFiles.actionItemsPath ? { actionItemsPath: textFiles.actionItemsPath } : {},
20549
+ ...textFiles.summaryStatus ? { summaryStatus: textFiles.summaryStatus } : {},
20550
+ audio: audioMetadata(audio)
20551
+ });
20552
+ await fs5.writeFile(
20553
+ textPath,
20554
+ renderHandoffMarkdown(recording, subscription, audio, data, transcript),
20555
+ "utf8"
20556
+ );
20557
+ await writeJson(manifestPath, {
20558
+ exportedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
20559
+ command: "recordings export",
20560
+ data
20561
+ });
20562
+ return data;
20563
+ }
20564
+ async function loadRecordingBundleContext(opts) {
20565
+ const recording = await opts.client.getRecording(opts.recordingId);
20566
+ const subscription = await opts.client.accountStatus();
20567
+ const transcript = recording.activeTranscriptId ? await opts.client.getTranscript(recording.activeTranscriptId) : void 0;
20568
+ return {
20569
+ recording,
20570
+ subscription,
20571
+ ...transcript ? { transcript } : {},
20572
+ transcriptId: transcript?.transcriptId ?? recording.activeTranscriptId ?? void 0
20573
+ };
20574
+ }
20575
+ async function downloadRecordingAudioToDir(recording, recordingAudio, directory) {
20576
+ return recordingAudio.downloadRecordingAudioFile(recording.recordingId, {
20577
+ directory,
20578
+ filenameStem: "recording",
20579
+ title: recording.title ?? recording.summaryTitle ?? recording.recordingId
20580
+ });
20581
+ }
20582
+ async function writeRecordingTextFiles(context, sessionDir, opts) {
20583
+ const { recording, transcript, subscription } = context;
20584
+ await fs5.mkdir(sessionDir, { recursive: true });
20585
+ const recordingJsonPath = path6.join(sessionDir, "recording.json");
20586
+ const sessionMetadataPath = path6.join(sessionDir, "session-metadata.json");
20587
+ const remoteManifestPath = path6.join(sessionDir, "remote-session.json");
20588
+ const existingManifest = await readJsonRecord(remoteManifestPath);
20589
+ const uploadFilename = opts.uploadFilename ?? (typeof existingManifest?.uploadFilename === "string" ? existingManifest.uploadFilename : void 0);
20590
+ await writeJson(recordingJsonPath, recording);
20591
+ await writeJson(sessionMetadataPath, renderSessionMetadata(recording));
20592
+ await writeJson(
20593
+ remoteManifestPath,
20594
+ renderRemoteSessionManifest(recording, uploadFilename, transcript, subscription, opts.now)
20595
+ );
20596
+ let transcriptPath;
20597
+ let transcriptJsonPath;
20598
+ let summaryPath;
20599
+ let summaryJsonPath;
20600
+ let actionItemsPath;
20601
+ let summaryStatus;
20602
+ if (transcript) {
20603
+ transcriptPath = path6.join(sessionDir, "transcript.md");
20604
+ transcriptJsonPath = path6.join(sessionDir, "transcript.json");
20605
+ summaryPath = path6.join(sessionDir, "summary.md");
20606
+ summaryJsonPath = path6.join(sessionDir, "summary.json");
20607
+ actionItemsPath = path6.join(sessionDir, "action-items.md");
20608
+ summaryStatus = transcript.summary.status;
20609
+ await fs5.writeFile(transcriptPath, renderTranscriptMarkdown(transcript), "utf8");
20610
+ await writeJson(transcriptJsonPath, transcript);
20611
+ await writeJson(summaryJsonPath, transcript.summary);
20612
+ await fs5.writeFile(summaryPath, renderSummaryMarkdown(recording, transcript), "utf8");
20613
+ await writeOptionalText(actionItemsPath, renderActionItemsMarkdown(transcript.summary));
20614
+ }
20615
+ return {
20616
+ recordingId: recording.recordingId,
20617
+ sessionDir,
20618
+ remoteManifestPath,
20619
+ sessionMetadataPath,
20620
+ recordingJsonPath,
20621
+ ...context.transcriptId !== void 0 ? { transcriptId: context.transcriptId } : {},
20622
+ ...transcriptPath ? { transcriptPath } : {},
20623
+ ...transcriptJsonPath ? { transcriptJsonPath } : {},
20624
+ ...summaryPath ? { summaryPath } : {},
20625
+ ...summaryJsonPath ? { summaryJsonPath } : {},
20626
+ ...actionItemsPath ? { actionItemsPath } : {},
20627
+ ...summaryStatus ? { summaryStatus } : {}
20628
+ };
20629
+ }
20630
+ async function resolveRecordingSessionDir(recording, opts) {
20631
+ if (opts.directory) return path6.resolve(opts.directory);
20632
+ const existing = await findExistingRecordingSessionDir(recording.recordingId, opts.homeDir, opts.env);
20633
+ if (existing) return existing;
20634
+ return createRecordingSessionDir(recording, opts.homeDir, opts.env);
20635
+ }
20636
+ async function findExistingRecordingSessionDir(recordingId, homeDir, env) {
20637
+ const base = recordingSessionBaseDirectory(homeDir, env);
20638
+ let entries;
20639
+ try {
20640
+ entries = await fs5.readdir(base, { withFileTypes: true });
20641
+ } catch {
20642
+ return void 0;
20643
+ }
20644
+ for (const entry of entries) {
20645
+ if (!entry.isDirectory()) continue;
20646
+ const dir = path6.join(base, entry.name);
20647
+ const manifest = await readJsonRecord(path6.join(dir, "remote-session.json"));
20648
+ if (manifest?.recordingId === recordingId) return dir;
20649
+ }
20650
+ return void 0;
20651
+ }
20652
+ async function createRecordingSessionDir(recording, homeDir, env) {
20653
+ const base = recordingSessionBaseDirectory(homeDir, env);
20654
+ await fs5.mkdir(base, { recursive: true });
20655
+ const stem = formatSessionDirectoryDate(new Date(recording.createdAt));
20656
+ let candidate = path6.join(base, stem);
20657
+ let suffix = 2;
20658
+ while (await pathExists(candidate)) {
20659
+ candidate = path6.join(base, `${stem}-cloud-${suffix}`);
20660
+ suffix += 1;
20661
+ }
20662
+ await fs5.mkdir(candidate, { recursive: true });
20663
+ return candidate;
20664
+ }
20665
+ function recordingSessionBaseDirectory(homeDir = os5.homedir(), env = process.env) {
20666
+ const explicit = env.RECAPPI_LOCAL_SESSIONS_DIR?.trim();
20667
+ if (explicit) return explicit;
20668
+ return path6.join(homeDir, "Documents", "Recappi Mini");
20669
+ }
20670
+ async function readJsonRecord(filePath) {
20671
+ try {
20672
+ const parsed = JSON.parse(await fs5.readFile(filePath, "utf8"));
20673
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : void 0;
20674
+ } catch {
20675
+ return void 0;
20676
+ }
20677
+ }
20678
+ async function pathExists(filePath) {
20679
+ try {
20680
+ await fs5.access(filePath);
20681
+ return true;
20682
+ } catch {
20683
+ return false;
20684
+ }
20685
+ }
20686
+ function formatSessionDirectoryDate(date5) {
20687
+ const year = date5.getFullYear();
20688
+ const month = String(date5.getMonth() + 1).padStart(2, "0");
20689
+ const day = String(date5.getDate()).padStart(2, "0");
20690
+ const hour = String(date5.getHours()).padStart(2, "0");
20691
+ const minute = String(date5.getMinutes()).padStart(2, "0");
20692
+ const second = String(date5.getSeconds()).padStart(2, "0");
20693
+ return `${year}-${month}-${day}_${hour}${minute}${second}`;
20694
+ }
20695
+ async function writeJson(filePath, value) {
20696
+ await fs5.writeFile(filePath, `${JSON.stringify(value, null, 2)}
20697
+ `, "utf8");
20698
+ }
20699
+ async function writeOptionalText(filePath, value) {
20700
+ if (value && value.trim()) {
20701
+ await fs5.writeFile(filePath, value, "utf8");
20702
+ return;
20703
+ }
20704
+ await fs5.rm(filePath, { force: true });
20705
+ }
20706
+ function renderHandoffMarkdown(recording, subscription, audio, data, transcript) {
20707
+ const lines = [];
20708
+ lines.push(`# ${recording.title ?? recording.summaryTitle ?? "Recappi Recording"}`);
20709
+ lines.push("");
20710
+ lines.push("## Files", "");
20711
+ lines.push(`- Audio: ${audio.localPath}`);
20712
+ lines.push(`- Subscription: ${data.subscriptionPath}`);
20713
+ if (data.summaryPath) lines.push(`- Summary: ${data.summaryPath}`);
20714
+ if (data.transcriptPath) lines.push(`- Transcript: ${data.transcriptPath}`);
20715
+ if (data.actionItemsPath) lines.push(`- Action items: ${data.actionItemsPath}`);
20716
+ lines.push(`- Remote session manifest: ${data.remoteManifestPath}`);
20717
+ lines.push(`- Session metadata: ${data.sessionMetadataPath}`);
20718
+ lines.push(`- Manifest: ${data.manifestPath}`);
20719
+ lines.push("");
20720
+ lines.push("## Recording", "");
20721
+ lines.push(`- recordingId: ${recording.recordingId}`);
20722
+ lines.push(`- status: ${recording.status}`);
20723
+ if (recording.durationMs !== void 0 && recording.durationMs !== null) {
20724
+ lines.push(`- duration: ${formatTimestamp(recording.durationMs)}`);
20725
+ }
20726
+ if (recording.sizeBytes !== void 0 && recording.sizeBytes !== null) {
20727
+ lines.push(`- sizeBytes: ${recording.sizeBytes}`);
20728
+ }
20729
+ if (transcript) {
20730
+ lines.push(`- transcriptId: ${transcript.transcriptId}`);
20731
+ lines.push(`- summaryStatus: ${transcript.summary.status}`);
20732
+ } else {
20733
+ lines.push("- transcript: not available");
20734
+ }
20735
+ lines.push("");
20736
+ lines.push("## Subscription", "");
20737
+ appendSubscriptionLines(lines, subscription);
20738
+ lines.push("");
20739
+ if (transcript) {
20740
+ lines.push(renderSummaryMarkdown(recording, transcript).trimEnd());
20741
+ lines.push("");
20742
+ lines.push(renderTranscriptMarkdown(transcript).trimEnd());
20743
+ lines.push("");
20744
+ }
20745
+ return `${lines.join("\n").trimEnd()}
20746
+ `;
20747
+ }
20748
+ function renderSubscriptionMarkdown(subscription) {
20749
+ const lines = ["# Subscription", ""];
20750
+ appendSubscriptionLines(lines, subscription);
20751
+ return `${lines.join("\n").trimEnd()}
20752
+ `;
20753
+ }
20754
+ function renderSessionMetadata(recording) {
20755
+ const sourceTitle = recording.title ?? recording.summaryTitle ?? "Recappi Cloud";
20756
+ return stripUndefined({
20757
+ summaryTitle: recording.summaryTitle ?? recording.title ?? void 0,
20758
+ sourceTitle,
20759
+ sourceAppName: void 0,
20760
+ sourceBundleID: void 0,
20761
+ startedAt: new Date(recording.createdAt).toISOString(),
20762
+ sceneTemplate: void 0,
20763
+ extraPrompt: void 0,
20764
+ includesMicrophoneAudio: void 0
20765
+ });
20766
+ }
20767
+ function renderRemoteSessionManifest(recording, uploadFilename, transcript, subscription, now) {
20768
+ return stripUndefined({
20769
+ recordingId: recording.recordingId,
20770
+ jobId: transcript?.jobId,
20771
+ transcriptId: transcript?.transcriptId ?? recording.activeTranscriptId ?? void 0,
20772
+ stage: transcript ? "done" : "synced",
20773
+ errorMessage: void 0,
20774
+ uploadFilename,
20775
+ provider: transcript?.provider,
20776
+ model: transcript?.model,
20777
+ updatedAt: (now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
20778
+ accountUserId: subscription.userId,
20779
+ accountBackendOrigin: subscription.origin
20780
+ });
20781
+ }
20782
+ function stripUndefined(value) {
20783
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
20784
+ }
20785
+ function appendSubscriptionLines(lines, subscription) {
20786
+ lines.push(`- origin: ${subscription.origin}`);
20787
+ lines.push(`- loggedIn: ${subscription.loggedIn}`);
20788
+ if (subscription.email) lines.push(`- email: ${subscription.email}`);
20789
+ if (subscription.userId) lines.push(`- userId: ${subscription.userId}`);
20790
+ if (subscription.billing) {
20791
+ lines.push(`- plan: ${subscription.billing.tier}`);
20792
+ lines.push(`- minutesUsed: ${subscription.billing.minutesUsed}`);
20793
+ lines.push(`- minutesCap: ${subscription.billing.minutesCap ?? "unlimited"}`);
20794
+ lines.push(`- batchMinutesUsed: ${subscription.billing.batchMinutesUsed}`);
20795
+ lines.push(`- realtimeMinutesUsed: ${subscription.billing.realtimeMinutesUsed}`);
20796
+ lines.push(`- storageBytes: ${subscription.billing.storageBytes}`);
20797
+ lines.push(`- storageCapBytes: ${subscription.billing.storageCapBytes ?? "unlimited"}`);
20798
+ lines.push(`- periodStart: ${subscription.billing.periodStart}`);
20799
+ lines.push(`- periodEnd: ${subscription.billing.periodEnd}`);
20800
+ }
20801
+ }
20802
+ function renderTranscriptMarkdown(transcript) {
20803
+ return `# Transcript
20804
+
20805
+ ${renderTranscriptLines(transcript)}
20806
+ `;
20807
+ }
20808
+ function renderTranscriptLines(transcript) {
20809
+ const lines = transcript.segments.map((segment) => {
20810
+ const speaker = segment.speaker ? `${segment.speaker}: ` : "";
20811
+ return `[${formatTimestamp(segment.startMs)}] ${speaker}${segment.text}`;
20812
+ });
20813
+ if (lines.length > 0) return lines.join("\n");
20814
+ return transcript.text;
20815
+ }
20816
+ function renderSummaryMarkdown(recording, transcript) {
20817
+ const summary = transcript.summary;
20818
+ const lines = [];
20819
+ lines.push(`# ${summary.title ?? recording.title ?? recording.summaryTitle ?? "Summary"}`);
20820
+ lines.push("");
20821
+ lines.push(`- recordingId: ${recording.recordingId}`);
20822
+ lines.push(`- transcriptId: ${transcript.transcriptId}`);
20823
+ lines.push(`- summaryStatus: ${summary.status}`);
20824
+ if (summary.error) lines.push(`- error: ${summary.error}`);
20825
+ lines.push("");
20826
+ if (summary.tldr) {
20827
+ lines.push("## TL;DR", "");
20828
+ lines.push(summary.tldr, "");
20829
+ }
20830
+ appendStringList(lines, "Key Points", summary.keyPoints);
20831
+ appendStringList(lines, "Topics", summary.topics);
20832
+ appendStringList(lines, "Decisions", summary.decisions);
20833
+ appendActionItems(lines, summary.actionItems);
20834
+ appendTimeline(lines, summary.timeline);
20835
+ appendQuotes(lines, summary.quotes);
20836
+ if (lines[lines.length - 1] !== "") lines.push("");
20837
+ return lines.join("\n");
20838
+ }
20839
+ function renderActionItemsMarkdown(summary) {
20840
+ if (!summary.actionItems || summary.actionItems.length === 0) return null;
20841
+ const lines = ["# Action Items", ""];
20842
+ for (const item of summary.actionItems) {
20843
+ lines.push(`- ${item.who ? `${item.who} - ` : ""}${item.what}`);
20844
+ }
20845
+ lines.push("");
20846
+ return lines.join("\n");
20847
+ }
20848
+ function appendStringList(lines, title, values) {
20849
+ if (!values || values.length === 0) return;
20850
+ lines.push(`## ${title}`, "");
20851
+ for (const value of values) lines.push(`- ${value}`);
20852
+ lines.push("");
20853
+ }
20854
+ function appendActionItems(lines, values) {
20855
+ if (!values || values.length === 0) return;
20856
+ lines.push("## Action Items", "");
20857
+ for (const item of values) {
20858
+ lines.push(`- ${item.who ? `${item.who}: ` : ""}${item.what}`);
20859
+ }
20860
+ lines.push("");
20861
+ }
20862
+ function appendTimeline(lines, values) {
20863
+ if (!values || values.length === 0) return;
20864
+ lines.push("## Timeline", "");
20865
+ for (const item of values) {
20866
+ lines.push(
20867
+ `- ${formatTimestamp(item.startMs)}-${formatTimestamp(item.endMs)}: ${item.title} - ${item.summary}`
20868
+ );
20869
+ }
20870
+ lines.push("");
20871
+ }
20872
+ function appendQuotes(lines, values) {
20873
+ if (!values || values.length === 0) return;
20874
+ lines.push("## Quotes", "");
20875
+ for (const item of values) {
20876
+ lines.push(`- ${item.speaker ? `${item.speaker}: ` : ""}"${item.text}"`);
20877
+ }
20878
+ lines.push("");
20879
+ }
20880
+ function audioMetadata(audio) {
20881
+ return {
20882
+ ...audio.contentType ? { contentType: audio.contentType } : {},
20883
+ ...audio.contentLength !== void 0 ? { contentLength: audio.contentLength } : {},
20884
+ reused: audio.reused
20885
+ };
20886
+ }
20887
+ function formatTimestamp(ms) {
20888
+ const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
20889
+ const hours = Math.floor(totalSeconds / 3600);
20890
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
20891
+ const seconds = totalSeconds % 60;
20892
+ const mm = String(minutes).padStart(2, "0");
20893
+ const ss = String(seconds).padStart(2, "0");
20894
+ return hours > 0 ? `${hours}:${mm}:${ss}` : `${mm}:${ss}`;
20895
+ }
20896
+
20397
20897
  // src/progressStepper.ts
20398
20898
  var STEP_DEFS = [
20399
20899
  { key: "check", label: "Check" },
@@ -20888,6 +21388,28 @@ Next:
20888
21388
  }
20889
21389
  return;
20890
21390
  }
21391
+ if (command === "recordings export" && isRecord4(data)) {
21392
+ opts.stdout("Recording export ready\n");
21393
+ printStringField(opts, data, "recordingId");
21394
+ printStringField(opts, data, "exportDir");
21395
+ printStringField(opts, data, "textPath");
21396
+ printStringField(opts, data, "audioPath");
21397
+ printStringField(opts, data, "transcriptPath");
21398
+ printStringField(opts, data, "summaryPath");
21399
+ printStringField(opts, data, "actionItemsPath");
21400
+ printStringField(opts, data, "subscriptionPath");
21401
+ printStringField(opts, data, "transcriptJsonPath");
21402
+ printStringField(opts, data, "summaryJsonPath");
21403
+ printStringField(opts, data, "subscriptionJsonPath");
21404
+ printStringField(opts, data, "recordingJsonPath");
21405
+ printStringField(opts, data, "remoteManifestPath");
21406
+ printStringField(opts, data, "sessionMetadataPath");
21407
+ printStringField(opts, data, "manifestPath");
21408
+ if (typeof data.transcriptPath !== "string") {
21409
+ opts.stdout(" transcript: no active transcript\n");
21410
+ }
21411
+ return;
21412
+ }
20891
21413
  if (command === "recordings retranscribe" && isRecord4(data)) {
20892
21414
  opts.stdout("Transcription started\n");
20893
21415
  if (typeof data.recordingId === "string") opts.stdout(` recordingId: ${data.recordingId}
@@ -21265,6 +21787,10 @@ function recordingTitle(item) {
21265
21787
  function numberText(value) {
21266
21788
  return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US") : "0";
21267
21789
  }
21790
+ function printStringField(opts, data, field) {
21791
+ if (typeof data[field] === "string") opts.stdout(` ${field}: ${data[field]}
21792
+ `);
21793
+ }
21268
21794
  function formatDurationMs(ms) {
21269
21795
  return formatClock(ms / 1e3);
21270
21796
  }
@@ -21381,6 +21907,7 @@ var COMMAND_DATA_SCHEMAS = {
21381
21907
  upload: uploadBatchDataSchema,
21382
21908
  record: recordCommandDataSchema,
21383
21909
  "recordings get": recordingDataSchema,
21910
+ "recordings export": recordingExportDataSchema,
21384
21911
  "recordings list": recordingListDataSchema,
21385
21912
  "recordings retranscribe": recordingTranscribeDataSchema,
21386
21913
  "recordings resummarize": recordingSummarizeDataSchema,
@@ -21419,11 +21946,11 @@ function buildSchemaDocument(program) {
21419
21946
  event: toJsonSchema(operationEventSchema)
21420
21947
  };
21421
21948
  }
21422
- function walkCommands(command, path6, out) {
21949
+ function walkCommands(command, path7, out) {
21423
21950
  for (const sub of subcommandsOf(command)) {
21424
21951
  const name = sub.name();
21425
21952
  if (name === "help") continue;
21426
- const fullPath = [...path6, name];
21953
+ const fullPath = [...path7, name];
21427
21954
  const children = subcommandsOf(sub).filter((child) => child.name() !== "help");
21428
21955
  if (children.length === 0) {
21429
21956
  out.push(leafCommandDoc(sub, fullPath.join(" ")));
@@ -21825,8 +22352,8 @@ function requestLaunchServicesSidecarShutdown(input) {
21825
22352
  } catch {
21826
22353
  }
21827
22354
  }
21828
- function createFifo(path6) {
21829
- const result = spawnSync("mkfifo", [path6], { encoding: "utf8" });
22355
+ function createFifo(path7) {
22356
+ const result = spawnSync("mkfifo", [path7], { encoding: "utf8" });
21830
22357
  if (result.status !== 0) {
21831
22358
  throw cliError("record.helper_unavailable", "Recappi recording helper could not start.", {
21832
22359
  hint: result.stderr || "Could not create the local recorder pipes. Try again."
@@ -22450,9 +22977,9 @@ function resolveSidecarCommand(opts) {
22450
22977
  hint: `No bundled helper is registered for ${platform}. Set ${SIDECAR_COMMAND_ENV} to a compatible helper when one is available.`
22451
22978
  });
22452
22979
  }
22453
- function ensureBundledHelperExecutable(path6, opts = {}) {
22454
- if (process.platform === "darwin" && path6.endsWith(".app")) {
22455
- const stableApp = ensureStableDarwinHelperApp(path6, opts);
22980
+ function ensureBundledHelperExecutable(path7, opts = {}) {
22981
+ if (process.platform === "darwin" && path7.endsWith(".app")) {
22982
+ const stableApp = ensureStableDarwinHelperApp(path7, opts);
22456
22983
  const executable = darwinAppExecutablePath(stableApp);
22457
22984
  if (!existsSync(executable)) {
22458
22985
  throw cliError("record.helper_unavailable", "Recappi recording helper is not available.", {
@@ -22462,19 +22989,19 @@ function ensureBundledHelperExecutable(path6, opts = {}) {
22462
22989
  ensureExecutableMode(executable);
22463
22990
  return stableApp;
22464
22991
  }
22465
- if (process.platform === "win32") return path6;
22466
- ensureExecutableMode(path6);
22467
- return path6;
22992
+ if (process.platform === "win32") return path7;
22993
+ ensureExecutableMode(path7);
22994
+ return path7;
22468
22995
  }
22469
- function ensureExecutableMode(path6) {
22470
- const mode = statSync(path6).mode;
22996
+ function ensureExecutableMode(path7) {
22997
+ const mode = statSync(path7).mode;
22471
22998
  if ((mode & 73) !== 0) return;
22472
22999
  try {
22473
- chmodSync(path6, mode | 493);
23000
+ chmodSync(path7, mode | 493);
22474
23001
  } catch (error51) {
22475
23002
  const message = error51 instanceof Error ? error51.message : String(error51);
22476
23003
  throw cliError("record.helper_unavailable", "Recappi recording helper is not executable.", {
22477
- hint: `Could not make bundled helper executable at ${path6}: ${message}. Reinstall recappi, or set ${SIDECAR_COMMAND_ENV} to a compatible helper.`
23004
+ hint: `Could not make bundled helper executable at ${path7}: ${message}. Reinstall recappi, or set ${SIDECAR_COMMAND_ENV} to a compatible helper.`
22478
23005
  });
22479
23006
  }
22480
23007
  }
@@ -22519,14 +23046,14 @@ function helperSourceSignature(sourceApp) {
22519
23046
  codeSignature: existsSync(signaturePath) ? fileDigest(signaturePath) : null
22520
23047
  });
22521
23048
  }
22522
- function fileDigest(path6) {
23049
+ function fileDigest(path7) {
22523
23050
  const hash2 = createHash("sha256");
22524
- hash2.update(readFileSync2(path6));
23051
+ hash2.update(readFileSync2(path7));
22525
23052
  return hash2.digest("hex");
22526
23053
  }
22527
- function readTextIfExists(path6) {
23054
+ function readTextIfExists(path7) {
22528
23055
  try {
22529
- return readFileSync2(path6, "utf8");
23056
+ return readFileSync2(path7, "utf8");
22530
23057
  } catch {
22531
23058
  return null;
22532
23059
  }
@@ -22889,6 +23416,26 @@ async function runCli(deps = {}) {
22889
23416
  },
22890
23417
  retranscribeRecording: (recordingId, options = {}) => client.transcribeRecording({ recordingId, ...options }),
22891
23418
  resummarizeRecording: (recordingId) => client.summarizeRecording({ recordingId }),
23419
+ syncRecordingText: (recordingId) => syncRecordingText({
23420
+ recordingId,
23421
+ client,
23422
+ env: deps.env,
23423
+ homeDir: deps.homeDir
23424
+ }),
23425
+ syncRecordingAudio: (recordingId) => syncRecordingAudio({
23426
+ recordingId,
23427
+ client,
23428
+ recordingAudio,
23429
+ env: deps.env,
23430
+ homeDir: deps.homeDir
23431
+ }),
23432
+ exportRecording: (recordingId) => exportRecording({
23433
+ recordingId,
23434
+ client,
23435
+ recordingAudio,
23436
+ env: deps.env,
23437
+ homeDir: deps.homeDir
23438
+ }),
22892
23439
  initialView: parsed.initialView
22893
23440
  });
22894
23441
  return 0;
@@ -22919,7 +23466,7 @@ async function runCli(deps = {}) {
22919
23466
  return 0;
22920
23467
  }
22921
23468
  if (parsed.kind === "auth-logout") {
22922
- const cleared = await clearAuthConfig(deps.homeDir ?? os5.homedir());
23469
+ const cleared = await clearAuthConfig(deps.homeDir ?? os6.homedir());
22923
23470
  renderSuccess("auth logout", { loggedIn: false, origin: auth.origin, cleared }, render3);
22924
23471
  return 0;
22925
23472
  }
@@ -22930,7 +23477,7 @@ async function runCli(deps = {}) {
22930
23477
  hint: keychain.hint ?? "Run recappi auth login instead."
22931
23478
  });
22932
23479
  }
22933
- await saveAuthConfig(deps.homeDir ?? os5.homedir(), {
23480
+ await saveAuthConfig(deps.homeDir ?? os6.homedir(), {
22934
23481
  origin: auth.origin,
22935
23482
  token: keychain.token
22936
23483
  });
@@ -23079,6 +23626,29 @@ async function runCli(deps = {}) {
23079
23626
  renderSuccess("recordings get", data, render3);
23080
23627
  return 0;
23081
23628
  }
23629
+ if (parsed.kind === "recordings-export") {
23630
+ const status = await client.authStatus();
23631
+ if (!status.loggedIn || !status.userId) {
23632
+ throw cliError("auth.not_logged_in", "Sign in before exporting a recording bundle.", {
23633
+ hint: "Run recappi auth login, or import the Recappi Mini session with recappi auth import-macos."
23634
+ });
23635
+ }
23636
+ const recordingAudio = createRecordingAudioRuntime(client, {
23637
+ account: { backendOrigin: auth.origin, userId: status.userId },
23638
+ env: deps.env,
23639
+ homeDir: deps.homeDir
23640
+ });
23641
+ const data = await exportRecording({
23642
+ recordingId: parsed.recordingId,
23643
+ ...parsed.directory ? { directory: parsed.directory } : {},
23644
+ client,
23645
+ recordingAudio,
23646
+ env: deps.env,
23647
+ homeDir: deps.homeDir
23648
+ });
23649
+ renderSuccess("recordings export", data, render3);
23650
+ return 0;
23651
+ }
23082
23652
  if (parsed.kind === "recordings-retranscribe") {
23083
23653
  const eventMode = parsed.options.mode === "jsonl" ? "jsonl" : "human";
23084
23654
  const data = await client.transcribeRecording({
@@ -23434,6 +24004,20 @@ Agent mode:
23434
24004
  });
23435
24005
  }
23436
24006
  );
24007
+ 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"));
24008
+ addCommonOptions(recordingsExport);
24009
+ recordingsExport.action(
24010
+ (recordingId, _options, command) => {
24011
+ const opts = command.opts();
24012
+ onSelect({
24013
+ kind: "recordings-export",
24014
+ options: collectGlobalOptions(command),
24015
+ commandName: "recordings export",
24016
+ recordingId,
24017
+ ...typeof opts.dir === "string" ? { directory: opts.dir } : {}
24018
+ });
24019
+ }
24020
+ );
23437
24021
  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"));
23438
24022
  addCommonOptions(recordingsRetranscribe);
23439
24023
  recordingsRetranscribe.action(
@@ -23608,7 +24192,8 @@ var VALUE_OPTIONS = /* @__PURE__ */ new Set([
23608
24192
  "--status",
23609
24193
  "--limit",
23610
24194
  "--cursor",
23611
- "--search"
24195
+ "--search",
24196
+ "--dir"
23612
24197
  ]);
23613
24198
  function hasCommandToken(argv) {
23614
24199
  return commandTokens(argv).length > 0;