recappi 0.1.59 → 0.1.61
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 +127 -11
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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",
|
|
@@ -17865,12 +17868,19 @@ var recordingTranscribeDataSchema = external_exports.object({
|
|
|
17865
17868
|
});
|
|
17866
17869
|
var jobDataSchema = external_exports.object({
|
|
17867
17870
|
jobId: external_exports.string(),
|
|
17871
|
+
origin: external_exports.string().optional(),
|
|
17868
17872
|
recordingId: external_exports.string().optional(),
|
|
17869
17873
|
transcriptId: external_exports.string().nullable().optional(),
|
|
17870
17874
|
status: transcriptionJobStatusSchema,
|
|
17871
17875
|
provider: external_exports.string().optional(),
|
|
17872
17876
|
model: external_exports.string().optional(),
|
|
17873
|
-
language: external_exports.string().nullable().optional()
|
|
17877
|
+
language: external_exports.string().nullable().optional(),
|
|
17878
|
+
progressPercent: external_exports.number().min(0).max(100).nullable().optional(),
|
|
17879
|
+
processedDurationMs: external_exports.number().int().nonnegative().nullable().optional(),
|
|
17880
|
+
recording: external_exports.object({
|
|
17881
|
+
title: external_exports.string().nullable().optional(),
|
|
17882
|
+
durationMs: external_exports.number().int().nonnegative().nullable().optional()
|
|
17883
|
+
}).optional()
|
|
17874
17884
|
});
|
|
17875
17885
|
var jobStatusFilterSchema = external_exports.enum([
|
|
17876
17886
|
"active",
|
|
@@ -18030,6 +18040,7 @@ var operationEventTypeSchema = external_exports.enum(["started", "progress", "re
|
|
|
18030
18040
|
var operationEventSchema = external_exports.object({
|
|
18031
18041
|
type: operationEventTypeSchema,
|
|
18032
18042
|
command: external_exports.string(),
|
|
18043
|
+
origin: external_exports.string().optional(),
|
|
18033
18044
|
filePath: external_exports.string().optional(),
|
|
18034
18045
|
recordingId: external_exports.string().optional(),
|
|
18035
18046
|
jobId: external_exports.string().optional(),
|
|
@@ -18049,6 +18060,7 @@ var DEFAULT_EXIT_CODES = {
|
|
|
18049
18060
|
"auth.not_logged_in": 3,
|
|
18050
18061
|
"auth.unauthorized": 3,
|
|
18051
18062
|
"input.not_found": 4,
|
|
18063
|
+
"input.permission_denied": 4,
|
|
18052
18064
|
"input.not_file": 4,
|
|
18053
18065
|
"input.unsupported_audio": 4,
|
|
18054
18066
|
"input.duration_unavailable": 4,
|
|
@@ -18536,8 +18548,19 @@ async function planAudioFile(filePath, titleOverride) {
|
|
|
18536
18548
|
let stat;
|
|
18537
18549
|
try {
|
|
18538
18550
|
stat = await fs2.stat(filePath);
|
|
18539
|
-
} catch {
|
|
18540
|
-
|
|
18551
|
+
} catch (error51) {
|
|
18552
|
+
if (isNodeErrorCode(error51, "ENOENT") || isNodeErrorCode(error51, "ENOTDIR")) {
|
|
18553
|
+
throw cliError("input.not_found", `Path not found: ${filePath}`);
|
|
18554
|
+
}
|
|
18555
|
+
if (isNodeErrorCode(error51, "EACCES") || isNodeErrorCode(error51, "EPERM")) {
|
|
18556
|
+
throw cliError("input.permission_denied", `Permission denied reading path: ${filePath}`, {
|
|
18557
|
+
hint: "Grant this terminal/agent access to the file, or copy the audio to a readable location."
|
|
18558
|
+
});
|
|
18559
|
+
}
|
|
18560
|
+
throw cliError(
|
|
18561
|
+
"internal.unexpected",
|
|
18562
|
+
error51 instanceof Error ? error51.message : `Could not inspect path: ${filePath}`
|
|
18563
|
+
);
|
|
18541
18564
|
}
|
|
18542
18565
|
if (!stat.isFile()) {
|
|
18543
18566
|
throw cliError("input.not_file", `Path is not a file: ${filePath}`, {
|
|
@@ -18560,6 +18583,9 @@ async function planAudioFile(filePath, titleOverride) {
|
|
|
18560
18583
|
...durationMs ? { durationMs } : {}
|
|
18561
18584
|
};
|
|
18562
18585
|
}
|
|
18586
|
+
function isNodeErrorCode(error51, code) {
|
|
18587
|
+
return typeof error51 === "object" && error51 !== null && "code" in error51 && error51.code === code;
|
|
18588
|
+
}
|
|
18563
18589
|
async function readDurationMs(filePath, contentType) {
|
|
18564
18590
|
if (contentType === "audio/wav") {
|
|
18565
18591
|
const handle = await fs2.open(filePath, "r");
|
|
@@ -19259,9 +19285,12 @@ var RecappiApiClient = class {
|
|
|
19259
19285
|
opts.onEvent?.({
|
|
19260
19286
|
type: "progress",
|
|
19261
19287
|
command: "jobs wait",
|
|
19288
|
+
origin: this.auth.origin,
|
|
19262
19289
|
jobId,
|
|
19290
|
+
...job.recordingId ? { recordingId: job.recordingId } : {},
|
|
19263
19291
|
status: job.status,
|
|
19264
|
-
...job.transcriptId ? { transcriptId: job.transcriptId } : {}
|
|
19292
|
+
...job.transcriptId ? { transcriptId: job.transcriptId } : {},
|
|
19293
|
+
...typeof job.progressPercent === "number" ? { percent: job.progressPercent } : {}
|
|
19265
19294
|
});
|
|
19266
19295
|
if (job.status === "succeeded") return job;
|
|
19267
19296
|
if (job.status === "failed") {
|
|
@@ -19299,6 +19328,15 @@ var RecappiApiClient = class {
|
|
|
19299
19328
|
message: "Finishing upload"
|
|
19300
19329
|
});
|
|
19301
19330
|
await this.postJson(`/api/recordings/${init.id}/complete`, { parts });
|
|
19331
|
+
opts.onEvent?.({
|
|
19332
|
+
type: "progress",
|
|
19333
|
+
command: "upload",
|
|
19334
|
+
origin: this.auth.origin,
|
|
19335
|
+
filePath: relative,
|
|
19336
|
+
recordingId: init.id,
|
|
19337
|
+
status: "uploaded",
|
|
19338
|
+
message: `Uploaded \xB7 ${recordingCloudUrl(this.auth.origin, init.id)}`
|
|
19339
|
+
});
|
|
19302
19340
|
let jobId;
|
|
19303
19341
|
let transcriptId;
|
|
19304
19342
|
let status = "ready";
|
|
@@ -19306,6 +19344,7 @@ var RecappiApiClient = class {
|
|
|
19306
19344
|
opts.onEvent?.({
|
|
19307
19345
|
type: "progress",
|
|
19308
19346
|
command: "upload",
|
|
19347
|
+
origin: this.auth.origin,
|
|
19309
19348
|
filePath: relative,
|
|
19310
19349
|
recordingId: init.id,
|
|
19311
19350
|
status: "starting_transcription",
|
|
@@ -19323,6 +19362,17 @@ var RecappiApiClient = class {
|
|
|
19323
19362
|
jobId = transcribe.jobId;
|
|
19324
19363
|
status = transcribe.status;
|
|
19325
19364
|
if (transcribe.transcriptId) transcriptId = transcribe.transcriptId;
|
|
19365
|
+
opts.onEvent?.({
|
|
19366
|
+
type: "progress",
|
|
19367
|
+
command: "upload",
|
|
19368
|
+
origin: this.auth.origin,
|
|
19369
|
+
filePath: relative,
|
|
19370
|
+
recordingId: init.id,
|
|
19371
|
+
jobId,
|
|
19372
|
+
status,
|
|
19373
|
+
...transcriptId ? { transcriptId } : {},
|
|
19374
|
+
message: status === "succeeded" ? "Transcription already ready" : "Transcription queued"
|
|
19375
|
+
});
|
|
19326
19376
|
if (opts.wait) {
|
|
19327
19377
|
const waited = await this.waitForJob(jobId, {
|
|
19328
19378
|
onEvent: (event) => opts.onEvent?.({
|
|
@@ -19393,14 +19443,27 @@ var RecappiApiClient = class {
|
|
|
19393
19443
|
if (typeof parsed.id !== "string" || typeof parsed.status !== "string") {
|
|
19394
19444
|
throw cliError("cloud.invalid_response", "Job response was missing id or status.");
|
|
19395
19445
|
}
|
|
19446
|
+
const recording = isRecord2(parsed.recording) ? parsed.recording : void 0;
|
|
19447
|
+
const processedDurationMs = typeof parsed.processedDurationMs === "number" || parsed.processedDurationMs === null ? parsed.processedDurationMs : void 0;
|
|
19448
|
+
const recordingDurationMs = recording && (typeof recording.durationMs === "number" || recording.durationMs === null) ? recording.durationMs : void 0;
|
|
19449
|
+
const progressPercent = jobProgressPercent(parsed, processedDurationMs, recordingDurationMs);
|
|
19396
19450
|
return {
|
|
19397
19451
|
jobId: parsed.id,
|
|
19452
|
+
origin: this.auth.origin,
|
|
19398
19453
|
...typeof parsed.recordingId === "string" ? { recordingId: parsed.recordingId } : {},
|
|
19399
19454
|
status: parsed.status,
|
|
19400
19455
|
...typeof parsed.transcriptId === "string" || parsed.transcriptId === null ? { transcriptId: parsed.transcriptId } : {},
|
|
19401
19456
|
...typeof parsed.provider === "string" ? { provider: parsed.provider } : {},
|
|
19402
19457
|
...typeof parsed.model === "string" ? { model: parsed.model } : {},
|
|
19403
|
-
...typeof parsed.language === "string" || parsed.language === null ? { language: parsed.language } : {}
|
|
19458
|
+
...typeof parsed.language === "string" || parsed.language === null ? { language: parsed.language } : {},
|
|
19459
|
+
...progressPercent !== void 0 ? { progressPercent } : {},
|
|
19460
|
+
...processedDurationMs !== void 0 ? { processedDurationMs } : {},
|
|
19461
|
+
...recording ? {
|
|
19462
|
+
recording: {
|
|
19463
|
+
...typeof recording.title === "string" || recording.title === null ? { title: recording.title } : {},
|
|
19464
|
+
...recordingDurationMs !== void 0 ? { durationMs: recordingDurationMs } : {}
|
|
19465
|
+
}
|
|
19466
|
+
} : {}
|
|
19404
19467
|
};
|
|
19405
19468
|
}
|
|
19406
19469
|
async getJson(path6) {
|
|
@@ -19719,6 +19782,21 @@ function stringValue2(value) {
|
|
|
19719
19782
|
function numberValue2(value) {
|
|
19720
19783
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
19721
19784
|
}
|
|
19785
|
+
function recordingCloudUrl(origin, recordingId) {
|
|
19786
|
+
return `${origin.replace(/\/+$/, "")}/recordings/${encodeURIComponent(recordingId)}`;
|
|
19787
|
+
}
|
|
19788
|
+
function jobProgressPercent(row, processedDurationMs, recordingDurationMs) {
|
|
19789
|
+
const chunkProgress = isRecord2(row.chunkProgress) ? row.chunkProgress : void 0;
|
|
19790
|
+
const chunkPercent = numberValue2(chunkProgress?.percent);
|
|
19791
|
+
if (chunkPercent !== void 0) return clampPercent(chunkPercent);
|
|
19792
|
+
if (typeof processedDurationMs === "number" && typeof recordingDurationMs === "number" && recordingDurationMs > 0) {
|
|
19793
|
+
return clampPercent(processedDurationMs / recordingDurationMs * 100);
|
|
19794
|
+
}
|
|
19795
|
+
return void 0;
|
|
19796
|
+
}
|
|
19797
|
+
function clampPercent(value) {
|
|
19798
|
+
return Math.max(0, Math.min(100, Math.round(value)));
|
|
19799
|
+
}
|
|
19722
19800
|
function nullableCap(value) {
|
|
19723
19801
|
if (value === null) return null;
|
|
19724
19802
|
const number4 = numberValue2(value);
|
|
@@ -20173,7 +20251,10 @@ function renderEvent(event, opts) {
|
|
|
20173
20251
|
}
|
|
20174
20252
|
if ((event.type === "started" || event.type === "progress") && opts.mode === "human") {
|
|
20175
20253
|
const line = formatHumanProgress(event, opts);
|
|
20176
|
-
if (line)
|
|
20254
|
+
if (line) {
|
|
20255
|
+
if (isPersistentProgressEvent(event)) writePersistentHumanProgress(line, opts);
|
|
20256
|
+
else writeHumanProgress(line, opts);
|
|
20257
|
+
}
|
|
20177
20258
|
}
|
|
20178
20259
|
}
|
|
20179
20260
|
function renderEnvelope(envelope, opts) {
|
|
@@ -20337,10 +20418,12 @@ Next:
|
|
|
20337
20418
|
}
|
|
20338
20419
|
if (command === "upload" && isUploadBatch(data)) {
|
|
20339
20420
|
if (data.successes.length > 0) {
|
|
20340
|
-
opts.stdout(data.successes
|
|
20421
|
+
opts.stdout(uploadSuccessHeading(data.successes));
|
|
20341
20422
|
}
|
|
20342
20423
|
for (const item of data.successes) {
|
|
20343
20424
|
opts.stdout(` recordingId: ${item.recordingId}
|
|
20425
|
+
`);
|
|
20426
|
+
opts.stdout(` recordingUrl: ${uploadRecordingUrl(item)}
|
|
20344
20427
|
`);
|
|
20345
20428
|
if (item.jobId) opts.stdout(` jobId: ${item.jobId}
|
|
20346
20429
|
`);
|
|
@@ -20432,7 +20515,11 @@ Next:
|
|
|
20432
20515
|
}
|
|
20433
20516
|
if ((command === "jobs wait" || command === "upload") && isRecord4(data)) {
|
|
20434
20517
|
if (typeof data.transcriptId === "string") {
|
|
20435
|
-
opts.stdout("
|
|
20518
|
+
opts.stdout("Transcript ready\n");
|
|
20519
|
+
if (typeof data.recordingId === "string" && typeof data.origin === "string") {
|
|
20520
|
+
opts.stdout(` recordingUrl: ${jobRecordingUrl(data.origin, data.recordingId, data.jobId)}
|
|
20521
|
+
`);
|
|
20522
|
+
}
|
|
20436
20523
|
opts.stdout(` transcriptId: ${data.transcriptId}
|
|
20437
20524
|
`);
|
|
20438
20525
|
opts.stdout(`
|
|
@@ -20511,17 +20598,20 @@ function formatUploadProgress(event, opts, scope) {
|
|
|
20511
20598
|
return `Uploading${event.filePath ? ` ${humanFileLabel(event.filePath)}` : ""}: ${event.percent}%`;
|
|
20512
20599
|
}
|
|
20513
20600
|
if (event.status === "finishing_upload") return "Finalizing upload";
|
|
20601
|
+
if (event.status === "uploaded") {
|
|
20602
|
+
return event.message ?? uploadedLine(event.origin, event.recordingId);
|
|
20603
|
+
}
|
|
20514
20604
|
if (event.status === "starting_transcription") return "Starting transcription";
|
|
20515
20605
|
return formatJobProgress(event);
|
|
20516
20606
|
}
|
|
20517
20607
|
function formatJobProgress(event) {
|
|
20518
20608
|
switch (event.status) {
|
|
20519
20609
|
case "queued":
|
|
20520
|
-
return "Waiting for transcription";
|
|
20610
|
+
return event.jobId ? `Waiting for transcription (job ${event.jobId})` : "Waiting for transcription";
|
|
20521
20611
|
case "running":
|
|
20522
|
-
return "Transcribing";
|
|
20612
|
+
return typeof event.percent === "number" ? `Transcribing: ${event.percent}%` : "Transcribing\u2026";
|
|
20523
20613
|
case "succeeded":
|
|
20524
|
-
return "
|
|
20614
|
+
return "\u2713 Transcript ready";
|
|
20525
20615
|
case "failed":
|
|
20526
20616
|
return "Transcription failed";
|
|
20527
20617
|
default:
|
|
@@ -20539,6 +20629,11 @@ function writeHumanProgress(line, opts) {
|
|
|
20539
20629
|
opts.stderr(`\r${line}${padding}`);
|
|
20540
20630
|
state.activeLineLength = line.length;
|
|
20541
20631
|
}
|
|
20632
|
+
function writePersistentHumanProgress(line, opts) {
|
|
20633
|
+
finishHumanProgress(opts);
|
|
20634
|
+
opts.stderr(`${line}
|
|
20635
|
+
`);
|
|
20636
|
+
}
|
|
20542
20637
|
function finishHumanProgress(opts) {
|
|
20543
20638
|
const state = opts.progress;
|
|
20544
20639
|
if (!state?.interactive || state.activeLineLength === 0) return;
|
|
@@ -20548,11 +20643,32 @@ function finishHumanProgress(opts) {
|
|
|
20548
20643
|
function progressScope(event) {
|
|
20549
20644
|
return [event.command, event.filePath, event.recordingId, event.jobId].filter(Boolean).join(":");
|
|
20550
20645
|
}
|
|
20646
|
+
function isPersistentProgressEvent(event) {
|
|
20647
|
+
return event.command === "upload" && event.status === "uploaded";
|
|
20648
|
+
}
|
|
20551
20649
|
function humanFileLabel(filePath) {
|
|
20552
20650
|
if (!filePath) return void 0;
|
|
20553
20651
|
const normalized = filePath.replaceAll("\\", "/");
|
|
20554
20652
|
return normalized.split("/").filter(Boolean).at(-1) ?? filePath;
|
|
20555
20653
|
}
|
|
20654
|
+
function uploadRecordingUrl(item) {
|
|
20655
|
+
return jobRecordingUrl(item.origin, item.recordingId, item.jobId);
|
|
20656
|
+
}
|
|
20657
|
+
function uploadSuccessHeading(successes) {
|
|
20658
|
+
const transcriptReady = successes.length > 0 && successes.every((item) => Boolean(item.transcriptId));
|
|
20659
|
+
if (transcriptReady) return successes.length === 1 ? "\u2713 Transcript ready\n" : "\u2713 Transcripts ready\n";
|
|
20660
|
+
return successes.length === 1 ? "Upload complete\n" : "Uploads complete\n";
|
|
20661
|
+
}
|
|
20662
|
+
function jobRecordingUrl(origin, recordingId, jobId) {
|
|
20663
|
+
const base = recordingCloudUrl2(origin, recordingId);
|
|
20664
|
+
return typeof jobId === "string" ? `${base}?job=${encodeURIComponent(jobId)}` : base;
|
|
20665
|
+
}
|
|
20666
|
+
function uploadedLine(origin, recordingId) {
|
|
20667
|
+
return origin && recordingId ? `Uploaded \xB7 ${recordingCloudUrl2(origin, recordingId)}` : "Uploaded";
|
|
20668
|
+
}
|
|
20669
|
+
function recordingCloudUrl2(origin, recordingId) {
|
|
20670
|
+
return `${origin.replace(/\/+$/, "")}/recordings/${encodeURIComponent(recordingId)}`;
|
|
20671
|
+
}
|
|
20556
20672
|
function renderTranscriptHuman(data, opts) {
|
|
20557
20673
|
const segments = Array.isArray(data.segments) ? data.segments : [];
|
|
20558
20674
|
let printedBody = false;
|