minutes-sdk 0.14.0 → 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 +1 -1
- package/dist/index.js +1 -1
- package/dist/reader.d.ts +61 -0
- package/dist/reader.js +185 -0
- package/package.json +1 -1
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