recappi 0.1.69 → 0.1.71

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
@@ -150,6 +150,12 @@ function transcribeFraction(item) {
150
150
  if (!total || total <= 0 || done == null) return null;
151
151
  return Math.max(0, Math.min(1, done / total));
152
152
  }
153
+ function isJobStalled(item, nowMs) {
154
+ return item.status === "running" && typeof item.claimExpiresAt === "number" && item.claimExpiresAt < nowMs;
155
+ }
156
+ function effectiveJobStatus(item, nowMs) {
157
+ return isJobStalled(item, nowMs) ? "stalled" : item.status;
158
+ }
153
159
  function statusStyle(status) {
154
160
  switch (status) {
155
161
  case "running":
@@ -160,6 +166,8 @@ function statusStyle(status) {
160
166
  return { label: "Ready", color: "green" };
161
167
  case "failed":
162
168
  return { label: "Failed", color: "red" };
169
+ case "stalled":
170
+ return { label: "Stalled", color: "yellow" };
163
171
  default:
164
172
  return { label: status, color: "gray" };
165
173
  }
@@ -191,11 +199,14 @@ function statusGlyph(status, spinnerFrame) {
191
199
  return "\u2713";
192
200
  case "failed":
193
201
  return "\u2717";
202
+ case "stalled":
203
+ return "!";
194
204
  default:
195
205
  return "\u2022";
196
206
  }
197
207
  }
198
- function jobDetail(item) {
208
+ function jobDetail(item, nowMs) {
209
+ if (nowMs != null && isJobStalled(item, nowMs)) return "stalled \u2014 worker lost \xB7 T retry";
199
210
  if (item.status === "running") {
200
211
  const fraction = transcribeFraction(item);
201
212
  if (fraction != null) {
@@ -735,17 +746,19 @@ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
735
746
  function JobRow({
736
747
  item,
737
748
  selected,
738
- spinnerFrame
749
+ spinnerFrame,
750
+ nowMs
739
751
  }) {
740
- const style = statusStyle(item.status);
741
- const glyph = statusGlyph(item.status, spinnerFrame);
752
+ const status = nowMs != null ? effectiveJobStatus(item, nowMs) : item.status;
753
+ const style = statusStyle(status);
754
+ const glyph = statusGlyph(status, spinnerFrame);
742
755
  const title = item.recording?.title ?? item.recordingId;
743
756
  return /* @__PURE__ */ jsxs5(Box5, { children: [
744
757
  /* @__PURE__ */ jsx7(Box5, { width: 3, children: /* @__PURE__ */ jsx7(Text5, { color: "cyan", children: selected ? "\u25B8" : "" }) }),
745
758
  /* @__PURE__ */ jsx7(Box5, { width: 2, children: /* @__PURE__ */ jsx7(Text5, { color: style.color, children: glyph }) }),
746
759
  /* @__PURE__ */ jsx7(Box5, { width: 13, children: /* @__PURE__ */ jsx7(Text5, { color: style.color, children: style.label }) }),
747
760
  /* @__PURE__ */ jsx7(Box5, { width: 26, children: /* @__PURE__ */ jsx7(Text5, { bold: selected, wrap: "truncate-end", children: title }) }),
748
- /* @__PURE__ */ jsx7(Text5, { dimColor: !selected, children: jobDetail(item) })
761
+ /* @__PURE__ */ jsx7(Text5, { dimColor: !selected, children: jobDetail(item, nowMs) })
749
762
  ] });
750
763
  }
751
764
  var init_JobRow = __esm({
@@ -761,7 +774,8 @@ import { jsx as jsx8 } from "react/jsx-runtime";
761
774
  function JobsView({
762
775
  items,
763
776
  selectedIndex,
764
- spinnerFrame
777
+ spinnerFrame,
778
+ nowMs
765
779
  }) {
766
780
  if (items.length === 0) {
767
781
  return /* @__PURE__ */ jsx8(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text6, { dimColor: true, children: "No transcription jobs yet \u2014 run: recappi upload <file> --transcribe" }) });
@@ -771,7 +785,8 @@ function JobsView({
771
785
  {
772
786
  item,
773
787
  selected: index === selectedIndex,
774
- spinnerFrame
788
+ spinnerFrame,
789
+ nowMs
775
790
  },
776
791
  item.jobId
777
792
  )) });
@@ -794,6 +809,7 @@ function recordingTitle2(item) {
794
809
  function recordingProcessingState(item, jobStatus, spinnerFrame) {
795
810
  if (item.status === "uploading") return { glyph: "\u2191", color: "cyan" };
796
811
  if (item.status === "failed" || jobStatus === "failed") return { glyph: "\u2717", color: "red" };
812
+ if (jobStatus === "stalled") return { glyph: "!", color: "yellow" };
797
813
  if (jobStatus === "running") return { glyph: spinnerChar(spinnerFrame), color: "cyan" };
798
814
  if (jobStatus === "queued") return { glyph: "\u25CB", color: "yellow" };
799
815
  if (item.status === "aborted") return { glyph: "\u2022", color: "gray" };
@@ -1019,7 +1035,8 @@ function JobDetailView({
1019
1035
  spinnerFrame,
1020
1036
  nowMs
1021
1037
  }) {
1022
- const style = statusStyle(item.status);
1038
+ const status = effectiveJobStatus(item, nowMs);
1039
+ const style = statusStyle(status);
1023
1040
  const links = resolveJobLinks(item, origin);
1024
1041
  const title = item.recording?.title ?? item.recordingId;
1025
1042
  return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", paddingX: 1, children: [
@@ -1067,10 +1084,10 @@ function JobDetailView({
1067
1084
  /* @__PURE__ */ jsx13(
1068
1085
  TimelineRow,
1069
1086
  {
1070
- label: item.status === "failed" ? "Failed" : item.status === "running" ? "Transcribing" : "Finished",
1087
+ label: status === "failed" ? "Failed" : status === "stalled" ? "Stalled \u2014 worker lost" : status === "running" ? "Transcribing" : "Finished",
1071
1088
  done: item.finishedAt != null,
1072
1089
  failed: item.status === "failed",
1073
- running: item.status === "running",
1090
+ running: status === "running",
1074
1091
  at: item.finishedAt,
1075
1092
  nowMs
1076
1093
  }
@@ -2452,12 +2469,13 @@ function AppShell({
2452
2469
  const id = setInterval(() => setSpinnerFrame((f) => f + 1), spinnerMs);
2453
2470
  return () => clearInterval(id);
2454
2471
  }, [hasRunning, loaded, spinnerMs]);
2455
- const jobRank = (s) => s === "running" ? 4 : s === "queued" ? 3 : s === "failed" ? 2 : s === "succeeded" ? 1 : 0;
2472
+ const jobRank = (s) => s === "running" ? 5 : s === "stalled" ? 4 : s === "queued" ? 3 : s === "failed" ? 2 : s === "succeeded" ? 1 : 0;
2456
2473
  const jobStatusByRecording = /* @__PURE__ */ new Map();
2457
2474
  for (const job of jobs) {
2475
+ const status = effectiveJobStatus(job, now());
2458
2476
  const prev = jobStatusByRecording.get(job.recordingId);
2459
- if (!prev || jobRank(job.status) > jobRank(prev)) {
2460
- jobStatusByRecording.set(job.recordingId, job.status);
2477
+ if (!prev || jobRank(status) > jobRank(prev)) {
2478
+ jobStatusByRecording.set(job.recordingId, status);
2461
2479
  }
2462
2480
  }
2463
2481
  useEffect4(() => {
@@ -2804,7 +2822,8 @@ function AppShell({
2804
2822
  {
2805
2823
  items: jobs.slice(win.start, win.end),
2806
2824
  selectedIndex: selected - win.start,
2807
- spinnerFrame
2825
+ spinnerFrame,
2826
+ nowMs: now()
2808
2827
  }
2809
2828
  );
2810
2829
  }
@@ -2916,6 +2935,7 @@ async function runDashboard(deps) {
2916
2935
  startRecordSetupPreview: deps.startRecordSetupPreview,
2917
2936
  transcribeRecordingArtifact: deps.transcribeRecordingArtifact,
2918
2937
  onRetranscribe: deps.retranscribeRecording,
2938
+ onResummarize: deps.resummarizeRecording,
2919
2939
  initialView: deps.initialView ?? "overview",
2920
2940
  openUrl,
2921
2941
  copyText
@@ -18009,6 +18029,12 @@ var summaryStatusSchema = external_exports.enum([
18009
18029
  "failed",
18010
18030
  "skipped"
18011
18031
  ]);
18032
+ var recordingSummarizeDataSchema = external_exports.object({
18033
+ origin: external_exports.string(),
18034
+ recordingId: external_exports.string(),
18035
+ transcriptId: external_exports.string(),
18036
+ summaryStatus: summaryStatusSchema
18037
+ });
18012
18038
  var summaryActionItemSchema = external_exports.object({
18013
18039
  who: external_exports.string().optional(),
18014
18040
  what: external_exports.string()
@@ -19282,6 +19308,22 @@ var RecappiApiClient = class {
19282
19308
  }
19283
19309
  return result;
19284
19310
  }
19311
+ async summarizeRecording(opts) {
19312
+ const hasPrompt = Boolean(opts.prompt?.trim());
19313
+ const parsed = await this.postJson(
19314
+ `/api/recordings/${encodeURIComponent(opts.recordingId)}/summarize`,
19315
+ {
19316
+ ...hasPrompt ? { prompt: opts.prompt } : {},
19317
+ ...opts.model ? { model: opts.model } : {}
19318
+ }
19319
+ );
19320
+ return recordingSummarizeDataSchema.parse({
19321
+ origin: this.auth.origin,
19322
+ recordingId: opts.recordingId,
19323
+ transcriptId: parsed.transcriptId,
19324
+ summaryStatus: parsed.summaryStatus
19325
+ });
19326
+ }
19285
19327
  async downloadRecordingAudio(recordingId, opts = {}) {
19286
19328
  const response = await this.request(
19287
19329
  "GET",
@@ -20141,6 +20183,7 @@ var COMMON_TASKS = [
20141
20183
  { label: "Record audio + live captions", command: "recappi record --live" },
20142
20184
  { label: "Transcribe a local file", command: "recappi upload <file> --transcribe --wait" },
20143
20185
  { label: "Re-transcribe a recording", command: "recappi recordings retranscribe <recordingId> --wait" },
20186
+ { label: "Re-summarize a recording", command: "recappi recordings resummarize <recordingId>" },
20144
20187
  { label: "List / find recordings", command: "recappi recordings list" },
20145
20188
  { label: "Read a transcript", command: "recappi transcript get <transcriptId>" },
20146
20189
  { label: "Download / open audio", command: "recappi audio <recordingId> --open" },
@@ -20271,6 +20314,20 @@ var COMMAND_METADATA = {
20271
20314
  ],
20272
20315
  relatedCommands: ["jobs wait", "transcript get"]
20273
20316
  },
20317
+ "recordings resummarize": {
20318
+ capabilities: ["Retry or regenerate the summary for an existing recording", "Re-summarize with a custom prompt/model"],
20319
+ examples: [
20320
+ {
20321
+ description: "Retry summary generation for the active transcript",
20322
+ command: "recappi recordings resummarize <recordingId>"
20323
+ },
20324
+ {
20325
+ description: "Re-summarize with custom context",
20326
+ command: 'recappi recordings resummarize <recordingId> --prompt "focus on action items"'
20327
+ }
20328
+ ],
20329
+ relatedCommands: ["recordings get", "transcript get"]
20330
+ },
20274
20331
  "transcript get": {
20275
20332
  capabilities: ["Fetch a finished transcript by id"],
20276
20333
  examples: [{ description: "Fetch an existing transcript", command: "recappi transcript get <transcriptId>" }],
@@ -20831,6 +20888,24 @@ Next:
20831
20888
  opts.stdout(`
20832
20889
  Next:
20833
20890
  recappi jobs wait ${data.jobId}
20891
+ `);
20892
+ }
20893
+ return;
20894
+ }
20895
+ if (command === "recordings resummarize" && isRecord4(data)) {
20896
+ opts.stdout("Summary queued\n");
20897
+ if (typeof data.recordingId === "string") opts.stdout(` recordingId: ${data.recordingId}
20898
+ `);
20899
+ if (typeof data.transcriptId === "string") opts.stdout(` transcriptId: ${data.transcriptId}
20900
+ `);
20901
+ if (typeof data.summaryStatus === "string") {
20902
+ opts.stdout(` summaryStatus: ${data.summaryStatus}
20903
+ `);
20904
+ }
20905
+ if (typeof data.transcriptId === "string") {
20906
+ opts.stdout(`
20907
+ Next:
20908
+ recappi transcript get ${data.transcriptId}
20834
20909
  `);
20835
20910
  }
20836
20911
  return;
@@ -21289,6 +21364,7 @@ var COMMAND_DATA_SCHEMAS = {
21289
21364
  "recordings get": recordingDataSchema,
21290
21365
  "recordings list": recordingListDataSchema,
21291
21366
  "recordings retranscribe": recordingTranscribeDataSchema,
21367
+ "recordings resummarize": recordingSummarizeDataSchema,
21292
21368
  "jobs list": jobListDataSchema,
21293
21369
  "jobs wait": jobDataSchema,
21294
21370
  "transcript get": transcriptDataSchema
@@ -22793,6 +22869,7 @@ async function runCli(deps = {}) {
22793
22869
  return success2;
22794
22870
  },
22795
22871
  retranscribeRecording: (recordingId, options = {}) => client.transcribeRecording({ recordingId, ...options }),
22872
+ resummarizeRecording: (recordingId) => client.summarizeRecording({ recordingId }),
22796
22873
  initialView: parsed.initialView
22797
22874
  });
22798
22875
  return 0;
@@ -23001,6 +23078,15 @@ async function runCli(deps = {}) {
23001
23078
  renderSuccess("recordings retranscribe", data, render3);
23002
23079
  return 0;
23003
23080
  }
23081
+ if (parsed.kind === "recordings-resummarize") {
23082
+ const data = await client.summarizeRecording({
23083
+ recordingId: parsed.recordingId,
23084
+ prompt: parsed.prompt,
23085
+ model: parsed.model
23086
+ });
23087
+ renderSuccess("recordings resummarize", data, render3);
23088
+ return 0;
23089
+ }
23004
23090
  if (parsed.kind === "dashboard-stats") {
23005
23091
  const data = await client.dashboardStats();
23006
23092
  renderSuccess("dashboard stats", data, render3);
@@ -23302,7 +23388,7 @@ Agent mode:
23302
23388
  commandName: "dashboard stats"
23303
23389
  });
23304
23390
  });
23305
- const recordings = program.command("recordings").description("List, fetch, and re-transcribe recordings");
23391
+ const recordings = program.command("recordings").description("List, fetch, re-transcribe, and re-summarize recordings");
23306
23392
  addCommonOptions(recordings);
23307
23393
  const recordingsList = recordings.command("list").description("List recent recordings").option("--limit <n>", "number of recordings to show", parseLimitOption("--limit", 1, 100), 20).option("--cursor <cursor>", "pagination cursor", parseStringOption("--cursor")).option("--search <query>", "search recordings and transcripts", parseStringOption("--search")).addHelpText("after", commandMetadataHelpText("recordings list"));
23308
23394
  addCommonOptions(recordingsList);
@@ -23348,6 +23434,21 @@ Agent mode:
23348
23434
  });
23349
23435
  }
23350
23436
  );
23437
+ const recordingsResummarize = recordings.command("resummarize <recordingId>").description("Retry or regenerate the summary for an existing recording").option("--model <name>", "summary model", parseStringOption("--model")).option("--prompt <text>", "custom summary prompt/context", parseStringOption("--prompt")).addHelpText("after", commandMetadataHelpText("recordings resummarize"));
23438
+ addCommonOptions(recordingsResummarize);
23439
+ recordingsResummarize.action(
23440
+ (recordingId, _options, command) => {
23441
+ const opts = command.opts();
23442
+ onSelect({
23443
+ kind: "recordings-resummarize",
23444
+ options: collectGlobalOptions(command),
23445
+ commandName: "recordings resummarize",
23446
+ recordingId,
23447
+ ...typeof opts.model === "string" ? { model: opts.model } : {},
23448
+ ...typeof opts.prompt === "string" ? { prompt: opts.prompt } : {}
23449
+ });
23450
+ }
23451
+ );
23351
23452
  const transcript = program.command("transcript").description("Read transcripts (create via upload --transcribe or recordings retranscribe)").addHelpText(
23352
23453
  "after",
23353
23454
  `