minutes-sdk 0.5.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 -0
- package/dist/index.js +15 -0
- package/dist/reader.d.ts +84 -0
- package/dist/reader.js +248 -0
- package/package.json +45 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { type ActionItem, type Decision, type Intent, type Frontmatter, type MeetingFile, splitFrontmatter, parseFrontmatter, listMeetings, searchMeetings, getMeeting, findOpenActions, getPersonProfile, } from "./reader.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// minutes-sdk — conversation memory for AI agents
|
|
2
|
+
//
|
|
3
|
+
// The "Mem0 for human conversations." Query meeting transcripts,
|
|
4
|
+
// decisions, action items, and people from any AI agent or app.
|
|
5
|
+
//
|
|
6
|
+
// Usage:
|
|
7
|
+
// import { listMeetings, searchMeetings } from 'minutes-sdk';
|
|
8
|
+
//
|
|
9
|
+
// const meetings = await listMeetings('~/meetings');
|
|
10
|
+
// const results = await searchMeetings('~/meetings', 'pricing');
|
|
11
|
+
export {
|
|
12
|
+
// Parsing
|
|
13
|
+
splitFrontmatter, parseFrontmatter,
|
|
14
|
+
// Query API
|
|
15
|
+
listMeetings, searchMeetings, getMeeting, findOpenActions, getPersonProfile, } from "./reader.js";
|
package/dist/reader.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export interface ActionItem {
|
|
2
|
+
assignee: string;
|
|
3
|
+
task: string;
|
|
4
|
+
due?: string;
|
|
5
|
+
status: string;
|
|
6
|
+
}
|
|
7
|
+
export interface Decision {
|
|
8
|
+
text: string;
|
|
9
|
+
topic?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface Intent {
|
|
12
|
+
kind: string;
|
|
13
|
+
what: string;
|
|
14
|
+
who?: string;
|
|
15
|
+
status: string;
|
|
16
|
+
by_date?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface Frontmatter {
|
|
19
|
+
title: string;
|
|
20
|
+
type: string;
|
|
21
|
+
date: string;
|
|
22
|
+
duration: string;
|
|
23
|
+
source?: string;
|
|
24
|
+
status?: string;
|
|
25
|
+
tags: string[];
|
|
26
|
+
attendees: string[];
|
|
27
|
+
people: string[];
|
|
28
|
+
context?: string;
|
|
29
|
+
calendar_event?: string;
|
|
30
|
+
action_items: ActionItem[];
|
|
31
|
+
decisions: Decision[];
|
|
32
|
+
intents: Intent[];
|
|
33
|
+
}
|
|
34
|
+
export interface MeetingFile {
|
|
35
|
+
frontmatter: Frontmatter;
|
|
36
|
+
body: string;
|
|
37
|
+
path: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Split markdown content into YAML frontmatter and body.
|
|
41
|
+
* Returns null frontmatter string if no valid frontmatter found.
|
|
42
|
+
*/
|
|
43
|
+
export declare function splitFrontmatter(content: string): {
|
|
44
|
+
yaml: string | null;
|
|
45
|
+
body: string;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Parse a meeting markdown file into its frontmatter and body.
|
|
49
|
+
* Returns null if the file has no valid frontmatter or is unparseable.
|
|
50
|
+
*/
|
|
51
|
+
export declare function parseFrontmatter(content: string, filePath: string): MeetingFile | null;
|
|
52
|
+
/**
|
|
53
|
+
* List meetings from a directory, sorted by date descending.
|
|
54
|
+
*/
|
|
55
|
+
export declare function listMeetings(dir: string, limit?: number): Promise<MeetingFile[]>;
|
|
56
|
+
/**
|
|
57
|
+
* Search meetings by a text query in title and body.
|
|
58
|
+
* Uses String.includes() — no regex, safe from special character crashes.
|
|
59
|
+
*/
|
|
60
|
+
export declare function searchMeetings(dir: string, query: string, limit?: number): Promise<MeetingFile[]>;
|
|
61
|
+
/**
|
|
62
|
+
* Get a single meeting by file path.
|
|
63
|
+
*/
|
|
64
|
+
export declare function getMeeting(filePath: string): Promise<MeetingFile | null>;
|
|
65
|
+
/**
|
|
66
|
+
* Find open action items across all meetings.
|
|
67
|
+
*/
|
|
68
|
+
export declare function findOpenActions(dir: string, assignee?: string): Promise<Array<{
|
|
69
|
+
path: string;
|
|
70
|
+
item: ActionItem;
|
|
71
|
+
}>>;
|
|
72
|
+
/**
|
|
73
|
+
* Build a person profile from all meetings mentioning them.
|
|
74
|
+
*/
|
|
75
|
+
export declare function getPersonProfile(dir: string, name: string): Promise<{
|
|
76
|
+
name: string;
|
|
77
|
+
meetings: Array<{
|
|
78
|
+
title: string;
|
|
79
|
+
date: string;
|
|
80
|
+
path: string;
|
|
81
|
+
}>;
|
|
82
|
+
openActions: ActionItem[];
|
|
83
|
+
topics: string[];
|
|
84
|
+
}>;
|
package/dist/reader.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// minutes-sdk — conversation memory for AI agents
|
|
2
|
+
//
|
|
3
|
+
// Query meeting transcripts, decisions, and action items from any
|
|
4
|
+
// AI agent or application. The "Mem0 for human conversations."
|
|
5
|
+
//
|
|
6
|
+
// Same functionality as the Rust `minutes-reader` crate.
|
|
7
|
+
//
|
|
8
|
+
// Architecture:
|
|
9
|
+
// ~/meetings/*.md --> parseFrontmatter() --> MeetingFile
|
|
10
|
+
// |
|
|
11
|
+
// +-------------------+
|
|
12
|
+
// v v
|
|
13
|
+
// listMeetings() searchMeetings()
|
|
14
|
+
import { readFile, readdir } from "fs/promises";
|
|
15
|
+
import { join, extname } from "path";
|
|
16
|
+
import { parse as parseYaml } from "yaml";
|
|
17
|
+
// ── Parsing ──────────────────────────────────────────────────
|
|
18
|
+
/**
|
|
19
|
+
* Split markdown content into YAML frontmatter and body.
|
|
20
|
+
* Returns null frontmatter string if no valid frontmatter found.
|
|
21
|
+
*/
|
|
22
|
+
export function splitFrontmatter(content) {
|
|
23
|
+
if (!content.startsWith("---")) {
|
|
24
|
+
return { yaml: null, body: content };
|
|
25
|
+
}
|
|
26
|
+
const endIndex = content.indexOf("\n---", 3);
|
|
27
|
+
if (endIndex === -1) {
|
|
28
|
+
return { yaml: null, body: content };
|
|
29
|
+
}
|
|
30
|
+
const yaml = content.slice(3, endIndex).trim();
|
|
31
|
+
const bodyStart = content.indexOf("\n", endIndex + 4);
|
|
32
|
+
const body = bodyStart === -1 ? "" : content.slice(bodyStart + 1);
|
|
33
|
+
return { yaml, body };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Parse a meeting markdown file into its frontmatter and body.
|
|
37
|
+
* Returns null if the file has no valid frontmatter or is unparseable.
|
|
38
|
+
*/
|
|
39
|
+
export function parseFrontmatter(content, filePath) {
|
|
40
|
+
const { yaml, body } = splitFrontmatter(content);
|
|
41
|
+
if (!yaml)
|
|
42
|
+
return null;
|
|
43
|
+
try {
|
|
44
|
+
const parsed = parseYaml(yaml);
|
|
45
|
+
if (!parsed || typeof parsed !== "object")
|
|
46
|
+
return null;
|
|
47
|
+
const fm = {
|
|
48
|
+
title: String(parsed.title || ""),
|
|
49
|
+
type: String(parsed.type || "meeting"),
|
|
50
|
+
date: parsed.date instanceof Date ? parsed.date.toISOString() : String(parsed.date || ""),
|
|
51
|
+
duration: String(parsed.duration || ""),
|
|
52
|
+
source: parsed.source ? String(parsed.source) : undefined,
|
|
53
|
+
status: parsed.status ? String(parsed.status) : undefined,
|
|
54
|
+
tags: Array.isArray(parsed.tags) ? parsed.tags.map(String) : [],
|
|
55
|
+
attendees: Array.isArray(parsed.attendees)
|
|
56
|
+
? parsed.attendees.map(String)
|
|
57
|
+
: [],
|
|
58
|
+
people: Array.isArray(parsed.people) ? parsed.people.map(String) : [],
|
|
59
|
+
context: parsed.context ? String(parsed.context) : undefined,
|
|
60
|
+
calendar_event: parsed.calendar_event
|
|
61
|
+
? String(parsed.calendar_event)
|
|
62
|
+
: undefined,
|
|
63
|
+
action_items: Array.isArray(parsed.action_items)
|
|
64
|
+
? parsed.action_items.map((a) => ({
|
|
65
|
+
assignee: String(a.assignee || ""),
|
|
66
|
+
task: String(a.task || ""),
|
|
67
|
+
due: a.due ? String(a.due) : undefined,
|
|
68
|
+
status: String(a.status || "open"),
|
|
69
|
+
}))
|
|
70
|
+
: [],
|
|
71
|
+
decisions: Array.isArray(parsed.decisions)
|
|
72
|
+
? parsed.decisions.map((d) => ({
|
|
73
|
+
text: String(d.text || ""),
|
|
74
|
+
topic: d.topic ? String(d.topic) : undefined,
|
|
75
|
+
}))
|
|
76
|
+
: [],
|
|
77
|
+
intents: Array.isArray(parsed.intents)
|
|
78
|
+
? parsed.intents.map((i) => ({
|
|
79
|
+
kind: String(i.kind || ""),
|
|
80
|
+
what: String(i.what || ""),
|
|
81
|
+
who: i.who ? String(i.who) : undefined,
|
|
82
|
+
status: String(i.status || ""),
|
|
83
|
+
by_date: i.by_date ? String(i.by_date) : undefined,
|
|
84
|
+
}))
|
|
85
|
+
: [],
|
|
86
|
+
};
|
|
87
|
+
return { frontmatter: fm, body, path: filePath };
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// ── File scanning ────────────────────────────────────────────
|
|
94
|
+
/**
|
|
95
|
+
* Recursively find all .md files in a directory.
|
|
96
|
+
*/
|
|
97
|
+
async function findMarkdownFiles(dir) {
|
|
98
|
+
const results = [];
|
|
99
|
+
try {
|
|
100
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
101
|
+
for (const entry of entries) {
|
|
102
|
+
const fullPath = join(dir, entry.name);
|
|
103
|
+
if (entry.isDirectory()) {
|
|
104
|
+
// Skip hidden directories and common non-meeting dirs
|
|
105
|
+
if (!entry.name.startsWith(".")) {
|
|
106
|
+
const nested = await findMarkdownFiles(fullPath);
|
|
107
|
+
results.push(...nested);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else if (entry.isFile() &&
|
|
111
|
+
extname(entry.name).toLowerCase() === ".md") {
|
|
112
|
+
results.push(fullPath);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Directory doesn't exist or permission denied — return empty
|
|
118
|
+
}
|
|
119
|
+
return results;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Parse a single meeting file from disk.
|
|
123
|
+
*/
|
|
124
|
+
async function readMeetingFile(filePath) {
|
|
125
|
+
try {
|
|
126
|
+
const content = await readFile(filePath, "utf-8");
|
|
127
|
+
return parseFrontmatter(content, filePath);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Sort meetings by date descending (newest first).
|
|
135
|
+
*/
|
|
136
|
+
function sortByDateDesc(meetings) {
|
|
137
|
+
return meetings.sort((a, b) => {
|
|
138
|
+
const dateA = a.frontmatter.date || "";
|
|
139
|
+
const dateB = b.frontmatter.date || "";
|
|
140
|
+
return dateB.localeCompare(dateA);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
// ── Public API ───────────────────────────────────────────────
|
|
144
|
+
/**
|
|
145
|
+
* List meetings from a directory, sorted by date descending.
|
|
146
|
+
*/
|
|
147
|
+
export async function listMeetings(dir, limit = 20) {
|
|
148
|
+
const files = await findMarkdownFiles(dir);
|
|
149
|
+
const meetings = [];
|
|
150
|
+
for (const file of files) {
|
|
151
|
+
const meeting = await readMeetingFile(file);
|
|
152
|
+
if (meeting)
|
|
153
|
+
meetings.push(meeting);
|
|
154
|
+
}
|
|
155
|
+
return sortByDateDesc(meetings).slice(0, limit);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Search meetings by a text query in title and body.
|
|
159
|
+
* Uses String.includes() — no regex, safe from special character crashes.
|
|
160
|
+
*/
|
|
161
|
+
export async function searchMeetings(dir, query, limit = 20) {
|
|
162
|
+
if (!query)
|
|
163
|
+
return [];
|
|
164
|
+
const queryLower = query.toLowerCase();
|
|
165
|
+
const files = await findMarkdownFiles(dir);
|
|
166
|
+
const results = [];
|
|
167
|
+
for (const file of files) {
|
|
168
|
+
const meeting = await readMeetingFile(file);
|
|
169
|
+
if (!meeting)
|
|
170
|
+
continue;
|
|
171
|
+
const titleMatch = meeting.frontmatter.title
|
|
172
|
+
.toLowerCase()
|
|
173
|
+
.includes(queryLower);
|
|
174
|
+
const bodyMatch = meeting.body.toLowerCase().includes(queryLower);
|
|
175
|
+
if (titleMatch || bodyMatch) {
|
|
176
|
+
results.push(meeting);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return sortByDateDesc(results).slice(0, limit);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get a single meeting by file path.
|
|
183
|
+
*/
|
|
184
|
+
export async function getMeeting(filePath) {
|
|
185
|
+
return readMeetingFile(filePath);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Find open action items across all meetings.
|
|
189
|
+
*/
|
|
190
|
+
export async function findOpenActions(dir, assignee) {
|
|
191
|
+
const files = await findMarkdownFiles(dir);
|
|
192
|
+
const results = [];
|
|
193
|
+
for (const file of files) {
|
|
194
|
+
const meeting = await readMeetingFile(file);
|
|
195
|
+
if (!meeting)
|
|
196
|
+
continue;
|
|
197
|
+
for (const item of meeting.frontmatter.action_items) {
|
|
198
|
+
if (item.status !== "open")
|
|
199
|
+
continue;
|
|
200
|
+
if (assignee &&
|
|
201
|
+
item.assignee.toLowerCase() !== assignee.toLowerCase()) {
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
results.push({ path: meeting.path, item });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return results;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Build a person profile from all meetings mentioning them.
|
|
211
|
+
*/
|
|
212
|
+
export async function getPersonProfile(dir, name) {
|
|
213
|
+
const nameLower = name.toLowerCase();
|
|
214
|
+
const files = await findMarkdownFiles(dir);
|
|
215
|
+
const meetings = [];
|
|
216
|
+
const openActions = [];
|
|
217
|
+
const topicSet = new Set();
|
|
218
|
+
for (const file of files) {
|
|
219
|
+
const meeting = await readMeetingFile(file);
|
|
220
|
+
if (!meeting)
|
|
221
|
+
continue;
|
|
222
|
+
const inAttendees = meeting.frontmatter.attendees.some((a) => a.toLowerCase().includes(nameLower));
|
|
223
|
+
const inPeople = meeting.frontmatter.people.some((p) => p.toLowerCase().includes(nameLower));
|
|
224
|
+
const inBody = meeting.body.toLowerCase().includes(nameLower);
|
|
225
|
+
if (inAttendees || inPeople || inBody) {
|
|
226
|
+
meetings.push({
|
|
227
|
+
title: meeting.frontmatter.title,
|
|
228
|
+
date: meeting.frontmatter.date,
|
|
229
|
+
path: meeting.path,
|
|
230
|
+
});
|
|
231
|
+
for (const tag of meeting.frontmatter.tags) {
|
|
232
|
+
topicSet.add(tag);
|
|
233
|
+
}
|
|
234
|
+
for (const item of meeting.frontmatter.action_items) {
|
|
235
|
+
if (item.status === "open" &&
|
|
236
|
+
item.assignee.toLowerCase().includes(nameLower)) {
|
|
237
|
+
openActions.push(item);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
name,
|
|
244
|
+
meetings: meetings.sort((a, b) => b.date.localeCompare(a.date)),
|
|
245
|
+
openActions,
|
|
246
|
+
topics: Array.from(topicSet),
|
|
247
|
+
};
|
|
248
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "minutes-sdk",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Conversation memory SDK — query meeting transcripts, decisions, and action items from any AI agent or application",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist/"
|
|
10
|
+
],
|
|
11
|
+
"keywords": [
|
|
12
|
+
"meeting",
|
|
13
|
+
"transcription",
|
|
14
|
+
"memory",
|
|
15
|
+
"ai",
|
|
16
|
+
"agent",
|
|
17
|
+
"mcp",
|
|
18
|
+
"claude",
|
|
19
|
+
"langchain",
|
|
20
|
+
"conversation",
|
|
21
|
+
"whisper"
|
|
22
|
+
],
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/silverstein/minutes.git"
|
|
26
|
+
},
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"author": "Mat Silverstein",
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc",
|
|
31
|
+
"test": "vitest run",
|
|
32
|
+
"prepublishOnly": "npm run build"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"yaml": "^2.8.3"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^22.0.0",
|
|
39
|
+
"typescript": "^5.5.0",
|
|
40
|
+
"vitest": "^4.1.0"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18"
|
|
44
|
+
}
|
|
45
|
+
}
|