recappi 0.1.64 → 0.1.66

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
@@ -139,7 +139,7 @@ function formatClockMs(ms) {
139
139
  const ss = String(secs).padStart(2, "0");
140
140
  return hours > 0 ? `${hours}:${mm}:${ss}` : `${mm}:${ss}`;
141
141
  }
142
- function progressBar(fraction, width = 10) {
142
+ function progressBar2(fraction, width = 10) {
143
143
  const clamped = Math.max(0, Math.min(1, fraction));
144
144
  const filled = Math.round(clamped * width);
145
145
  return `[${"\u2588".repeat(filled)}${"\u2591".repeat(width - filled)}]`;
@@ -200,7 +200,7 @@ function jobDetail(item) {
200
200
  const fraction = transcribeFraction(item);
201
201
  if (fraction != null) {
202
202
  const pct2 = Math.round(fraction * 100);
203
- return `${progressBar(fraction)} ${String(pct2).padStart(3)}% ${formatClockMs(
203
+ return `${progressBar2(fraction)} ${String(pct2).padStart(3)}% ${formatClockMs(
204
204
  item.processedDurationMs
205
205
  )} / ${formatClockMs(item.recording?.durationMs)}`;
206
206
  }
@@ -335,7 +335,7 @@ function groupedListWindow(buckets, selected, budget) {
335
335
  }
336
336
  return listWindow(selected, total, 1);
337
337
  }
338
- function formatBytes2(bytes) {
338
+ function formatBytes3(bytes) {
339
339
  if (bytes == null || !Number.isFinite(bytes) || bytes < 0) return "";
340
340
  const units = ["B", "KB", "MB", "GB", "TB"];
341
341
  let value = bytes;
@@ -647,16 +647,16 @@ function Usage({
647
647
  ] }),
648
648
  /* @__PURE__ */ jsxs3(Text3, { children: [
649
649
  /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "Minutes " }),
650
- minutesCap != null ? /* @__PURE__ */ jsx5(Text3, { color: billing.isOverMinutes ? "red" : "cyan", children: `${progressBar(minutesUsed / Math.max(1, minutesCap), 12)} ` }) : null,
650
+ minutesCap != null ? /* @__PURE__ */ jsx5(Text3, { color: billing.isOverMinutes ? "red" : "cyan", children: `${progressBar2(minutesUsed / Math.max(1, minutesCap), 12)} ` }) : null,
651
651
  /* @__PURE__ */ jsx5(Text3, { color: billing.isOverMinutes ? "red" : void 0, children: `${Math.round(minutesUsed)}` }),
652
652
  /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: ` / ${minutesCap != null ? Math.round(minutesCap) : "\u221E"} min` }),
653
653
  /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: ` (batch ${Math.round(billing.batchMinutesUsed)} \xB7 live ${Math.round(billing.realtimeMinutesUsed)})` })
654
654
  ] }),
655
655
  /* @__PURE__ */ jsxs3(Text3, { children: [
656
656
  /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "Storage " }),
657
- storageCap != null ? /* @__PURE__ */ jsx5(Text3, { color: billing.isOverStorage ? "red" : "cyan", children: `${progressBar(billing.storageBytes / Math.max(1, storageCap), 12)} ` }) : null,
658
- /* @__PURE__ */ jsx5(Text3, { color: billing.isOverStorage ? "red" : void 0, children: formatBytes2(billing.storageBytes) }),
659
- /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: ` / ${storageCap != null ? formatBytes2(storageCap) : "\u221E"}` })
657
+ storageCap != null ? /* @__PURE__ */ jsx5(Text3, { color: billing.isOverStorage ? "red" : "cyan", children: `${progressBar2(billing.storageBytes / Math.max(1, storageCap), 12)} ` }) : null,
658
+ /* @__PURE__ */ jsx5(Text3, { color: billing.isOverStorage ? "red" : void 0, children: formatBytes3(billing.storageBytes) }),
659
+ /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: ` / ${storageCap != null ? formatBytes3(storageCap) : "\u221E"}` })
660
660
  ] }),
661
661
  billing.isOverMinutes || billing.isOverStorage ? /* @__PURE__ */ jsxs3(Text3, { color: "red", children: [
662
662
  billing.isOverMinutes ? "Over minutes limit. " : "",
@@ -907,7 +907,7 @@ function PeekBody({
907
907
  const style = recordingStatusStyle(item.status);
908
908
  const meta3 = [
909
909
  item.durationMs ? formatClockMs(item.durationMs) : null,
910
- formatBytes2(item.sizeBytes) || null,
910
+ formatBytes3(item.sizeBytes) || null,
911
911
  formatAge(item.createdAt, nowMs)
912
912
  ].filter(Boolean).join(" \xB7 ");
913
913
  return /* @__PURE__ */ jsxs8(Fragment3, { children: [
@@ -1108,7 +1108,7 @@ function StatusLine({
1108
1108
  if (fraction != null) {
1109
1109
  const pct2 = Math.round(fraction * 100);
1110
1110
  return /* @__PURE__ */ jsxs10(Text11, { children: [
1111
- `${progressBar(fraction)} ${pct2}% ${formatClockMs(item.processedDurationMs)} / ${formatClockMs(
1111
+ `${progressBar2(fraction)} ${pct2}% ${formatClockMs(item.processedDurationMs)} / ${formatClockMs(
1112
1112
  item.recording?.durationMs
1113
1113
  )}`,
1114
1114
  /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: elapsed })
@@ -1169,7 +1169,7 @@ function RecordingDetailView({
1169
1169
  const title = recordingTitle2(item);
1170
1170
  const meta3 = [
1171
1171
  item.durationMs ? formatClockMs(item.durationMs) : void 0,
1172
- formatBytes2(item.sizeBytes) || void 0,
1172
+ formatBytes3(item.sizeBytes) || void 0,
1173
1173
  item.contentType || void 0
1174
1174
  ].filter(Boolean).join(" \xB7 ");
1175
1175
  const ready = typeof transcript === "object";
@@ -1729,7 +1729,7 @@ function RecordFrame({
1729
1729
  ...captions.lines.filter((l) => l.translation).map((l) => trimLead2(l.translation)),
1730
1730
  ...captions.translationPartial ? [trimLead2(captions.translationPartial)] : []
1731
1731
  ] : [];
1732
- const status = telemetry.sizeBytes ? formatBytes2(telemetry.sizeBytes) : "";
1732
+ const status = telemetry.sizeBytes ? formatBytes3(telemetry.sizeBytes) : "";
1733
1733
  const sourceLine = [telemetry.sourceLabel, telemetry.micEnabled ? "Microphone" : null, status || null].filter(Boolean).join(" \xB7 ");
1734
1734
  return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", paddingX: 1, height: size.rows, children: [
1735
1735
  /* @__PURE__ */ jsxs15(Box16, { justifyContent: "space-between", children: [
@@ -18657,7 +18657,7 @@ import os3 from "os";
18657
18657
  import path3 from "path";
18658
18658
  import { createRequire } from "module";
18659
18659
  var require2 = createRequire(import.meta.url);
18660
- var Database = require2("better-sqlite3");
18660
+ var sqliteModule = null;
18661
18661
  var CLI_STORE_SCHEMA_VERSION = 2;
18662
18662
  function defaultStorePath(homeDir = os3.homedir(), env = process.env) {
18663
18663
  const explicit = env.RECAPPI_CLI_STORE_PATH?.trim();
@@ -18689,7 +18689,7 @@ var CliLocalStore = class {
18689
18689
  if (!opts.readonly && dbPath !== ":memory:") {
18690
18690
  mkdirSync(path3.dirname(dbPath), { recursive: true, mode: 448 });
18691
18691
  }
18692
- this.db = new Database(dbPath, opts.readonly === true ? { readonly: true } : void 0);
18692
+ this.db = openSqliteDatabase(dbPath, { readonly: opts.readonly === true });
18693
18693
  this.now = opts.now ?? Date.now;
18694
18694
  if (!opts.readonly) this.migrate();
18695
18695
  }
@@ -18950,6 +18950,44 @@ function parseAccountPartition(input, mode) {
18950
18950
  return null;
18951
18951
  }
18952
18952
  }
18953
+ function openSqliteDatabase(filename, opts) {
18954
+ const { DatabaseSync } = loadNodeSqlite();
18955
+ const db = new DatabaseSync(filename, opts.readonly ? { readOnly: true } : {});
18956
+ return {
18957
+ exec: (source) => db.exec(source),
18958
+ prepare: (source) => db.prepare(source),
18959
+ pragma: (source) => db.prepare(`PRAGMA ${source.trim().replace(/;$/, "")}`).all(),
18960
+ close: () => db.close()
18961
+ };
18962
+ }
18963
+ function loadNodeSqlite() {
18964
+ if (sqliteModule) return sqliteModule;
18965
+ sqliteModule = suppressNodeSqliteWarning(() => require2("node:sqlite"));
18966
+ return sqliteModule;
18967
+ }
18968
+ function suppressNodeSqliteWarning(run) {
18969
+ const emitWarning = process.emitWarning;
18970
+ process.emitWarning = function emitWarningWithoutNodeSqliteNoise(warning, ...args) {
18971
+ const message = typeof warning === "string" ? warning : warning.message;
18972
+ if (message.includes("SQLite is an experimental feature")) return;
18973
+ return emitWarning.call(process, warning, ...args);
18974
+ };
18975
+ try {
18976
+ return run();
18977
+ } catch (error51) {
18978
+ if (isMissingNodeSqlite(error51)) {
18979
+ throw cliError("internal.unexpected", "Local SQLite store requires Node.js 22 or newer.", {
18980
+ hint: "Upgrade Node.js, then retry. Cloud-only commands that do not touch local state can still run."
18981
+ });
18982
+ }
18983
+ throw error51;
18984
+ } finally {
18985
+ process.emitWarning = emitWarning;
18986
+ }
18987
+ }
18988
+ function isMissingNodeSqlite(error51) {
18989
+ return error51 instanceof Error && ("code" in error51 ? error51.code === "ERR_UNKNOWN_BUILTIN_MODULE" : false);
18990
+ }
18953
18991
  function cleanString(value) {
18954
18992
  const trimmed = value?.trim();
18955
18993
  return trimmed ? trimmed : null;
@@ -20235,13 +20273,240 @@ ${lines.join("\n")}
20235
20273
  ` : "";
20236
20274
  }
20237
20275
 
20276
+ // src/progressStepper.ts
20277
+ var STEP_DEFS = [
20278
+ { key: "check", label: "Check" },
20279
+ { key: "upload", label: "Upload" },
20280
+ { key: "transcribe", label: "Transcribe" },
20281
+ { key: "done", label: "Done" }
20282
+ ];
20283
+ var SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
20284
+ var BAR_WIDTH = 16;
20285
+ var LABEL_WIDTH = 11;
20286
+ function createStepperModel() {
20287
+ return {
20288
+ steps: STEP_DEFS.map((d) => ({ ...d, status: "pending", detail: "" })),
20289
+ finished: false
20290
+ };
20291
+ }
20292
+ function isStepperEvent(event) {
20293
+ if (event.command !== "upload") return false;
20294
+ if (event.type === "started") return true;
20295
+ return typeof event.status === "string";
20296
+ }
20297
+ function cloneModel(model) {
20298
+ return {
20299
+ steps: model.steps.map((s) => ({ ...s })),
20300
+ ...model.transcribeStartMs != null ? { transcribeStartMs: model.transcribeStartMs } : {},
20301
+ finished: model.finished
20302
+ };
20303
+ }
20304
+ function applyStepperEvent(model, event, nowMs) {
20305
+ const next = cloneModel(model);
20306
+ const step = (key) => next.steps.find((s) => s.key === key);
20307
+ const markDone = (key) => {
20308
+ const s = step(key);
20309
+ if (s.status !== "failed") {
20310
+ s.status = "done";
20311
+ s.percent = void 0;
20312
+ }
20313
+ };
20314
+ const startTranscribeClock = () => {
20315
+ if (next.transcribeStartMs == null) next.transcribeStartMs = nowMs;
20316
+ };
20317
+ switch (event.status) {
20318
+ case "checking_audio":
20319
+ step("check").status = "active";
20320
+ break;
20321
+ case "starting_upload": {
20322
+ markDone("check");
20323
+ const file2 = fileFromEvent(event);
20324
+ if (file2) step("check").detail = file2;
20325
+ step("upload").status = "active";
20326
+ step("upload").detail = "Starting\u2026";
20327
+ break;
20328
+ }
20329
+ case "uploading":
20330
+ step("upload").status = "active";
20331
+ step("upload").detail = "";
20332
+ if (typeof event.percent === "number") step("upload").percent = event.percent;
20333
+ break;
20334
+ case "finishing_upload":
20335
+ step("upload").status = "active";
20336
+ step("upload").detail = "Finalizing";
20337
+ step("upload").percent = 100;
20338
+ break;
20339
+ case "uploaded":
20340
+ markDone("upload");
20341
+ step("upload").detail = recordingUrl(event) ?? "Uploaded";
20342
+ break;
20343
+ case "starting_transcription":
20344
+ step("transcribe").status = "active";
20345
+ step("transcribe").detail = "Starting\u2026";
20346
+ startTranscribeClock();
20347
+ break;
20348
+ case "queued":
20349
+ step("transcribe").status = "active";
20350
+ step("transcribe").detail = "Queued";
20351
+ startTranscribeClock();
20352
+ break;
20353
+ case "running":
20354
+ step("transcribe").status = "active";
20355
+ startTranscribeClock();
20356
+ if (typeof event.percent === "number") {
20357
+ step("transcribe").percent = event.percent;
20358
+ step("transcribe").detail = "";
20359
+ } else {
20360
+ step("transcribe").percent = void 0;
20361
+ step("transcribe").detail = "Transcribing\u2026";
20362
+ }
20363
+ break;
20364
+ case "succeeded":
20365
+ markDone("transcribe");
20366
+ markDone("done");
20367
+ if (event.transcriptId) {
20368
+ step("done").detail = `\u2192 recappi transcript get ${event.transcriptId}`;
20369
+ }
20370
+ next.finished = true;
20371
+ break;
20372
+ case "failed":
20373
+ markActiveFailed(next, event.message);
20374
+ break;
20375
+ default:
20376
+ if (event.type === "started") step("check").status = "active";
20377
+ break;
20378
+ }
20379
+ return next;
20380
+ }
20381
+ function markStepperFailed(model, message) {
20382
+ const next = cloneModel(model);
20383
+ markActiveFailed(next, message);
20384
+ return next;
20385
+ }
20386
+ function completeStepperModel(model, next) {
20387
+ const m = cloneModel(model);
20388
+ const transcribe = m.steps.find((s) => s.key === "transcribe");
20389
+ if (transcribe.status === "pending") {
20390
+ transcribe.status = "skipped";
20391
+ transcribe.detail = "no --transcribe";
20392
+ }
20393
+ for (const s of m.steps) {
20394
+ if (s.status === "active" || s.status === "pending") s.status = "done";
20395
+ s.percent = void 0;
20396
+ }
20397
+ const done = m.steps.find((s) => s.key === "done");
20398
+ if (next?.transcriptId) done.detail = `\u2192 recappi transcript get ${next.transcriptId}`;
20399
+ else if (next?.recordingId) done.detail = `\u2192 recappi recordings get ${next.recordingId}`;
20400
+ m.finished = true;
20401
+ return m;
20402
+ }
20403
+ function markActiveFailed(model, message) {
20404
+ const active = model.steps.find((s) => s.status === "active");
20405
+ const target = active ?? model.steps.find((s) => s.status === "pending");
20406
+ if (target) {
20407
+ target.status = "failed";
20408
+ if (message) target.detail = message;
20409
+ }
20410
+ model.finished = true;
20411
+ }
20412
+ function formatStepperLines(model, nowMs, spinnerFrame, color) {
20413
+ return model.steps.map((s) => {
20414
+ const marker = markerFor(s, spinnerFrame);
20415
+ const label = s.label.padEnd(LABEL_WIDTH);
20416
+ let detail = s.detail;
20417
+ if (s.status === "active" && typeof s.percent === "number") {
20418
+ detail = `${progressBar(s.percent)} ${clampPercent2(s.percent)}%`;
20419
+ }
20420
+ if (s.key === "transcribe" && s.status === "active" && model.transcribeStartMs != null) {
20421
+ const elapsed = formatElapsed(Math.max(0, nowMs - model.transcribeStartMs));
20422
+ detail = detail ? `${detail} \xB7 ${elapsed}` : elapsed;
20423
+ }
20424
+ const body = ` ${marker} ${label}${detail}`.trimEnd();
20425
+ return color ? colorize(s.status, marker, body) : body;
20426
+ });
20427
+ }
20428
+ function markerFor(step, spinnerFrame) {
20429
+ switch (step.status) {
20430
+ case "done":
20431
+ return "\u2713";
20432
+ case "failed":
20433
+ return "\u2717";
20434
+ case "active":
20435
+ return SPINNER[spinnerFrame % SPINNER.length];
20436
+ case "skipped":
20437
+ return "\xB7";
20438
+ default:
20439
+ return "\u25CB";
20440
+ }
20441
+ }
20442
+ function colorize(status, marker, line) {
20443
+ const code = status === "done" ? "32" : status === "failed" ? "31" : status === "active" ? "36" : "90";
20444
+ const colored = `\x1B[${code}m${marker}\x1B[0m`;
20445
+ return line.replace(marker, colored);
20446
+ }
20447
+ function progressBar(percent) {
20448
+ const p = clampPercent2(percent);
20449
+ const filled = Math.round(p / 100 * BAR_WIDTH);
20450
+ return "\u2588".repeat(filled) + "\u2591".repeat(BAR_WIDTH - filled);
20451
+ }
20452
+ function clampPercent2(percent) {
20453
+ return Math.max(0, Math.min(100, Math.round(percent)));
20454
+ }
20455
+ function fileFromEvent(event) {
20456
+ const data = event.data;
20457
+ if (!data || typeof data !== "object") return void 0;
20458
+ const file2 = data.file;
20459
+ if (!file2 || typeof file2 !== "object") return void 0;
20460
+ const f = file2;
20461
+ const parts = [];
20462
+ if (typeof f.title === "string" && f.title) parts.push(f.title);
20463
+ if (typeof f.sizeBytes === "number") parts.push(formatBytes(f.sizeBytes));
20464
+ if (typeof f.durationMs === "number") parts.push(formatMediaDuration(f.durationMs));
20465
+ return parts.length ? parts.join(" \xB7 ") : void 0;
20466
+ }
20467
+ function recordingUrl(event) {
20468
+ if (typeof event.message === "string" && event.message.includes("/recordings/")) {
20469
+ const idx = event.message.indexOf("http");
20470
+ if (idx >= 0) return event.message.slice(idx);
20471
+ }
20472
+ if (event.origin && event.recordingId) {
20473
+ return `${event.origin.replace(/\/+$/, "")}/recordings/${event.recordingId}`;
20474
+ }
20475
+ return void 0;
20476
+ }
20477
+ function formatBytes(bytes) {
20478
+ if (bytes >= 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
20479
+ if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
20480
+ if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;
20481
+ return `${bytes} B`;
20482
+ }
20483
+ function formatMediaDuration(ms) {
20484
+ const totalSec = Math.round(ms / 1e3);
20485
+ const h = Math.floor(totalSec / 3600);
20486
+ const m = Math.floor(totalSec % 3600 / 60);
20487
+ const s = totalSec % 60;
20488
+ if (h > 0) return `${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
20489
+ return `${m}:${String(s).padStart(2, "0")}`;
20490
+ }
20491
+ function formatElapsed(ms) {
20492
+ const totalSec = Math.floor(ms / 1e3);
20493
+ const h = Math.floor(totalSec / 3600);
20494
+ const m = Math.floor(totalSec % 3600 / 60);
20495
+ const s = totalSec % 60;
20496
+ const clock = h > 0 ? `${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}` : `${m}:${String(s).padStart(2, "0")}`;
20497
+ return `${clock} elapsed`;
20498
+ }
20499
+
20238
20500
  // src/render.ts
20239
20501
  function createHumanProgressState(interactive) {
20240
20502
  return {
20241
20503
  interactive,
20242
20504
  activeLineLength: 0,
20243
20505
  lastLineByScope: /* @__PURE__ */ new Map(),
20244
- lastUploadBucketByScope: /* @__PURE__ */ new Map()
20506
+ lastUploadBucketByScope: /* @__PURE__ */ new Map(),
20507
+ stepperLinesDrawn: 0,
20508
+ spinnerFrame: 0,
20509
+ stepperShown: false
20245
20510
  };
20246
20511
  }
20247
20512
  function renderSuccess(command, data, opts) {
@@ -20263,6 +20528,7 @@ function renderSuccess(command, data, opts) {
20263
20528
  renderEnvelope(envelope, opts);
20264
20529
  return;
20265
20530
  }
20531
+ finalizeStepperSuccess(command, filtered, opts);
20266
20532
  finishHumanProgress(opts);
20267
20533
  renderHumanSuccess(command, filtered, opts);
20268
20534
  }
@@ -20291,6 +20557,7 @@ function renderFailure(command, error51, opts, data) {
20291
20557
  renderEnvelope(envelope, opts);
20292
20558
  return;
20293
20559
  }
20560
+ if (finalizeStepperFailure(command, error51, opts, data)) return;
20294
20561
  finishHumanProgress(opts);
20295
20562
  opts.stderr(`recappi: ${error51.message}
20296
20563
  `);
@@ -20316,6 +20583,10 @@ function renderEvent(event, opts) {
20316
20583
  return;
20317
20584
  }
20318
20585
  if ((event.type === "started" || event.type === "progress") && opts.mode === "human") {
20586
+ if (opts.progress?.interactive && isStepperEvent(event)) {
20587
+ renderStepperEvent(event, opts);
20588
+ return;
20589
+ }
20319
20590
  const line = formatHumanProgress(event, opts);
20320
20591
  if (line) {
20321
20592
  if (isPersistentProgressEvent(event)) writePersistentHumanProgress(line, opts);
@@ -20323,6 +20594,56 @@ function renderEvent(event, opts) {
20323
20594
  }
20324
20595
  }
20325
20596
  }
20597
+ function renderStepperEvent(event, opts) {
20598
+ const state = opts.progress;
20599
+ if (!state) return;
20600
+ if (!state.stepper) state.stepper = createStepperModel();
20601
+ state.stepper = applyStepperEvent(state.stepper, event, Date.now());
20602
+ state.spinnerFrame += 1;
20603
+ drawStepperBlock(state, opts);
20604
+ }
20605
+ function drawStepperBlock(state, opts) {
20606
+ if (!state.stepper) return;
20607
+ const lines = formatStepperLines(state.stepper, Date.now(), state.spinnerFrame, true);
20608
+ let out = "";
20609
+ if (state.stepperLinesDrawn > 0) out += `\x1B[${state.stepperLinesDrawn}A`;
20610
+ for (const line of lines) out += `\x1B[2K${line}
20611
+ `;
20612
+ opts.stderr(out);
20613
+ state.stepperLinesDrawn = lines.length;
20614
+ state.stepperShown = true;
20615
+ }
20616
+ function finalizeStepperSuccess(command, data, opts) {
20617
+ const state = opts.progress;
20618
+ if (!state?.stepper || state.stepperLinesDrawn === 0 || command !== "upload") return;
20619
+ const success2 = firstUploadSuccess(data);
20620
+ state.stepper = completeStepperModel(state.stepper, success2);
20621
+ drawStepperBlock(state, opts);
20622
+ }
20623
+ function firstUploadSuccess(data) {
20624
+ if (!isUploadBatch(data) || data.successes.length === 0) return void 0;
20625
+ const s = data.successes[0];
20626
+ return {
20627
+ ...s.transcriptId ? { transcriptId: s.transcriptId } : {},
20628
+ ...s.recordingId ? { recordingId: s.recordingId } : {}
20629
+ };
20630
+ }
20631
+ function finalizeStepperFailure(command, error51, opts, data) {
20632
+ const state = opts.progress;
20633
+ if (!state?.stepper || state.stepperLinesDrawn === 0) return false;
20634
+ state.stepper = markStepperFailed(state.stepper, stepperFailureDetail(command, error51, data));
20635
+ drawStepperBlock(state, opts);
20636
+ state.stepperLinesDrawn = 0;
20637
+ state.stepper = void 0;
20638
+ return true;
20639
+ }
20640
+ function stepperFailureDetail(command, error51, data) {
20641
+ if (command === "upload" && isUploadBatch(data) && data.failures.length > 0) {
20642
+ const first = data.failures[0].error;
20643
+ return `${first.message} (${first.code})`;
20644
+ }
20645
+ return `${error51.message} (${error51.code})`;
20646
+ }
20326
20647
  function renderEnvelope(envelope, opts) {
20327
20648
  const parsed = cliEnvelopeSchema.parse(envelope);
20328
20649
  opts.stdout(`${stableStringify(parsed, opts.compact === true)}
@@ -20372,7 +20693,7 @@ function renderHumanSuccess(command, data, opts) {
20372
20693
  }
20373
20694
  if (typeof billing.storageBytes === "number") {
20374
20695
  const cap = formatNullableCap(billing.storageCapBytes, "bytes");
20375
- opts.stdout(` storage: ${formatBytes(billing.storageBytes)} / ${cap}
20696
+ opts.stdout(` storage: ${formatBytes2(billing.storageBytes)} / ${cap}
20376
20697
  `);
20377
20698
  }
20378
20699
  const localStore = isRecord4(data.localStore) ? data.localStore : {};
@@ -20434,7 +20755,7 @@ Next cursor: ${data.nextCursor}
20434
20755
  if (typeof data.durationMs === "number")
20435
20756
  opts.stdout(` duration: ${formatDurationMs(data.durationMs)}
20436
20757
  `);
20437
- if (typeof data.sizeBytes === "number") opts.stdout(` size: ${formatBytes(data.sizeBytes)}
20758
+ if (typeof data.sizeBytes === "number") opts.stdout(` size: ${formatBytes2(data.sizeBytes)}
20438
20759
  `);
20439
20760
  if (typeof data.activeTranscriptId === "string") {
20440
20761
  opts.stdout(` activeTranscriptId: ${data.activeTranscriptId}
@@ -20483,6 +20804,7 @@ Next:
20483
20804
  return;
20484
20805
  }
20485
20806
  if (command === "upload" && isUploadBatch(data)) {
20807
+ if (opts.progress?.stepperShown && data.failures.length === 0) return;
20486
20808
  if (data.successes.length > 0) {
20487
20809
  opts.stdout(uploadSuccessHeading(data.successes));
20488
20810
  }
@@ -20708,7 +21030,13 @@ function writePersistentHumanProgress(line, opts) {
20708
21030
  }
20709
21031
  function finishHumanProgress(opts) {
20710
21032
  const state = opts.progress;
20711
- if (!state?.interactive || state.activeLineLength === 0) return;
21033
+ if (!state) return;
21034
+ if (state.stepperLinesDrawn > 0) {
21035
+ state.stepperLinesDrawn = 0;
21036
+ state.stepper = void 0;
21037
+ return;
21038
+ }
21039
+ if (!state.interactive || state.activeLineLength === 0) return;
20712
21040
  opts.stderr("\n");
20713
21041
  state.activeLineLength = 0;
20714
21042
  }
@@ -20801,7 +21129,7 @@ function numberText(value) {
20801
21129
  function formatDurationMs(ms) {
20802
21130
  return formatClock(ms / 1e3);
20803
21131
  }
20804
- function formatBytes(bytes) {
21132
+ function formatBytes2(bytes) {
20805
21133
  if (bytes < 1024) return `${bytes} B`;
20806
21134
  const units = ["KB", "MB", "GB", "TB"];
20807
21135
  let value = bytes / 1024;
@@ -20816,7 +21144,7 @@ function formatBytes(bytes) {
20816
21144
  function formatNullableCap(value, unit) {
20817
21145
  if (value === null || value === void 0) return "Unlimited";
20818
21146
  if (typeof value !== "number" || !Number.isFinite(value)) return "Unlimited";
20819
- return unit === "bytes" ? formatBytes(value) : String(value);
21147
+ return unit === "bytes" ? formatBytes2(value) : String(value);
20820
21148
  }
20821
21149
  function formatClock(seconds) {
20822
21150
  const total = Math.max(0, Math.floor(seconds));
@@ -21475,7 +21803,7 @@ function RecordingHeroScreen({
21475
21803
  const phase = stoppedPhase(artifact);
21476
21804
  const meta3 = [
21477
21805
  telemetry.durationMs != null ? formatClockMs(telemetry.durationMs) : null,
21478
- formatBytes2(telemetry.sizeBytes) || null
21806
+ formatBytes3(telemetry.sizeBytes) || null
21479
21807
  ].filter(Boolean).join(" \xB7 ");
21480
21808
  const saved = artifact?.uploadStatus === "uploaded" ? "\u2713 Saved to Recappi Cloud" : "\u2713 Saved to your Mac";
21481
21809
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
@@ -21510,7 +21838,7 @@ function RecordingHeroScreen({
21510
21838
  const starting = telemetry.status === "starting" || telemetry.status === "stopping";
21511
21839
  const badge = paused ? "\u23F8 PAUSED" : starting ? "\u2026" : "\u23FA REC";
21512
21840
  const meterW = Math.max(10, Math.min(72, innerWidth - 20));
21513
- const sizeStr = telemetry.sizeBytes ? formatBytes2(telemetry.sizeBytes) : "";
21841
+ const sizeStr = telemetry.sizeBytes ? formatBytes3(telemetry.sizeBytes) : "";
21514
21842
  const context = [telemetry.sourceLabel, telemetry.micEnabled ? "Microphone" : null, sizeStr || null].filter(Boolean).join(" \xB7 ");
21515
21843
  const waveRows = waveRowsFor(size.rows);
21516
21844
  const meterBlockRows = (telemetry.micEnabled ? 2 : 1) * (waveRows + 1) + (telemetry.micEnabled ? 1 : 0);