@ikenga/mcp-meetings 0.1.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.
Files changed (39) hide show
  1. package/LICENSE +201 -0
  2. package/dist/index.d.ts +132 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +97 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/index.test.d.ts +2 -0
  7. package/dist/index.test.d.ts.map +1 -0
  8. package/dist/index.test.js +72 -0
  9. package/dist/index.test.js.map +1 -0
  10. package/dist/integration.test.d.ts +2 -0
  11. package/dist/integration.test.d.ts.map +1 -0
  12. package/dist/integration.test.js +181 -0
  13. package/dist/integration.test.js.map +1 -0
  14. package/dist/sqlite.d.ts +49 -0
  15. package/dist/sqlite.d.ts.map +1 -0
  16. package/dist/sqlite.js +303 -0
  17. package/dist/sqlite.js.map +1 -0
  18. package/dist/sqlite.test.d.ts +2 -0
  19. package/dist/sqlite.test.d.ts.map +1 -0
  20. package/dist/sqlite.test.js +36 -0
  21. package/dist/sqlite.test.js.map +1 -0
  22. package/dist/tools/recorder.d.ts +94 -0
  23. package/dist/tools/recorder.d.ts.map +1 -0
  24. package/dist/tools/recorder.js +187 -0
  25. package/dist/tools/recorder.js.map +1 -0
  26. package/dist/tools/recorder.test.d.ts +2 -0
  27. package/dist/tools/recorder.test.d.ts.map +1 -0
  28. package/dist/tools/recorder.test.js +94 -0
  29. package/dist/tools/recorder.test.js.map +1 -0
  30. package/dist/tools/search.d.ts +35 -0
  31. package/dist/tools/search.d.ts.map +1 -0
  32. package/dist/tools/search.js +112 -0
  33. package/dist/tools/search.js.map +1 -0
  34. package/dist/tools/search.test.d.ts +2 -0
  35. package/dist/tools/search.test.d.ts.map +1 -0
  36. package/dist/tools/search.test.js +116 -0
  37. package/dist/tools/search.test.js.map +1 -0
  38. package/manifest.json +39 -0
  39. package/package.json +38 -0
@@ -0,0 +1,181 @@
1
+ import { describe, it, before, after } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import os from 'node:os';
6
+ import crypto from 'node:crypto';
7
+ import { MeetingsDbClient } from '@ikenga/meetings-contract';
8
+ import { BetterSqliteExecutor } from './sqlite.js';
9
+ import { handleSearchTool } from './tools/search.js';
10
+ import { handleRecorderTool } from './tools/recorder.js';
11
+ describe('MCP Meetings Real SQLite Integration Test', () => {
12
+ let tmpDbPath;
13
+ let executor;
14
+ let client;
15
+ const meetingId = '7b053bb1-29a3-4e07-bfc5-3c4e40d08033';
16
+ const speaker1Id = '8c053bb1-29a3-4e07-bfc5-3c4e40d08034';
17
+ const speaker2Id = '9d053bb1-29a3-4e07-bfc5-3c4e40d08035';
18
+ const transcript1Id = 'ae053bb1-29a3-4e07-bfc5-3c4e40d08036';
19
+ const transcript2Id = 'bf053bb1-29a3-4e07-bfc5-3c4e40d08037';
20
+ const summaryId = 'd1053bb1-29a3-4e07-bfc5-3c4e40d08039';
21
+ before(async () => {
22
+ tmpDbPath = path.join(os.tmpdir(), `test-meetings-${Date.now()}-${crypto.randomBytes(4).toString('hex')}.db`);
23
+ executor = new BetterSqliteExecutor(tmpDbPath);
24
+ client = new MeetingsDbClient(executor);
25
+ // Insert meeting row with nullable columns explicitly NULL in SQLite
26
+ await executor.exec(`
27
+ INSERT INTO meetings (
28
+ id, title, platform, url, status, start_time, end_time, duration_seconds, video_path, audio_path, created_at, updated_at
29
+ ) VALUES (
30
+ '${meetingId}',
31
+ 'test meeting 1',
32
+ 'google_meet',
33
+ 'https://meet.google.com/test-abc-xyz',
34
+ 'completed',
35
+ '2026-09-02T10:00:00.000Z',
36
+ '2026-09-02T10:45:00.000Z',
37
+ 2700,
38
+ NULL,
39
+ NULL,
40
+ '2026-09-02T09:55:00.000Z',
41
+ '2026-09-02T10:46:00.000Z'
42
+ );
43
+
44
+ INSERT INTO meeting_speakers (
45
+ id, meeting_id, name, avatar_url, contact_id, speaker_source
46
+ ) VALUES (
47
+ '${speaker1Id}',
48
+ '${meetingId}',
49
+ 'Speaker 1',
50
+ NULL,
51
+ NULL,
52
+ 'dom_cue'
53
+ ), (
54
+ '${speaker2Id}',
55
+ '${meetingId}',
56
+ 'Speaker 2',
57
+ NULL,
58
+ NULL,
59
+ 'audio_embedding'
60
+ );
61
+
62
+ INSERT INTO meeting_transcripts (
63
+ id, meeting_id, speaker_id, speaker_name, speaker_source, start_ms, end_ms, text, confidence, words_json
64
+ ) VALUES (
65
+ '${transcript1Id}',
66
+ '${meetingId}',
67
+ '${speaker1Id}',
68
+ 'Speaker 1',
69
+ 'dom_cue',
70
+ 0,
71
+ 4500,
72
+ 'test transcript segment alpha with sample search target phrase',
73
+ 0.98,
74
+ NULL
75
+ ), (
76
+ '${transcript2Id}',
77
+ '${meetingId}',
78
+ '${speaker2Id}',
79
+ 'Speaker 2',
80
+ 'audio_embedding',
81
+ 5000,
82
+ 10200,
83
+ 'test transcript segment beta with target phrase and additional details',
84
+ 0.99,
85
+ NULL
86
+ );
87
+
88
+ INSERT INTO meeting_summaries (
89
+ id, meeting_id, executive_summary, key_decisions_json, topics_json, created_at
90
+ ) VALUES (
91
+ '${summaryId}',
92
+ '${meetingId}',
93
+ 'test executive summary lorem ipsum for test meeting 1',
94
+ '["test decision 1", "test decision 2"]',
95
+ '["topic 1", "topic 2"]',
96
+ NULL
97
+ );
98
+ `);
99
+ });
100
+ after(() => {
101
+ executor.close();
102
+ if (fs.existsSync(tmpDbPath)) {
103
+ try {
104
+ fs.unlinkSync(tmpDbPath);
105
+ }
106
+ catch { }
107
+ }
108
+ });
109
+ it('searches transcripts across meeting 7b053bb1-29a3-4e07-bfc5-3c4e40d08033 with real SQLite', async () => {
110
+ const res = await handleSearchTool(client, 'search_transcripts', {
111
+ query: 'target phrase',
112
+ meeting_id: meetingId,
113
+ });
114
+ assert.equal(res.count, 2);
115
+ assert.equal(res.segments.length, 2);
116
+ assert.equal(res.segments[0]?.speaker, 'Speaker 1');
117
+ assert.equal(res.segments[0]?.start_ms, 0);
118
+ assert.equal(res.segments[1]?.speaker, 'Speaker 2');
119
+ assert.equal(res.segments[1]?.start_ms, 5000);
120
+ assert.ok(res.segments[1]?.text.includes('target phrase'));
121
+ });
122
+ it('retrieves full meeting transcript and speakers for meeting 7b053bb1-29a3-4e07-bfc5-3c4e40d08033', async () => {
123
+ const res = await handleSearchTool(client, 'get_meeting_transcript', {
124
+ meeting_id: meetingId,
125
+ });
126
+ assert.equal(res.meeting_id, meetingId);
127
+ assert.equal(res.segment_count, 2);
128
+ assert.equal(res.speakers.length, 2);
129
+ assert.equal(res.speakers[0]?.name, 'Speaker 1');
130
+ assert.equal(res.speakers[1]?.name, 'Speaker 2');
131
+ });
132
+ it('retrieves meeting summary without failing on SQLite NULL created_at', async () => {
133
+ const res = await handleSearchTool(client, 'get_meeting_summary', {
134
+ meeting_id: meetingId,
135
+ });
136
+ assert.equal(res.found, true);
137
+ assert.equal(res.meeting_id, meetingId);
138
+ assert.ok(res.executive_summary.includes('test executive summary'));
139
+ assert.equal(res.key_decisions.length, 2);
140
+ assert.deepEqual(res.key_decisions, [
141
+ 'test decision 1',
142
+ 'test decision 2',
143
+ ]);
144
+ });
145
+ it('records and updates meetings via recorder tools in SQLite', async () => {
146
+ // 1. Start recording
147
+ const startRes = await handleRecorderTool(client, 'start_recording', {
148
+ title: 'test recording 2',
149
+ });
150
+ assert.equal(startRes.ok, true);
151
+ assert.equal(startRes.status, 'recording');
152
+ const newId = startRes.meeting_id;
153
+ assert.ok(newId);
154
+ // 2. Stop recording
155
+ const stopRes = await handleRecorderTool(client, 'stop_recording', {
156
+ meeting_id: newId,
157
+ });
158
+ assert.equal(stopRes.ok, true);
159
+ assert.equal(stopRes.status, 'completed');
160
+ // 3. Schedule recording
161
+ const schedRes = await handleRecorderTool(client, 'schedule_recording', {
162
+ title: 'test scheduled meeting 3',
163
+ start_time: '2026-09-10T14:00:00.000Z',
164
+ platform: 'google_meet',
165
+ url: 'https://meet.google.com/test-link',
166
+ });
167
+ assert.equal(schedRes.ok, true);
168
+ assert.equal(schedRes.status, 'scheduled');
169
+ assert.equal(schedRes.title, 'test scheduled meeting 3');
170
+ // 4. List meetings
171
+ const listRes = await handleRecorderTool(client, 'list_meetings', {
172
+ limit: 10,
173
+ });
174
+ assert.ok(listRes.count >= 3);
175
+ const titles = listRes.meetings.map((m) => m.title);
176
+ assert.ok(titles.includes('test meeting 1'));
177
+ assert.ok(titles.includes('test recording 2'));
178
+ assert.ok(titles.includes('test scheduled meeting 3'));
179
+ });
180
+ });
181
+ //# sourceMappingURL=integration.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration.test.js","sourceRoot":"","sources":["../src/integration.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import Database from 'better-sqlite3';
2
+ import { SqlExecutor } from '@ikenga/meetings-contract';
3
+ export declare function resolveDatabasePath(customPath?: string): string;
4
+ /**
5
+ * Normalizes SQLite rows: converts `null` values to `undefined` so that
6
+ * Zod schemas with `.optional()` parse without throwing invalid_type errors.
7
+ */
8
+ export declare function normalizeRow<T>(row: any): T;
9
+ /**
10
+ * The tables this pkg is allowed to touch.
11
+ *
12
+ * Mirrors `permissions["sqlite.tables"]` in the com.ikenga.meetings manifest.
13
+ * That manifest scope is enforced by the shell for the IFRAME path
14
+ * (`host.dbQuery` / `host.dbExec` check it in pkg-iframe-host.tsx) — but a pkg's
15
+ * BACKEND process has no scoped accessor available to it, so opening the
16
+ * database with better-sqlite3 means holding a connection with full read/write
17
+ * over every table in `ikenga.db`: tasks, email, finance, everything.
18
+ *
19
+ * That is a real widening of trust and it should not be incidental. This guard
20
+ * re-imposes the manifest scope in code, so a bug — or a prompt injection
21
+ * reaching a tool argument — cannot reach beyond the meetings domain. It is not
22
+ * a substitute for host-side enforcement; it is the best a pkg can do for itself
23
+ * until a scoped backend accessor exists.
24
+ */
25
+ export declare const ALLOWED_TABLES: readonly ["meetings", "meeting_speakers", "meeting_transcripts", "meeting_action_items", "meeting_summaries"];
26
+ /** Every table name a statement references, lowercased. */
27
+ export declare function tablesReferenced(sql: string): string[];
28
+ /** Throw unless every table a statement touches is in the pkg's scope. */
29
+ export declare function assertInScope(sql: string): void;
30
+ export declare class BetterSqliteExecutor implements SqlExecutor {
31
+ readonly db: Database.Database;
32
+ /** True only for a database this pkg created for itself (tests). */
33
+ private readonly ownsSchema;
34
+ constructor(dbPathOrInstance?: string | Database.Database);
35
+ private initSchema;
36
+ query<T = unknown>(sql: string, params?: unknown[]): Promise<T[]>;
37
+ exec(sql: string, params?: unknown[]): Promise<void | unknown>;
38
+ close(): void;
39
+ }
40
+ export declare class InMemorySqlExecutor implements SqlExecutor {
41
+ private meetingsMap;
42
+ private transcriptsMap;
43
+ private speakersMap;
44
+ private summariesMap;
45
+ private actionsMap;
46
+ query<T = unknown>(sql: string, params?: unknown[]): Promise<T[]>;
47
+ exec(sql: string, params?: unknown[]): Promise<void>;
48
+ }
49
+ //# sourceMappingURL=sqlite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../src/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAItC,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAExD,wBAAgB,mBAAmB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAiB/D;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC,CAa3C;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,cAAc,+GAMjB,CAAC;AAEX,2DAA2D;AAC3D,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAQtD;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAS/C;AAED,qBAAa,oBAAqB,YAAW,WAAW;IACtD,SAAgB,EAAE,EAAE,QAAQ,CAAC,QAAQ,CAAC;IACtC,oEAAoE;IACpE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAU;gBAEzB,gBAAgB,GAAE,MAAM,GAAG,QAAQ,CAAC,QAAgC;IAyChF,OAAO,CAAC,UAAU;IAoEZ,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,OAAO,EAAO,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAOrE,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,OAAO,EAAO,GAAG,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC;IASxE,KAAK,IAAI,IAAI;CAGd;AAED,qBAAa,mBAAoB,YAAW,WAAW;IACrD,OAAO,CAAC,WAAW,CAA0B;IAC7C,OAAO,CAAC,cAAc,CAA4B;IAClD,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,YAAY,CAA0B;IAC9C,OAAO,CAAC,UAAU,CAA4B;IAExC,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,OAAO,EAAO,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAgCrE,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,OAAO,EAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAsC/D"}
package/dist/sqlite.js ADDED
@@ -0,0 +1,303 @@
1
+ import Database from 'better-sqlite3';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import os from 'node:os';
5
+ export function resolveDatabasePath(customPath) {
6
+ if (customPath)
7
+ return customPath;
8
+ if (process.env.IKENGA_DB)
9
+ return process.env.IKENGA_DB;
10
+ if (process.env.SQLITE_PATH)
11
+ return process.env.SQLITE_PATH;
12
+ if (process.env.MEETINGS_DB_PATH)
13
+ return process.env.MEETINGS_DB_PATH;
14
+ const localSharePath = path.join(os.homedir(), '.local/share/app.ikenga/ikenga.db');
15
+ if (fs.existsSync(localSharePath)) {
16
+ return localSharePath;
17
+ }
18
+ const dotIkengaPath = path.join(os.homedir(), '.ikenga/ikenga.db');
19
+ if (fs.existsSync(dotIkengaPath)) {
20
+ return dotIkengaPath;
21
+ }
22
+ return localSharePath;
23
+ }
24
+ /**
25
+ * Normalizes SQLite rows: converts `null` values to `undefined` so that
26
+ * Zod schemas with `.optional()` parse without throwing invalid_type errors.
27
+ */
28
+ export function normalizeRow(row) {
29
+ if (!row || typeof row !== 'object')
30
+ return row;
31
+ const out = Array.isArray(row) ? [] : {};
32
+ for (const [k, v] of Object.entries(row)) {
33
+ if (v === null) {
34
+ out[k] = undefined;
35
+ }
36
+ else if (typeof v === 'object') {
37
+ out[k] = normalizeRow(v);
38
+ }
39
+ else {
40
+ out[k] = v;
41
+ }
42
+ }
43
+ return out;
44
+ }
45
+ /**
46
+ * The tables this pkg is allowed to touch.
47
+ *
48
+ * Mirrors `permissions["sqlite.tables"]` in the com.ikenga.meetings manifest.
49
+ * That manifest scope is enforced by the shell for the IFRAME path
50
+ * (`host.dbQuery` / `host.dbExec` check it in pkg-iframe-host.tsx) — but a pkg's
51
+ * BACKEND process has no scoped accessor available to it, so opening the
52
+ * database with better-sqlite3 means holding a connection with full read/write
53
+ * over every table in `ikenga.db`: tasks, email, finance, everything.
54
+ *
55
+ * That is a real widening of trust and it should not be incidental. This guard
56
+ * re-imposes the manifest scope in code, so a bug — or a prompt injection
57
+ * reaching a tool argument — cannot reach beyond the meetings domain. It is not
58
+ * a substitute for host-side enforcement; it is the best a pkg can do for itself
59
+ * until a scoped backend accessor exists.
60
+ */
61
+ export const ALLOWED_TABLES = [
62
+ 'meetings',
63
+ 'meeting_speakers',
64
+ 'meeting_transcripts',
65
+ 'meeting_action_items',
66
+ 'meeting_summaries',
67
+ ];
68
+ /** Every table name a statement references, lowercased. */
69
+ export function tablesReferenced(sql) {
70
+ const found = new Set();
71
+ const re = /\b(?:from|join|into|update|table)\s+["'`\[]?([A-Za-z_][A-Za-z0-9_]*)["'`\]]?/gi;
72
+ let m;
73
+ while ((m = re.exec(sql)) !== null) {
74
+ if (m[1])
75
+ found.add(m[1].toLowerCase());
76
+ }
77
+ return [...found];
78
+ }
79
+ /** Throw unless every table a statement touches is in the pkg's scope. */
80
+ export function assertInScope(sql) {
81
+ const allowed = new Set(ALLOWED_TABLES);
82
+ const out = tablesReferenced(sql).filter((t) => !allowed.has(t));
83
+ if (out.length > 0) {
84
+ throw new Error(`meetings pkg may not touch table(s): ${out.join(', ')}. ` +
85
+ `Allowed: ${ALLOWED_TABLES.join(', ')}.`);
86
+ }
87
+ }
88
+ export class BetterSqliteExecutor {
89
+ db;
90
+ /** True only for a database this pkg created for itself (tests). */
91
+ ownsSchema;
92
+ constructor(dbPathOrInstance = resolveDatabasePath()) {
93
+ const livePath = resolveDatabasePath();
94
+ this.ownsSchema =
95
+ typeof dbPathOrInstance === 'string'
96
+ ? dbPathOrInstance === ':memory:' || path.resolve(dbPathOrInstance) !== path.resolve(livePath)
97
+ : false;
98
+ if (typeof dbPathOrInstance === 'string') {
99
+ if (dbPathOrInstance !== ':memory:') {
100
+ const dir = path.dirname(dbPathOrInstance);
101
+ if (!fs.existsSync(dir)) {
102
+ fs.mkdirSync(dir, { recursive: true });
103
+ }
104
+ }
105
+ this.db = new Database(dbPathOrInstance);
106
+ }
107
+ else {
108
+ this.db = dbPathOrInstance;
109
+ }
110
+ try {
111
+ this.db.pragma('journal_mode = WAL');
112
+ this.db.pragma('busy_timeout = 5000');
113
+ this.db.pragma('synchronous = NORMAL');
114
+ }
115
+ catch {
116
+ // Pragmas may not apply to all database configurations
117
+ }
118
+ // Schema is owned by the shell's migration runner (0063_meetings_domain,
119
+ // recorded in `_pa_migrations`), NOT by this pkg.
120
+ //
121
+ // Creating tables here would put DDL into the user's canonical database
122
+ // from a pkg process, outside the migration ledger. If this inline schema
123
+ // ever drifted from the migration, the divergence would be silent and
124
+ // unattributable. So the tables are only created for a database this pkg
125
+ // owns outright — an in-memory or explicitly-pathed test database — and
126
+ // never for the live one.
127
+ if (this.ownsSchema) {
128
+ this.initSchema();
129
+ }
130
+ }
131
+ initSchema() {
132
+ this.db.exec(`
133
+ CREATE TABLE IF NOT EXISTS meetings (
134
+ id TEXT PRIMARY KEY,
135
+ title TEXT NOT NULL,
136
+ platform TEXT NOT NULL,
137
+ url TEXT,
138
+ status TEXT NOT NULL,
139
+ start_time TEXT NOT NULL,
140
+ end_time TEXT,
141
+ duration_seconds INTEGER NOT NULL DEFAULT 0,
142
+ video_path TEXT,
143
+ audio_path TEXT,
144
+ created_at TEXT NOT NULL,
145
+ updated_at TEXT NOT NULL
146
+ );
147
+
148
+ CREATE TABLE IF NOT EXISTS meeting_speakers (
149
+ id TEXT PRIMARY KEY,
150
+ meeting_id TEXT NOT NULL,
151
+ name TEXT NOT NULL,
152
+ avatar_url TEXT,
153
+ contact_id TEXT,
154
+ speaker_source TEXT NOT NULL DEFAULT 'dom_cue'
155
+ );
156
+
157
+ CREATE TABLE IF NOT EXISTS meeting_transcripts (
158
+ id TEXT PRIMARY KEY,
159
+ meeting_id TEXT NOT NULL,
160
+ speaker_id TEXT,
161
+ speaker_name TEXT,
162
+ speaker_source TEXT,
163
+ start_ms INTEGER NOT NULL,
164
+ end_ms INTEGER NOT NULL,
165
+ text TEXT NOT NULL,
166
+ confidence REAL NOT NULL DEFAULT 1.0,
167
+ words_json TEXT
168
+ );
169
+
170
+ CREATE TABLE IF NOT EXISTS meeting_action_items (
171
+ id TEXT PRIMARY KEY,
172
+ meeting_id TEXT NOT NULL,
173
+ title TEXT NOT NULL,
174
+ assignee TEXT,
175
+ due_date TEXT,
176
+ status TEXT NOT NULL DEFAULT 'pending',
177
+ task_id TEXT
178
+ );
179
+
180
+ CREATE TABLE IF NOT EXISTS meeting_summaries (
181
+ id TEXT PRIMARY KEY,
182
+ meeting_id TEXT NOT NULL,
183
+ executive_summary TEXT NOT NULL,
184
+ key_decisions_json TEXT NOT NULL DEFAULT '[]',
185
+ topics_json TEXT NOT NULL DEFAULT '[]',
186
+ created_at TEXT
187
+ );
188
+
189
+ CREATE INDEX IF NOT EXISTS idx_meetings_status ON meetings(status);
190
+ CREATE INDEX IF NOT EXISTS idx_meetings_created_at ON meetings(created_at);
191
+ CREATE INDEX IF NOT EXISTS idx_meeting_speakers_meeting_id ON meeting_speakers(meeting_id);
192
+ CREATE INDEX IF NOT EXISTS idx_meeting_transcripts_meeting_id ON meeting_transcripts(meeting_id);
193
+ CREATE INDEX IF NOT EXISTS idx_meeting_transcripts_start_ms ON meeting_transcripts(meeting_id, start_ms);
194
+ CREATE INDEX IF NOT EXISTS idx_meeting_action_items_meeting_id ON meeting_action_items(meeting_id);
195
+ CREATE INDEX IF NOT EXISTS idx_meeting_summaries_meeting_id ON meeting_summaries(meeting_id);
196
+ `);
197
+ }
198
+ async query(sql, params = []) {
199
+ assertInScope(sql);
200
+ const stmt = this.db.prepare(sql);
201
+ const rows = stmt.all(...params);
202
+ return rows.map((r) => normalizeRow(r));
203
+ }
204
+ async exec(sql, params = []) {
205
+ assertInScope(sql);
206
+ if (params && params.length > 0) {
207
+ const stmt = this.db.prepare(sql);
208
+ return stmt.run(...params);
209
+ }
210
+ return this.db.exec(sql);
211
+ }
212
+ close() {
213
+ this.db.close();
214
+ }
215
+ }
216
+ export class InMemorySqlExecutor {
217
+ meetingsMap = new Map();
218
+ transcriptsMap = new Map();
219
+ speakersMap = new Map();
220
+ summariesMap = new Map();
221
+ actionsMap = new Map();
222
+ async query(sql, params = []) {
223
+ let results = [];
224
+ if (sql.includes('SELECT * FROM meetings WHERE id = ?')) {
225
+ const id = String(params[0]);
226
+ const found = this.meetingsMap.get(id);
227
+ results = found ? [found] : [];
228
+ }
229
+ else if (sql.includes('SELECT * FROM meetings')) {
230
+ results = Array.from(this.meetingsMap.values());
231
+ }
232
+ else if (sql.includes('SELECT * FROM meeting_transcripts WHERE text LIKE ?')) {
233
+ const all = [];
234
+ for (const list of this.transcriptsMap.values()) {
235
+ all.push(...list);
236
+ }
237
+ const rawPattern = String(params[0] ?? '').replace(/%/g, '').toLowerCase();
238
+ results = all.filter((r) => r.text?.toLowerCase().includes(rawPattern));
239
+ }
240
+ else if (sql.includes('SELECT * FROM meeting_transcripts WHERE meeting_id = ?')) {
241
+ const id = String(params[0]);
242
+ results = this.transcriptsMap.get(id) ?? [];
243
+ }
244
+ else if (sql.includes('SELECT * FROM meeting_speakers WHERE meeting_id = ?')) {
245
+ const id = String(params[0]);
246
+ results = this.speakersMap.get(id) ?? [];
247
+ }
248
+ else if (sql.includes('SELECT * FROM meeting_summaries WHERE meeting_id = ?')) {
249
+ const id = String(params[0]);
250
+ const found = this.summariesMap.get(id);
251
+ results = found ? [found] : [];
252
+ }
253
+ else if (sql.includes('SELECT * FROM meeting_action_items WHERE meeting_id = ?')) {
254
+ const id = String(params[0]);
255
+ results = this.actionsMap.get(id) ?? [];
256
+ }
257
+ return results.map((r) => normalizeRow(r));
258
+ }
259
+ async exec(sql, params = []) {
260
+ if (sql.includes('INSERT INTO meetings')) {
261
+ const [id, title, platform, url, status, start_time, end_time, duration_seconds, video_path, audio_path, created_at, updated_at] = params;
262
+ this.meetingsMap.set(String(id), {
263
+ id, title, platform, url, status, start_time, end_time, duration_seconds, video_path, audio_path, created_at, updated_at
264
+ });
265
+ }
266
+ else if (sql.includes('UPDATE meetings SET')) {
267
+ const id = String(params[params.length - 1]);
268
+ const existing = this.meetingsMap.get(id);
269
+ if (existing) {
270
+ existing.status = params[0];
271
+ existing.updated_at = params[1];
272
+ if (params.length > 3) {
273
+ existing.end_time = params[2];
274
+ }
275
+ }
276
+ }
277
+ else if (sql.includes('INSERT INTO meeting_speakers')) {
278
+ const [id, meeting_id, name, avatar_url, contact_id, speaker_source] = params;
279
+ const list = this.speakersMap.get(String(meeting_id)) ?? [];
280
+ list.push({ id, meeting_id, name, avatar_url, contact_id, speaker_source });
281
+ this.speakersMap.set(String(meeting_id), list);
282
+ }
283
+ else if (sql.includes('INSERT INTO meeting_transcripts')) {
284
+ const [id, meeting_id, speaker_id, speaker_name, speaker_source, start_ms, end_ms, text, confidence, words_json] = params;
285
+ const list = this.transcriptsMap.get(String(meeting_id)) ?? [];
286
+ list.push({ id, meeting_id, speaker_id, speaker_name, speaker_source, start_ms, end_ms, text, confidence, words_json });
287
+ this.transcriptsMap.set(String(meeting_id), list);
288
+ }
289
+ else if (sql.includes('INSERT INTO meeting_summaries')) {
290
+ const [id, meeting_id, executive_summary, key_decisions_json, topics_json, created_at] = params;
291
+ this.summariesMap.set(String(meeting_id), {
292
+ id, meeting_id, executive_summary, key_decisions_json, topics_json, created_at
293
+ });
294
+ }
295
+ else if (sql.includes('INSERT INTO meeting_action_items')) {
296
+ const [id, meeting_id, title, assignee, due_date, status, task_id] = params;
297
+ const list = this.actionsMap.get(String(meeting_id)) ?? [];
298
+ list.push({ id, meeting_id, title, assignee, due_date, status, task_id });
299
+ this.actionsMap.set(String(meeting_id), list);
300
+ }
301
+ }
302
+ }
303
+ //# sourceMappingURL=sqlite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../src/sqlite.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sqlite.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.test.d.ts","sourceRoot":"","sources":["../src/sqlite.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,36 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { assertInScope, tablesReferenced, ALLOWED_TABLES } from './sqlite.js';
4
+ /**
5
+ * A pkg's backend process opens ikenga.db with full read/write over every
6
+ * table — there is no scoped accessor for it the way `host.dbQuery` scopes the
7
+ * iframe. These tests pin the in-code guard that re-imposes the manifest scope.
8
+ * Fabricated rows were written into the user's real database earlier in this
9
+ * plan's history through exactly this unguarded path.
10
+ */
11
+ describe('meetings pkg sqlite scope guard', () => {
12
+ it('allows the pkg its own tables', () => {
13
+ for (const t of ALLOWED_TABLES) {
14
+ assertInScope(`SELECT * FROM ${t} WHERE id = ?`);
15
+ }
16
+ });
17
+ it('refuses tables belonging to other domains', () => {
18
+ for (const sql of [
19
+ 'SELECT * FROM tasks',
20
+ 'INSERT INTO email_index (id) VALUES (?)',
21
+ 'UPDATE deals SET amount = ?',
22
+ 'DELETE FROM _pa_migrations',
23
+ ]) {
24
+ assert.throws(() => assertInScope(sql), /may not touch table/, sql);
25
+ }
26
+ });
27
+ it('catches a joined table, not just the leading one', () => {
28
+ assert.throws(() => assertInScope('SELECT * FROM meetings JOIN tasks ON tasks.id = meetings.id'), /tasks/);
29
+ });
30
+ it('extracts every referenced table', () => {
31
+ const t = tablesReferenced('SELECT * FROM meetings JOIN meeting_speakers ON 1=1');
32
+ assert.ok(t.includes('meetings'));
33
+ assert.ok(t.includes('meeting_speakers'));
34
+ });
35
+ });
36
+ //# sourceMappingURL=sqlite.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.test.js","sourceRoot":"","sources":["../src/sqlite.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE9E;;;;;;GAMG;AACH,QAAQ,CAAC,iCAAiC,EAAE,GAAG,EAAE;IAC/C,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,KAAK,MAAM,CAAC,IAAI,cAAc,EAAE,CAAC;YAC/B,aAAa,CAAC,iBAAiB,CAAC,eAAe,CAAC,CAAC;QACnD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,KAAK,MAAM,GAAG,IAAI;YAChB,qBAAqB;YACrB,yCAAyC;YACzC,6BAA6B;YAC7B,4BAA4B;SAC7B,EAAE,CAAC;YACF,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,qBAAqB,EAAE,GAAG,CAAC,CAAC;QACtE,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,CAAC,MAAM,CACX,GAAG,EAAE,CAAC,aAAa,CAAC,6DAA6D,CAAC,EAClF,OAAO,CACR,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,CAAC,GAAG,gBAAgB,CAAC,qDAAqD,CAAC,CAAC;QAClF,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;QAClC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}