recappi 0.1.58 → 0.1.60

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
@@ -1880,6 +1880,8 @@ function transcribeHandoffErrorCopy(error51) {
1880
1880
  return "Your Recappi session needs attention. Sign in and retry.";
1881
1881
  case "input.not_found":
1882
1882
  return "The local recording file is no longer available.";
1883
+ case "input.permission_denied":
1884
+ return "Recappi CLI cannot read the saved recording file yet.";
1883
1885
  case "input.not_file":
1884
1886
  return "The saved recording is not a readable audio file.";
1885
1887
  case "input.unsupported_audio":
@@ -17449,6 +17451,7 @@ var cliErrorCodeSchema = external_exports.enum([
17449
17451
  "auth.not_logged_in",
17450
17452
  "auth.unauthorized",
17451
17453
  "input.not_found",
17454
+ "input.permission_denied",
17452
17455
  "input.not_file",
17453
17456
  "input.unsupported_audio",
17454
17457
  "input.duration_unavailable",
@@ -18049,6 +18052,7 @@ var DEFAULT_EXIT_CODES = {
18049
18052
  "auth.not_logged_in": 3,
18050
18053
  "auth.unauthorized": 3,
18051
18054
  "input.not_found": 4,
18055
+ "input.permission_denied": 4,
18052
18056
  "input.not_file": 4,
18053
18057
  "input.unsupported_audio": 4,
18054
18058
  "input.duration_unavailable": 4,
@@ -18536,8 +18540,19 @@ async function planAudioFile(filePath, titleOverride) {
18536
18540
  let stat;
18537
18541
  try {
18538
18542
  stat = await fs2.stat(filePath);
18539
- } catch {
18540
- throw cliError("input.not_found", `Path not found: ${filePath}`);
18543
+ } catch (error51) {
18544
+ if (isNodeErrorCode(error51, "ENOENT") || isNodeErrorCode(error51, "ENOTDIR")) {
18545
+ throw cliError("input.not_found", `Path not found: ${filePath}`);
18546
+ }
18547
+ if (isNodeErrorCode(error51, "EACCES") || isNodeErrorCode(error51, "EPERM")) {
18548
+ throw cliError("input.permission_denied", `Permission denied reading path: ${filePath}`, {
18549
+ hint: "Grant this terminal/agent access to the file, or copy the audio to a readable location."
18550
+ });
18551
+ }
18552
+ throw cliError(
18553
+ "internal.unexpected",
18554
+ error51 instanceof Error ? error51.message : `Could not inspect path: ${filePath}`
18555
+ );
18541
18556
  }
18542
18557
  if (!stat.isFile()) {
18543
18558
  throw cliError("input.not_file", `Path is not a file: ${filePath}`, {
@@ -18560,6 +18575,9 @@ async function planAudioFile(filePath, titleOverride) {
18560
18575
  ...durationMs ? { durationMs } : {}
18561
18576
  };
18562
18577
  }
18578
+ function isNodeErrorCode(error51, code) {
18579
+ return typeof error51 === "object" && error51 !== null && "code" in error51 && error51.code === code;
18580
+ }
18563
18581
  async function readDurationMs(filePath, contentType) {
18564
18582
  if (contentType === "audio/wav") {
18565
18583
  const handle = await fs2.open(filePath, "r");
@@ -20153,6 +20171,14 @@ function renderFailure(command, error51, opts, data) {
20153
20171
  finishHumanProgress(opts);
20154
20172
  opts.stderr(`recappi: ${error51.message}
20155
20173
  `);
20174
+ if (command === "upload" && isUploadBatch(data) && data.failures.length > 0) {
20175
+ opts.stderr("Failures:\n");
20176
+ for (const item of data.failures) {
20177
+ const label = humanFileLabel(item.filePath) ?? item.filePath;
20178
+ opts.stderr(` ${label}: ${item.error.message} (${item.error.code})
20179
+ `);
20180
+ }
20181
+ }
20156
20182
  if (error51.hint) opts.stderr(`${error51.hint}
20157
20183
  `);
20158
20184
  }