minutes-sdk 0.14.1 → 0.15.0

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.d.ts CHANGED
@@ -1 +1 @@
1
- export { type ActionItem, type Decision, type Intent, type Frontmatter, type MeetingFile, defaultDir, splitFrontmatter, parseFrontmatter, listMeetings, searchMeetings, getMeeting, findOpenActions, findDecisions, getPersonProfile, listVoiceMemos, } from "./reader.js";
1
+ export { type ActionItem, type Decision, type Intent, type Frontmatter, type MeetingFile, type SpeakerAttribution, type SpeakerConfirmation, defaultDir, splitFrontmatter, parseFrontmatter, listMeetings, searchMeetings, getMeeting, getMeetingWithOverlays, applySpeakerOverlays, humanizeTranscript, findOpenActions, findDecisions, getPersonProfile, listVoiceMemos, } from "./reader.js";
package/dist/index.js CHANGED
@@ -14,4 +14,4 @@ defaultDir,
14
14
  // Parsing
15
15
  splitFrontmatter, parseFrontmatter,
16
16
  // Query API
17
- listMeetings, searchMeetings, getMeeting, findOpenActions, findDecisions, getPersonProfile, listVoiceMemos, } from "./reader.js";
17
+ listMeetings, searchMeetings, getMeeting, getMeetingWithOverlays, applySpeakerOverlays, humanizeTranscript, findOpenActions, findDecisions, getPersonProfile, listVoiceMemos, } from "./reader.js";
package/dist/reader.d.ts CHANGED
@@ -21,6 +21,20 @@ export interface SpeakerAttribution {
21
21
  confidence: "high" | "medium" | "low";
22
22
  source: "deterministic" | "llm" | "enrollment" | "manual";
23
23
  }
24
+ /**
25
+ * A user-confirmed speaker correction stored in the sidecar overlay store
26
+ * (`~/.minutes/overlays.db`). Overlays layer over raw frontmatter at read
27
+ * time without ever mutating the meeting markdown on disk.
28
+ *
29
+ * Confirmations carry high confidence and `manual` source by definition —
30
+ * they record an explicit user action, not a model inference.
31
+ */
32
+ export interface SpeakerConfirmation {
33
+ speaker_label: string;
34
+ name: string;
35
+ /** Optional name the overlay overrode, useful for "undo" UIs. */
36
+ previous_name?: string;
37
+ }
24
38
  export interface Frontmatter {
25
39
  title: string;
26
40
  type: string;
@@ -72,6 +86,53 @@ export declare function searchMeetings(dir: string, query: string, limit?: numbe
72
86
  * Get a single meeting by file path.
73
87
  */
74
88
  export declare function getMeeting(filePath: string): Promise<MeetingFile | null>;
89
+ /**
90
+ * Layer sidecar speaker confirmations over a meeting's `speaker_map`,
91
+ * returning a new MeetingFile with the corrections applied. The original
92
+ * meeting object is not mutated, and the body text is not rewritten —
93
+ * Minutes treats raw markdown as immutable capture.
94
+ *
95
+ * For each confirmation:
96
+ * - if a `speaker_map` entry with the same `speaker_label` exists, its
97
+ * `name` is replaced and confidence/source are bumped to high/manual
98
+ * - if no entry exists, a new one is appended
99
+ *
100
+ * Pass an empty `confirmations` array to no-op.
101
+ */
102
+ export declare function applySpeakerOverlays(meeting: MeetingFile, confirmations: SpeakerConfirmation[]): MeetingFile;
103
+ /**
104
+ * Rewrite `[SPEAKER_N <timestamp>] text` line prefixes in a meeting
105
+ * transcript body to use the speaker's mapped name. Mirrors the Rust
106
+ * `apply_confirmed_names` helper:
107
+ *
108
+ * - Only attributions with `confidence: "high"` are applied — model
109
+ * guesses below that bar do not silently rewrite the transcript.
110
+ * - If a line's body itself looks like a non-lexical event marker
111
+ * (e.g. `[laughter]`, `[music]`), the speaker label is left alone
112
+ * so the rendered output keeps the event tag instead of saying
113
+ * "Alex Kim: [laughter]".
114
+ * - Non-bracketed lines (headings, prose, blank) are returned
115
+ * unchanged.
116
+ *
117
+ * The function is pure: the input string is not mutated.
118
+ */
119
+ export declare function humanizeTranscript(body: string, speakerMap: SpeakerAttribution[] | undefined): string;
120
+ /**
121
+ * Get a meeting with sidecar overlay confirmations layered over its
122
+ * `speaker_map`. Best-effort convenience: shells to the local `minutes`
123
+ * CLI (`minutes get <path> --json`) which reads `~/.minutes/overlays.db`
124
+ * server-side and returns an overlay-applied payload. If the CLI is not
125
+ * available or the call fails, falls back to plain `getMeeting()` so
126
+ * consumers always get a usable result.
127
+ *
128
+ * For full control over which overlays apply (e.g. to layer a remote
129
+ * overlay store, or to test against fixtures), use `applySpeakerOverlays`
130
+ * directly with confirmations sourced however you prefer.
131
+ */
132
+ export declare function getMeetingWithOverlays(filePath: string, options?: {
133
+ minutesBin?: string;
134
+ timeoutMs?: number;
135
+ }): Promise<MeetingFile | null>;
75
136
  /**
76
137
  * Find open action items across all meetings.
77
138
  */
package/dist/reader.js CHANGED
@@ -104,6 +104,23 @@ export function parseFrontmatter(content, filePath) {
104
104
  by_date: i.by_date ? String(i.by_date) : undefined,
105
105
  }))
106
106
  : [],
107
+ speaker_map: Array.isArray(parsed.speaker_map)
108
+ ? parsed.speaker_map.map((s) => ({
109
+ speaker_label: String(s.speaker_label || ""),
110
+ name: String(s.name || ""),
111
+ confidence: (s.confidence === "high" ||
112
+ s.confidence === "medium" ||
113
+ s.confidence === "low"
114
+ ? s.confidence
115
+ : "medium"),
116
+ source: (s.source === "deterministic" ||
117
+ s.source === "llm" ||
118
+ s.source === "enrollment" ||
119
+ s.source === "manual"
120
+ ? s.source
121
+ : "llm"),
122
+ }))
123
+ : undefined,
107
124
  };
108
125
  return { frontmatter: fm, body, path: filePath };
109
126
  }
@@ -205,6 +222,174 @@ export async function searchMeetings(dir, query, limit = 20) {
205
222
  export async function getMeeting(filePath) {
206
223
  return readMeetingFile(filePath);
207
224
  }
225
+ /**
226
+ * Layer sidecar speaker confirmations over a meeting's `speaker_map`,
227
+ * returning a new MeetingFile with the corrections applied. The original
228
+ * meeting object is not mutated, and the body text is not rewritten —
229
+ * Minutes treats raw markdown as immutable capture.
230
+ *
231
+ * For each confirmation:
232
+ * - if a `speaker_map` entry with the same `speaker_label` exists, its
233
+ * `name` is replaced and confidence/source are bumped to high/manual
234
+ * - if no entry exists, a new one is appended
235
+ *
236
+ * Pass an empty `confirmations` array to no-op.
237
+ */
238
+ export function applySpeakerOverlays(meeting, confirmations) {
239
+ if (!confirmations || confirmations.length === 0) {
240
+ return meeting;
241
+ }
242
+ const baseMap = meeting.frontmatter.speaker_map ?? [];
243
+ const merged = baseMap.map((attr) => ({ ...attr }));
244
+ for (const confirmation of confirmations) {
245
+ if (!confirmation.speaker_label || !confirmation.name)
246
+ continue;
247
+ const existing = merged.find((attr) => attr.speaker_label === confirmation.speaker_label);
248
+ if (existing) {
249
+ existing.name = confirmation.name;
250
+ existing.confidence = "high";
251
+ existing.source = "manual";
252
+ }
253
+ else {
254
+ merged.push({
255
+ speaker_label: confirmation.speaker_label,
256
+ name: confirmation.name,
257
+ confidence: "high",
258
+ source: "manual",
259
+ });
260
+ }
261
+ }
262
+ return {
263
+ ...meeting,
264
+ frontmatter: { ...meeting.frontmatter, speaker_map: merged },
265
+ };
266
+ }
267
+ /**
268
+ * Rewrite `[SPEAKER_N <timestamp>] text` line prefixes in a meeting
269
+ * transcript body to use the speaker's mapped name. Mirrors the Rust
270
+ * `apply_confirmed_names` helper:
271
+ *
272
+ * - Only attributions with `confidence: "high"` are applied — model
273
+ * guesses below that bar do not silently rewrite the transcript.
274
+ * - If a line's body itself looks like a non-lexical event marker
275
+ * (e.g. `[laughter]`, `[music]`), the speaker label is left alone
276
+ * so the rendered output keeps the event tag instead of saying
277
+ * "Alex Kim: [laughter]".
278
+ * - Non-bracketed lines (headings, prose, blank) are returned
279
+ * unchanged.
280
+ *
281
+ * The function is pure: the input string is not mutated.
282
+ */
283
+ export function humanizeTranscript(body, speakerMap) {
284
+ if (!speakerMap || speakerMap.length === 0)
285
+ return body;
286
+ const highMap = new Map();
287
+ for (const attr of speakerMap) {
288
+ if (attr.confidence === "high" && attr.speaker_label && attr.name) {
289
+ highMap.set(attr.speaker_label, attr.name);
290
+ }
291
+ }
292
+ if (highMap.size === 0)
293
+ return body;
294
+ const out = [];
295
+ for (const line of body.split("\n")) {
296
+ out.push(humanizeOneLine(line, highMap));
297
+ }
298
+ return out.join("\n");
299
+ }
300
+ function humanizeOneLine(line, highMap) {
301
+ if (!line.startsWith("["))
302
+ return line;
303
+ const close = line.indexOf("]");
304
+ if (close < 0)
305
+ return line;
306
+ const inside = line.slice(1, close);
307
+ const space = inside.indexOf(" ");
308
+ if (space < 0)
309
+ return line;
310
+ const label = inside.slice(0, space);
311
+ const replacement = highMap.get(label);
312
+ if (!replacement)
313
+ return line;
314
+ const remainder = inside.slice(space + 1);
315
+ const after = line.slice(close + 1);
316
+ // Skip rewriting when the body is itself a bracketed event tag —
317
+ // matches Rust's is_non_lexical_event_text guard.
318
+ const trimmedAfter = after.trimStart();
319
+ if (trimmedAfter.startsWith("[") && trimmedAfter.trimEnd().endsWith("]")) {
320
+ return line;
321
+ }
322
+ return `[${replacement} ${remainder}]${after}`;
323
+ }
324
+ /**
325
+ * Get a meeting with sidecar overlay confirmations layered over its
326
+ * `speaker_map`. Best-effort convenience: shells to the local `minutes`
327
+ * CLI (`minutes get <path> --json`) which reads `~/.minutes/overlays.db`
328
+ * server-side and returns an overlay-applied payload. If the CLI is not
329
+ * available or the call fails, falls back to plain `getMeeting()` so
330
+ * consumers always get a usable result.
331
+ *
332
+ * For full control over which overlays apply (e.g. to layer a remote
333
+ * overlay store, or to test against fixtures), use `applySpeakerOverlays`
334
+ * directly with confirmations sourced however you prefer.
335
+ */
336
+ export async function getMeetingWithOverlays(filePath, options = {}) {
337
+ const fallback = await getMeeting(filePath);
338
+ if (!fallback)
339
+ return null;
340
+ // Dynamically import child_process so this module still loads in
341
+ // environments without it (browsers, Edge runtimes). The function
342
+ // simply degrades to non-overlay behavior in those cases.
343
+ let execFile;
344
+ try {
345
+ ({ execFile } = await import("child_process"));
346
+ }
347
+ catch {
348
+ return fallback;
349
+ }
350
+ const bin = options.minutesBin ?? process.env.MINUTES_BIN ?? "minutes";
351
+ const timeoutMs = options.timeoutMs ?? 10_000;
352
+ const stdout = await new Promise((resolve) => {
353
+ execFile(bin, ["get", filePath, "--json", "--compact-json"], { timeout: timeoutMs, maxBuffer: 8 * 1024 * 1024 }, (err, out) => {
354
+ if (err)
355
+ resolve(null);
356
+ else
357
+ resolve(out.toString());
358
+ });
359
+ });
360
+ if (!stdout)
361
+ return fallback;
362
+ try {
363
+ const payload = JSON.parse(stdout);
364
+ const overlaidMap = payload?.frontmatter?.speaker_map;
365
+ if (!Array.isArray(overlaidMap))
366
+ return fallback;
367
+ return {
368
+ ...fallback,
369
+ frontmatter: {
370
+ ...fallback.frontmatter,
371
+ speaker_map: overlaidMap.map((attr) => ({
372
+ speaker_label: String(attr.speaker_label || ""),
373
+ name: String(attr.name || ""),
374
+ confidence: (attr.confidence === "high" ||
375
+ attr.confidence === "medium" ||
376
+ attr.confidence === "low"
377
+ ? attr.confidence
378
+ : "medium"),
379
+ source: (attr.source === "deterministic" ||
380
+ attr.source === "llm" ||
381
+ attr.source === "enrollment" ||
382
+ attr.source === "manual"
383
+ ? attr.source
384
+ : "llm"),
385
+ })),
386
+ },
387
+ };
388
+ }
389
+ catch {
390
+ return fallback;
391
+ }
392
+ }
208
393
  /**
209
394
  * Find open action items across all meetings.
210
395
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minutes-sdk",
3
- "version": "0.14.1",
3
+ "version": "0.15.0",
4
4
  "description": "Conversation memory SDK — query meeting transcripts, decisions, and action items from any AI agent or application",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",