@steno-ai/mcp 0.1.10 → 0.1.11

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/init.js CHANGED
@@ -73,7 +73,7 @@ const MIGRATIONS = [
73
73
  input_size INTEGER,
74
74
  scope TEXT NOT NULL,
75
75
  scope_id TEXT NOT NULL,
76
- session_id UUID,
76
+ session_id TEXT,
77
77
  tier_used TEXT,
78
78
  llm_model TEXT,
79
79
  facts_created INTEGER NOT NULL DEFAULT 0,
@@ -96,7 +96,7 @@ const MIGRATIONS = [
96
96
  tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
97
97
  scope TEXT NOT NULL CHECK (scope IN ('user','agent','session','hive')),
98
98
  scope_id TEXT NOT NULL,
99
- session_id UUID REFERENCES sessions(id) ON DELETE SET NULL,
99
+ session_id TEXT,
100
100
  content TEXT NOT NULL,
101
101
  embedding VECTOR(2000),
102
102
  embedding_model TEXT,
@@ -276,6 +276,133 @@ const MIGRATIONS = [
276
276
  AND f.search_vector @@ plainto_tsquery('english', search_query)
277
277
  ORDER BY ts_rank(f.search_vector, plainto_tsquery('english', search_query)) DESC LIMIT match_count);
278
278
  END; $$;`,
279
+ // Usage records unique constraint (required by increment_usage)
280
+ `ALTER TABLE usage_records ADD CONSTRAINT IF NOT EXISTS usage_records_tenant_period_unique UNIQUE (tenant_id, period_start);`,
281
+ // Extraction hash unique index
282
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_extractions_hash ON extractions(tenant_id, input_hash);`,
283
+ // match_facts RPC
284
+ `CREATE OR REPLACE FUNCTION match_facts(
285
+ query_embedding TEXT, match_tenant_id UUID, match_scope TEXT,
286
+ match_scope_id TEXT, match_count INT, min_similarity FLOAT DEFAULT 0,
287
+ match_as_of TIMESTAMPTZ DEFAULT NULL
288
+ ) RETURNS TABLE (
289
+ id UUID, tenant_id UUID, scope TEXT, scope_id TEXT, session_id TEXT,
290
+ content TEXT, embedding_model TEXT, embedding_dim INT, version INT,
291
+ lineage_id UUID, valid_from TIMESTAMPTZ, valid_until TIMESTAMPTZ,
292
+ operation TEXT, parent_id UUID, importance NUMERIC, frequency INT,
293
+ last_accessed TIMESTAMPTZ, decay_score NUMERIC, contradiction_status TEXT,
294
+ contradicts_id UUID, source_type TEXT, source_ref JSONB, confidence NUMERIC,
295
+ original_content TEXT, extraction_id UUID, extraction_tier TEXT,
296
+ modality TEXT, tags TEXT[], metadata JSONB,
297
+ event_date TIMESTAMPTZ, document_date TIMESTAMPTZ, source_chunk TEXT,
298
+ created_at TIMESTAMPTZ, similarity FLOAT
299
+ ) LANGUAGE plpgsql AS $$ BEGIN RETURN QUERY SELECT
300
+ f.id, f.tenant_id, f.scope, f.scope_id, f.session_id,
301
+ f.content, f.embedding_model, f.embedding_dim, f.version, f.lineage_id,
302
+ f.valid_from, f.valid_until, f.operation, f.parent_id, f.importance, f.frequency,
303
+ f.last_accessed, f.decay_score, f.contradiction_status, f.contradicts_id,
304
+ f.source_type, f.source_ref, f.confidence, f.original_content, f.extraction_id,
305
+ f.extraction_tier, f.modality, f.tags, f.metadata,
306
+ f.event_date, f.document_date, f.source_chunk, f.created_at,
307
+ (1 - (f.embedding <=> query_embedding::vector)) AS similarity
308
+ FROM facts f WHERE f.tenant_id = match_tenant_id AND f.scope = match_scope
309
+ AND f.scope_id = match_scope_id
310
+ AND (CASE WHEN match_as_of IS NOT NULL THEN f.valid_from <= match_as_of AND (f.valid_until IS NULL OR f.valid_until > match_as_of) ELSE f.valid_until IS NULL END)
311
+ AND (1 - (f.embedding <=> query_embedding::vector)) >= min_similarity
312
+ ORDER BY f.embedding <=> query_embedding::vector LIMIT match_count; END; $$;`,
313
+ // increment_usage RPC
314
+ `CREATE OR REPLACE FUNCTION increment_usage(p_tenant_id UUID, p_tokens INT, p_queries INT, p_extractions INT, p_cost_usd FLOAT)
315
+ RETURNS VOID LANGUAGE plpgsql AS $$ DECLARE p_start DATE := date_trunc('month', CURRENT_DATE)::date;
316
+ p_end DATE := (date_trunc('month', CURRENT_DATE) + interval '1 month' - interval '1 day')::date;
317
+ BEGIN INSERT INTO usage_records (id, tenant_id, period_start, period_end, tokens_used, queries_used, extractions_count, cost_usd)
318
+ VALUES (gen_random_uuid(), p_tenant_id, p_start, p_end, p_tokens, p_queries, p_extractions, p_cost_usd)
319
+ ON CONFLICT (tenant_id, period_start) DO UPDATE SET
320
+ tokens_used = usage_records.tokens_used + EXCLUDED.tokens_used,
321
+ queries_used = usage_records.queries_used + EXCLUDED.queries_used,
322
+ extractions_count = usage_records.extractions_count + EXCLUDED.extractions_count,
323
+ cost_usd = usage_records.cost_usd + EXCLUDED.cost_usd, updated_at = NOW(); END; $$;`,
324
+ // match_entities RPC
325
+ `CREATE OR REPLACE FUNCTION match_entities(query_embedding TEXT, match_tenant_id UUID, match_count INT DEFAULT 10, min_similarity FLOAT DEFAULT 0.3)
326
+ RETURNS TABLE (id UUID, tenant_id UUID, name TEXT, entity_type TEXT, canonical_name TEXT, properties JSONB,
327
+ embedding_model TEXT, embedding_dim INT, merge_target_id UUID, created_at TIMESTAMPTZ, updated_at TIMESTAMPTZ, similarity FLOAT)
328
+ LANGUAGE plpgsql AS $$ BEGIN RETURN QUERY SELECT e.id, e.tenant_id, e.name, e.entity_type, e.canonical_name,
329
+ e.properties, e.embedding_model, e.embedding_dim, e.merge_target_id, e.created_at, e.updated_at,
330
+ (1 - (e.embedding <=> query_embedding::vector))::float AS similarity
331
+ FROM entities e WHERE e.tenant_id = match_tenant_id AND e.embedding IS NOT NULL
332
+ AND (1 - (e.embedding <=> query_embedding::vector)) >= min_similarity
333
+ ORDER BY e.embedding <=> query_embedding::vector LIMIT match_count; END; $$;`,
334
+ // graph_traverse RPC
335
+ `CREATE OR REPLACE FUNCTION graph_traverse(match_tenant_id UUID, seed_entity_ids UUID[], max_depth INT DEFAULT 3, max_entities INT DEFAULT 200, match_as_of TIMESTAMPTZ DEFAULT NULL)
336
+ RETURNS TABLE (entity_id UUID, entity_name TEXT, entity_type TEXT, canonical_name TEXT, properties JSONB, hop_depth INT,
337
+ edge_id UUID, edge_source_id UUID, edge_target_id UUID, edge_relation TEXT, edge_type TEXT, edge_weight FLOAT,
338
+ edge_valid_from TIMESTAMPTZ, edge_valid_until TIMESTAMPTZ, edge_confidence FLOAT)
339
+ LANGUAGE plpgsql AS $$ BEGIN RETURN QUERY WITH RECURSIVE traversal AS (
340
+ SELECT e.id AS entity_id, e.name AS entity_name, e.entity_type, e.canonical_name, e.properties, 0 AS hop_depth,
341
+ NULL::UUID AS edge_id, NULL::UUID AS edge_source_id, NULL::UUID AS edge_target_id, NULL::TEXT AS edge_relation,
342
+ NULL::TEXT AS edge_type, NULL::FLOAT AS edge_weight, NULL::TIMESTAMPTZ AS edge_valid_from,
343
+ NULL::TIMESTAMPTZ AS edge_valid_until, NULL::FLOAT AS edge_confidence, ARRAY[e.id] AS visited_ids
344
+ FROM entities e WHERE e.id = ANY(seed_entity_ids) AND e.tenant_id = match_tenant_id
345
+ UNION ALL
346
+ SELECT e2.id, e2.name, e2.entity_type, e2.canonical_name, e2.properties, t.hop_depth + 1,
347
+ ed.id, ed.source_id, ed.target_id, ed.relation, ed.edge_type, ed.weight, ed.valid_from, ed.valid_until, ed.confidence,
348
+ t.visited_ids || e2.id
349
+ FROM traversal t JOIN edges ed ON (ed.source_id = t.entity_id OR ed.target_id = t.entity_id) AND ed.tenant_id = match_tenant_id
350
+ AND (CASE WHEN match_as_of IS NOT NULL THEN ed.valid_from <= match_as_of AND (ed.valid_until IS NULL OR ed.valid_until > match_as_of) ELSE ed.valid_until IS NULL END)
351
+ JOIN entities e2 ON e2.id = CASE WHEN ed.source_id = t.entity_id THEN ed.target_id ELSE ed.source_id END AND e2.tenant_id = match_tenant_id
352
+ WHERE t.hop_depth < max_depth AND e2.id != ALL(t.visited_ids)
353
+ ) SELECT traversal.entity_id, traversal.entity_name, traversal.entity_type, traversal.canonical_name, traversal.properties,
354
+ traversal.hop_depth, traversal.edge_id, traversal.edge_source_id, traversal.edge_target_id, traversal.edge_relation,
355
+ traversal.edge_type, traversal.edge_weight, traversal.edge_valid_from, traversal.edge_valid_until, traversal.edge_confidence
356
+ FROM traversal LIMIT max_entities; END; $$;`,
357
+ // get_facts_for_entities RPC
358
+ `CREATE OR REPLACE FUNCTION get_facts_for_entities(match_tenant_id UUID, entity_ids UUID[], per_entity_limit INT DEFAULT 20)
359
+ RETURNS TABLE (entity_id UUID, fact_id UUID, tenant_id UUID, scope TEXT, scope_id TEXT, session_id TEXT,
360
+ content TEXT, embedding_model TEXT, embedding_dim INTEGER, version INTEGER, lineage_id UUID,
361
+ valid_from TIMESTAMPTZ, valid_until TIMESTAMPTZ, operation TEXT, parent_id UUID,
362
+ importance NUMERIC, frequency INTEGER, last_accessed TIMESTAMPTZ, decay_score NUMERIC,
363
+ contradiction_status TEXT, contradicts_id UUID, source_type TEXT, source_ref JSONB, confidence NUMERIC,
364
+ original_content TEXT, extraction_id UUID, extraction_tier TEXT, modality TEXT, tags TEXT[], metadata JSONB,
365
+ event_date TIMESTAMPTZ, document_date TIMESTAMPTZ, source_chunk TEXT, created_at TIMESTAMPTZ)
366
+ LANGUAGE plpgsql AS $$ BEGIN RETURN QUERY SELECT ranked.entity_id, ranked.fact_id, ranked.tenant_id, ranked.scope,
367
+ ranked.scope_id, ranked.session_id, ranked.content, ranked.embedding_model, ranked.embedding_dim, ranked.version,
368
+ ranked.lineage_id, ranked.valid_from, ranked.valid_until, ranked.operation, ranked.parent_id, ranked.importance,
369
+ ranked.frequency, ranked.last_accessed, ranked.decay_score, ranked.contradiction_status, ranked.contradicts_id,
370
+ ranked.source_type, ranked.source_ref, ranked.confidence, ranked.original_content, ranked.extraction_id,
371
+ ranked.extraction_tier, ranked.modality, ranked.tags, ranked.metadata,
372
+ ranked.event_date, ranked.document_date, ranked.source_chunk, ranked.created_at
373
+ FROM (SELECT fe.entity_id, f.id AS fact_id, f.tenant_id, f.scope, f.scope_id, f.session_id, f.content,
374
+ f.embedding_model, f.embedding_dim, f.version, f.lineage_id, f.valid_from, f.valid_until, f.operation,
375
+ f.parent_id, f.importance, f.frequency, f.last_accessed, f.decay_score, f.contradiction_status, f.contradicts_id,
376
+ f.source_type, f.source_ref, f.confidence, f.original_content, f.extraction_id, f.extraction_tier, f.modality,
377
+ f.tags, f.metadata, f.event_date, f.document_date, f.source_chunk, f.created_at,
378
+ ROW_NUMBER() OVER (PARTITION BY fe.entity_id ORDER BY f.importance DESC, f.created_at DESC) AS rn
379
+ FROM fact_entities fe JOIN facts f ON f.id = fe.fact_id WHERE fe.entity_id = ANY(entity_ids)
380
+ AND f.tenant_id = match_tenant_id AND f.valid_until IS NULL AND NOT ('raw_chunk' = ANY(f.tags))) ranked
381
+ WHERE ranked.rn <= per_entity_limit; END; $$;`,
382
+ // keyword_search_facts RPC
383
+ `CREATE OR REPLACE FUNCTION keyword_search_facts(search_query TEXT, match_tenant_id UUID, match_scope TEXT,
384
+ match_scope_id TEXT, match_count INT, match_as_of TIMESTAMPTZ DEFAULT NULL)
385
+ RETURNS TABLE (id UUID, tenant_id UUID, scope TEXT, scope_id TEXT, session_id TEXT,
386
+ content TEXT, embedding_model TEXT, embedding_dim INT, version INT, lineage_id UUID,
387
+ valid_from TIMESTAMPTZ, valid_until TIMESTAMPTZ, operation TEXT, parent_id UUID,
388
+ importance NUMERIC, frequency INT, last_accessed TIMESTAMPTZ, decay_score NUMERIC,
389
+ contradiction_status TEXT, contradicts_id UUID, source_type TEXT, source_ref JSONB, confidence NUMERIC,
390
+ original_content TEXT, extraction_id UUID, extraction_tier TEXT, modality TEXT, tags TEXT[], metadata JSONB,
391
+ event_date TIMESTAMPTZ, document_date TIMESTAMPTZ, source_chunk TEXT, created_at TIMESTAMPTZ, rank_score FLOAT)
392
+ LANGUAGE plpgsql AS $$ BEGIN RETURN QUERY SELECT f.id, f.tenant_id, f.scope, f.scope_id, f.session_id,
393
+ f.content, f.embedding_model, f.embedding_dim, f.version, f.lineage_id, f.valid_from, f.valid_until,
394
+ f.operation, f.parent_id, f.importance, f.frequency, f.last_accessed, f.decay_score, f.contradiction_status,
395
+ f.contradicts_id, f.source_type, f.source_ref, f.confidence, f.original_content, f.extraction_id,
396
+ f.extraction_tier, f.modality, f.tags, f.metadata, f.event_date, f.document_date, f.source_chunk, f.created_at,
397
+ ts_rank(f.search_vector, plainto_tsquery('english', search_query)) AS rank_score
398
+ FROM facts f WHERE f.tenant_id = match_tenant_id AND f.scope = match_scope AND f.scope_id = match_scope_id
399
+ AND (CASE WHEN match_as_of IS NOT NULL THEN f.valid_from <= match_as_of AND (f.valid_until IS NULL OR f.valid_until > match_as_of) ELSE f.valid_until IS NULL END)
400
+ AND f.search_vector @@ plainto_tsquery('english', search_query)
401
+ ORDER BY rank_score DESC LIMIT match_count; END; $$;`,
402
+ // increment_trigger_fired RPC
403
+ `CREATE OR REPLACE FUNCTION increment_trigger_fired(p_tenant_id UUID, p_trigger_id UUID)
404
+ RETURNS VOID LANGUAGE plpgsql AS $$ BEGIN UPDATE triggers SET times_fired = times_fired + 1,
405
+ last_fired_at = NOW(), updated_at = NOW() WHERE tenant_id = p_tenant_id AND id = p_trigger_id; END; $$;`,
279
406
  // Default tenant
280
407
  `INSERT INTO tenants (id, name, slug, plan) VALUES ('00000000-0000-0000-0000-000000000001', 'Default', 'default', 'enterprise') ON CONFLICT DO NOTHING;`,
281
408
  ];
@@ -293,34 +420,38 @@ async function main() {
293
420
  // 2. Run migrations
294
421
  console.log('\n Running database migrations...');
295
422
  const supabase = createClient(supabaseUrl, supabaseKey);
296
- let success = 0;
297
- let skipped = 0;
298
- for (let i = 0; i < MIGRATIONS.length; i++) {
299
- try {
300
- const { error } = await supabase.rpc('exec_sql', { query: MIGRATIONS[i] }).catch(() => ({ error: { message: 'rpc not available' } }));
301
- if (error) {
302
- // Try direct REST approach
303
- const res = await fetch(`${supabaseUrl}/rest/v1/rpc/exec_sql`, {
304
- method: 'POST',
305
- headers: { 'apikey': supabaseKey, 'Authorization': `Bearer ${supabaseKey}`, 'Content-Type': 'application/json' },
306
- body: JSON.stringify({ query: MIGRATIONS[i] }),
307
- });
308
- if (res.ok) {
309
- success++;
310
- }
311
- else {
312
- skipped++;
313
- }
314
- }
315
- else {
316
- success++;
317
- }
318
- }
319
- catch {
320
- skipped++;
423
+ // Try running migrations via Supabase Management API (requires service role key)
424
+ // If that fails, write SQL to a file for manual execution
425
+ const allSql = MIGRATIONS.join('\n\n');
426
+ const sqlPath = path.join(process.cwd(), 'steno-migrations.sql');
427
+ // Attempt automatic migration via Supabase SQL endpoint
428
+ let autoMigrated = false;
429
+ try {
430
+ const res = await fetch(`${supabaseUrl}/rest/v1/rpc/exec_sql`, {
431
+ method: 'POST',
432
+ headers: { 'apikey': supabaseKey, 'Authorization': `Bearer ${supabaseKey}`, 'Content-Type': 'application/json' },
433
+ body: JSON.stringify({ query: allSql }),
434
+ });
435
+ if (res.ok) {
436
+ autoMigrated = true;
437
+ console.log(' ✓ All migrations applied automatically');
321
438
  }
322
439
  }
323
- console.log(` ✓ ${success} migrations applied, ${skipped} skipped (may already exist)`);
440
+ catch { /* RPC not available */ }
441
+ if (!autoMigrated) {
442
+ // Write SQL file for manual execution
443
+ fs.writeFileSync(sqlPath, allSql);
444
+ console.log(`
445
+ ⚠️ Could not run migrations automatically.
446
+
447
+ Please run the SQL manually:
448
+ 1. Open your Supabase dashboard → SQL Editor
449
+ 2. Paste the contents of: ${sqlPath}
450
+ 3. Click "Run"
451
+ 4. Come back here and press Enter to continue
452
+ `);
453
+ await ask(' Press Enter after running the SQL... ');
454
+ }
324
455
  // 3. Write Claude Desktop config
325
456
  const configDir = path.join(os.homedir(), 'Library', 'Application Support', 'Claude');
326
457
  const configPath = path.join(configDir, 'claude_desktop_config.json');
package/dist/init.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steno-ai/mcp",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "MCP server for Claude Code, Claude Desktop, and other MCP clients",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -9,7 +9,10 @@
9
9
  "directory": "packages/mcp-server"
10
10
  },
11
11
  "type": "module",
12
- "bin": "./dist/cli.js",
12
+ "bin": {
13
+ "steno-mcp": "./dist/cli.js",
14
+ "steno-mcp-init": "./dist/init.js"
15
+ },
13
16
  "exports": {
14
17
  ".": {
15
18
  "import": "./dist/index.js",
@@ -28,7 +31,7 @@
28
31
  "@supabase/supabase-js": "^2.49.0",
29
32
  "openai": "^4.0.0",
30
33
  "zod": "^3.25",
31
- "@steno-ai/engine": "0.1.7",
34
+ "@steno-ai/engine": "0.1.8",
32
35
  "@steno-ai/openai-adapter": "0.1.2",
33
36
  "@steno-ai/supabase-adapter": "0.1.4"
34
37
  },
package/src/init.ts CHANGED
@@ -75,7 +75,7 @@ const MIGRATIONS = [
75
75
  input_size INTEGER,
76
76
  scope TEXT NOT NULL,
77
77
  scope_id TEXT NOT NULL,
78
- session_id UUID,
78
+ session_id TEXT,
79
79
  tier_used TEXT,
80
80
  llm_model TEXT,
81
81
  facts_created INTEGER NOT NULL DEFAULT 0,
@@ -98,7 +98,7 @@ const MIGRATIONS = [
98
98
  tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
99
99
  scope TEXT NOT NULL CHECK (scope IN ('user','agent','session','hive')),
100
100
  scope_id TEXT NOT NULL,
101
- session_id UUID REFERENCES sessions(id) ON DELETE SET NULL,
101
+ session_id TEXT,
102
102
  content TEXT NOT NULL,
103
103
  embedding VECTOR(2000),
104
104
  embedding_model TEXT,
@@ -278,6 +278,133 @@ const MIGRATIONS = [
278
278
  AND f.search_vector @@ plainto_tsquery('english', search_query)
279
279
  ORDER BY ts_rank(f.search_vector, plainto_tsquery('english', search_query)) DESC LIMIT match_count);
280
280
  END; $$;`,
281
+ // Usage records unique constraint (required by increment_usage)
282
+ `ALTER TABLE usage_records ADD CONSTRAINT IF NOT EXISTS usage_records_tenant_period_unique UNIQUE (tenant_id, period_start);`,
283
+ // Extraction hash unique index
284
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_extractions_hash ON extractions(tenant_id, input_hash);`,
285
+ // match_facts RPC
286
+ `CREATE OR REPLACE FUNCTION match_facts(
287
+ query_embedding TEXT, match_tenant_id UUID, match_scope TEXT,
288
+ match_scope_id TEXT, match_count INT, min_similarity FLOAT DEFAULT 0,
289
+ match_as_of TIMESTAMPTZ DEFAULT NULL
290
+ ) RETURNS TABLE (
291
+ id UUID, tenant_id UUID, scope TEXT, scope_id TEXT, session_id TEXT,
292
+ content TEXT, embedding_model TEXT, embedding_dim INT, version INT,
293
+ lineage_id UUID, valid_from TIMESTAMPTZ, valid_until TIMESTAMPTZ,
294
+ operation TEXT, parent_id UUID, importance NUMERIC, frequency INT,
295
+ last_accessed TIMESTAMPTZ, decay_score NUMERIC, contradiction_status TEXT,
296
+ contradicts_id UUID, source_type TEXT, source_ref JSONB, confidence NUMERIC,
297
+ original_content TEXT, extraction_id UUID, extraction_tier TEXT,
298
+ modality TEXT, tags TEXT[], metadata JSONB,
299
+ event_date TIMESTAMPTZ, document_date TIMESTAMPTZ, source_chunk TEXT,
300
+ created_at TIMESTAMPTZ, similarity FLOAT
301
+ ) LANGUAGE plpgsql AS $$ BEGIN RETURN QUERY SELECT
302
+ f.id, f.tenant_id, f.scope, f.scope_id, f.session_id,
303
+ f.content, f.embedding_model, f.embedding_dim, f.version, f.lineage_id,
304
+ f.valid_from, f.valid_until, f.operation, f.parent_id, f.importance, f.frequency,
305
+ f.last_accessed, f.decay_score, f.contradiction_status, f.contradicts_id,
306
+ f.source_type, f.source_ref, f.confidence, f.original_content, f.extraction_id,
307
+ f.extraction_tier, f.modality, f.tags, f.metadata,
308
+ f.event_date, f.document_date, f.source_chunk, f.created_at,
309
+ (1 - (f.embedding <=> query_embedding::vector)) AS similarity
310
+ FROM facts f WHERE f.tenant_id = match_tenant_id AND f.scope = match_scope
311
+ AND f.scope_id = match_scope_id
312
+ AND (CASE WHEN match_as_of IS NOT NULL THEN f.valid_from <= match_as_of AND (f.valid_until IS NULL OR f.valid_until > match_as_of) ELSE f.valid_until IS NULL END)
313
+ AND (1 - (f.embedding <=> query_embedding::vector)) >= min_similarity
314
+ ORDER BY f.embedding <=> query_embedding::vector LIMIT match_count; END; $$;`,
315
+ // increment_usage RPC
316
+ `CREATE OR REPLACE FUNCTION increment_usage(p_tenant_id UUID, p_tokens INT, p_queries INT, p_extractions INT, p_cost_usd FLOAT)
317
+ RETURNS VOID LANGUAGE plpgsql AS $$ DECLARE p_start DATE := date_trunc('month', CURRENT_DATE)::date;
318
+ p_end DATE := (date_trunc('month', CURRENT_DATE) + interval '1 month' - interval '1 day')::date;
319
+ BEGIN INSERT INTO usage_records (id, tenant_id, period_start, period_end, tokens_used, queries_used, extractions_count, cost_usd)
320
+ VALUES (gen_random_uuid(), p_tenant_id, p_start, p_end, p_tokens, p_queries, p_extractions, p_cost_usd)
321
+ ON CONFLICT (tenant_id, period_start) DO UPDATE SET
322
+ tokens_used = usage_records.tokens_used + EXCLUDED.tokens_used,
323
+ queries_used = usage_records.queries_used + EXCLUDED.queries_used,
324
+ extractions_count = usage_records.extractions_count + EXCLUDED.extractions_count,
325
+ cost_usd = usage_records.cost_usd + EXCLUDED.cost_usd, updated_at = NOW(); END; $$;`,
326
+ // match_entities RPC
327
+ `CREATE OR REPLACE FUNCTION match_entities(query_embedding TEXT, match_tenant_id UUID, match_count INT DEFAULT 10, min_similarity FLOAT DEFAULT 0.3)
328
+ RETURNS TABLE (id UUID, tenant_id UUID, name TEXT, entity_type TEXT, canonical_name TEXT, properties JSONB,
329
+ embedding_model TEXT, embedding_dim INT, merge_target_id UUID, created_at TIMESTAMPTZ, updated_at TIMESTAMPTZ, similarity FLOAT)
330
+ LANGUAGE plpgsql AS $$ BEGIN RETURN QUERY SELECT e.id, e.tenant_id, e.name, e.entity_type, e.canonical_name,
331
+ e.properties, e.embedding_model, e.embedding_dim, e.merge_target_id, e.created_at, e.updated_at,
332
+ (1 - (e.embedding <=> query_embedding::vector))::float AS similarity
333
+ FROM entities e WHERE e.tenant_id = match_tenant_id AND e.embedding IS NOT NULL
334
+ AND (1 - (e.embedding <=> query_embedding::vector)) >= min_similarity
335
+ ORDER BY e.embedding <=> query_embedding::vector LIMIT match_count; END; $$;`,
336
+ // graph_traverse RPC
337
+ `CREATE OR REPLACE FUNCTION graph_traverse(match_tenant_id UUID, seed_entity_ids UUID[], max_depth INT DEFAULT 3, max_entities INT DEFAULT 200, match_as_of TIMESTAMPTZ DEFAULT NULL)
338
+ RETURNS TABLE (entity_id UUID, entity_name TEXT, entity_type TEXT, canonical_name TEXT, properties JSONB, hop_depth INT,
339
+ edge_id UUID, edge_source_id UUID, edge_target_id UUID, edge_relation TEXT, edge_type TEXT, edge_weight FLOAT,
340
+ edge_valid_from TIMESTAMPTZ, edge_valid_until TIMESTAMPTZ, edge_confidence FLOAT)
341
+ LANGUAGE plpgsql AS $$ BEGIN RETURN QUERY WITH RECURSIVE traversal AS (
342
+ SELECT e.id AS entity_id, e.name AS entity_name, e.entity_type, e.canonical_name, e.properties, 0 AS hop_depth,
343
+ NULL::UUID AS edge_id, NULL::UUID AS edge_source_id, NULL::UUID AS edge_target_id, NULL::TEXT AS edge_relation,
344
+ NULL::TEXT AS edge_type, NULL::FLOAT AS edge_weight, NULL::TIMESTAMPTZ AS edge_valid_from,
345
+ NULL::TIMESTAMPTZ AS edge_valid_until, NULL::FLOAT AS edge_confidence, ARRAY[e.id] AS visited_ids
346
+ FROM entities e WHERE e.id = ANY(seed_entity_ids) AND e.tenant_id = match_tenant_id
347
+ UNION ALL
348
+ SELECT e2.id, e2.name, e2.entity_type, e2.canonical_name, e2.properties, t.hop_depth + 1,
349
+ ed.id, ed.source_id, ed.target_id, ed.relation, ed.edge_type, ed.weight, ed.valid_from, ed.valid_until, ed.confidence,
350
+ t.visited_ids || e2.id
351
+ FROM traversal t JOIN edges ed ON (ed.source_id = t.entity_id OR ed.target_id = t.entity_id) AND ed.tenant_id = match_tenant_id
352
+ AND (CASE WHEN match_as_of IS NOT NULL THEN ed.valid_from <= match_as_of AND (ed.valid_until IS NULL OR ed.valid_until > match_as_of) ELSE ed.valid_until IS NULL END)
353
+ JOIN entities e2 ON e2.id = CASE WHEN ed.source_id = t.entity_id THEN ed.target_id ELSE ed.source_id END AND e2.tenant_id = match_tenant_id
354
+ WHERE t.hop_depth < max_depth AND e2.id != ALL(t.visited_ids)
355
+ ) SELECT traversal.entity_id, traversal.entity_name, traversal.entity_type, traversal.canonical_name, traversal.properties,
356
+ traversal.hop_depth, traversal.edge_id, traversal.edge_source_id, traversal.edge_target_id, traversal.edge_relation,
357
+ traversal.edge_type, traversal.edge_weight, traversal.edge_valid_from, traversal.edge_valid_until, traversal.edge_confidence
358
+ FROM traversal LIMIT max_entities; END; $$;`,
359
+ // get_facts_for_entities RPC
360
+ `CREATE OR REPLACE FUNCTION get_facts_for_entities(match_tenant_id UUID, entity_ids UUID[], per_entity_limit INT DEFAULT 20)
361
+ RETURNS TABLE (entity_id UUID, fact_id UUID, tenant_id UUID, scope TEXT, scope_id TEXT, session_id TEXT,
362
+ content TEXT, embedding_model TEXT, embedding_dim INTEGER, version INTEGER, lineage_id UUID,
363
+ valid_from TIMESTAMPTZ, valid_until TIMESTAMPTZ, operation TEXT, parent_id UUID,
364
+ importance NUMERIC, frequency INTEGER, last_accessed TIMESTAMPTZ, decay_score NUMERIC,
365
+ contradiction_status TEXT, contradicts_id UUID, source_type TEXT, source_ref JSONB, confidence NUMERIC,
366
+ original_content TEXT, extraction_id UUID, extraction_tier TEXT, modality TEXT, tags TEXT[], metadata JSONB,
367
+ event_date TIMESTAMPTZ, document_date TIMESTAMPTZ, source_chunk TEXT, created_at TIMESTAMPTZ)
368
+ LANGUAGE plpgsql AS $$ BEGIN RETURN QUERY SELECT ranked.entity_id, ranked.fact_id, ranked.tenant_id, ranked.scope,
369
+ ranked.scope_id, ranked.session_id, ranked.content, ranked.embedding_model, ranked.embedding_dim, ranked.version,
370
+ ranked.lineage_id, ranked.valid_from, ranked.valid_until, ranked.operation, ranked.parent_id, ranked.importance,
371
+ ranked.frequency, ranked.last_accessed, ranked.decay_score, ranked.contradiction_status, ranked.contradicts_id,
372
+ ranked.source_type, ranked.source_ref, ranked.confidence, ranked.original_content, ranked.extraction_id,
373
+ ranked.extraction_tier, ranked.modality, ranked.tags, ranked.metadata,
374
+ ranked.event_date, ranked.document_date, ranked.source_chunk, ranked.created_at
375
+ FROM (SELECT fe.entity_id, f.id AS fact_id, f.tenant_id, f.scope, f.scope_id, f.session_id, f.content,
376
+ f.embedding_model, f.embedding_dim, f.version, f.lineage_id, f.valid_from, f.valid_until, f.operation,
377
+ f.parent_id, f.importance, f.frequency, f.last_accessed, f.decay_score, f.contradiction_status, f.contradicts_id,
378
+ f.source_type, f.source_ref, f.confidence, f.original_content, f.extraction_id, f.extraction_tier, f.modality,
379
+ f.tags, f.metadata, f.event_date, f.document_date, f.source_chunk, f.created_at,
380
+ ROW_NUMBER() OVER (PARTITION BY fe.entity_id ORDER BY f.importance DESC, f.created_at DESC) AS rn
381
+ FROM fact_entities fe JOIN facts f ON f.id = fe.fact_id WHERE fe.entity_id = ANY(entity_ids)
382
+ AND f.tenant_id = match_tenant_id AND f.valid_until IS NULL AND NOT ('raw_chunk' = ANY(f.tags))) ranked
383
+ WHERE ranked.rn <= per_entity_limit; END; $$;`,
384
+ // keyword_search_facts RPC
385
+ `CREATE OR REPLACE FUNCTION keyword_search_facts(search_query TEXT, match_tenant_id UUID, match_scope TEXT,
386
+ match_scope_id TEXT, match_count INT, match_as_of TIMESTAMPTZ DEFAULT NULL)
387
+ RETURNS TABLE (id UUID, tenant_id UUID, scope TEXT, scope_id TEXT, session_id TEXT,
388
+ content TEXT, embedding_model TEXT, embedding_dim INT, version INT, lineage_id UUID,
389
+ valid_from TIMESTAMPTZ, valid_until TIMESTAMPTZ, operation TEXT, parent_id UUID,
390
+ importance NUMERIC, frequency INT, last_accessed TIMESTAMPTZ, decay_score NUMERIC,
391
+ contradiction_status TEXT, contradicts_id UUID, source_type TEXT, source_ref JSONB, confidence NUMERIC,
392
+ original_content TEXT, extraction_id UUID, extraction_tier TEXT, modality TEXT, tags TEXT[], metadata JSONB,
393
+ event_date TIMESTAMPTZ, document_date TIMESTAMPTZ, source_chunk TEXT, created_at TIMESTAMPTZ, rank_score FLOAT)
394
+ LANGUAGE plpgsql AS $$ BEGIN RETURN QUERY SELECT f.id, f.tenant_id, f.scope, f.scope_id, f.session_id,
395
+ f.content, f.embedding_model, f.embedding_dim, f.version, f.lineage_id, f.valid_from, f.valid_until,
396
+ f.operation, f.parent_id, f.importance, f.frequency, f.last_accessed, f.decay_score, f.contradiction_status,
397
+ f.contradicts_id, f.source_type, f.source_ref, f.confidence, f.original_content, f.extraction_id,
398
+ f.extraction_tier, f.modality, f.tags, f.metadata, f.event_date, f.document_date, f.source_chunk, f.created_at,
399
+ ts_rank(f.search_vector, plainto_tsquery('english', search_query)) AS rank_score
400
+ FROM facts f WHERE f.tenant_id = match_tenant_id AND f.scope = match_scope AND f.scope_id = match_scope_id
401
+ AND (CASE WHEN match_as_of IS NOT NULL THEN f.valid_from <= match_as_of AND (f.valid_until IS NULL OR f.valid_until > match_as_of) ELSE f.valid_until IS NULL END)
402
+ AND f.search_vector @@ plainto_tsquery('english', search_query)
403
+ ORDER BY rank_score DESC LIMIT match_count; END; $$;`,
404
+ // increment_trigger_fired RPC
405
+ `CREATE OR REPLACE FUNCTION increment_trigger_fired(p_tenant_id UUID, p_trigger_id UUID)
406
+ RETURNS VOID LANGUAGE plpgsql AS $$ BEGIN UPDATE triggers SET times_fired = times_fired + 1,
407
+ last_fired_at = NOW(), updated_at = NOW() WHERE tenant_id = p_tenant_id AND id = p_trigger_id; END; $$;`,
281
408
  // Default tenant
282
409
  `INSERT INTO tenants (id, name, slug, plan) VALUES ('00000000-0000-0000-0000-000000000001', 'Default', 'default', 'enterprise') ON CONFLICT DO NOTHING;`,
283
410
  ];
@@ -300,27 +427,39 @@ async function main() {
300
427
  console.log('\n Running database migrations...');
301
428
  const supabase = createClient(supabaseUrl, supabaseKey);
302
429
 
303
- let success = 0;
304
- let skipped = 0;
305
- for (let i = 0; i < MIGRATIONS.length; i++) {
306
- try {
307
- const { error } = await (supabase.rpc('exec_sql', { query: MIGRATIONS[i] }) as any).catch(() => ({ error: { message: 'rpc not available' } }));
308
- if (error) {
309
- // Try direct REST approach
310
- const res = await fetch(`${supabaseUrl}/rest/v1/rpc/exec_sql`, {
311
- method: 'POST',
312
- headers: { 'apikey': supabaseKey, 'Authorization': `Bearer ${supabaseKey}`, 'Content-Type': 'application/json' },
313
- body: JSON.stringify({ query: MIGRATIONS[i] }),
314
- });
315
- if (res.ok) { success++; } else { skipped++; }
316
- } else {
317
- success++;
318
- }
319
- } catch {
320
- skipped++;
430
+ // Try running migrations via Supabase Management API (requires service role key)
431
+ // If that fails, write SQL to a file for manual execution
432
+ const allSql = MIGRATIONS.join('\n\n');
433
+ const sqlPath = path.join(process.cwd(), 'steno-migrations.sql');
434
+
435
+ // Attempt automatic migration via Supabase SQL endpoint
436
+ let autoMigrated = false;
437
+ try {
438
+ const res = await fetch(`${supabaseUrl}/rest/v1/rpc/exec_sql`, {
439
+ method: 'POST',
440
+ headers: { 'apikey': supabaseKey, 'Authorization': `Bearer ${supabaseKey}`, 'Content-Type': 'application/json' },
441
+ body: JSON.stringify({ query: allSql }),
442
+ });
443
+ if (res.ok) {
444
+ autoMigrated = true;
445
+ console.log(' ✓ All migrations applied automatically');
321
446
  }
447
+ } catch { /* RPC not available */ }
448
+
449
+ if (!autoMigrated) {
450
+ // Write SQL file for manual execution
451
+ fs.writeFileSync(sqlPath, allSql);
452
+ console.log(`
453
+ ⚠️ Could not run migrations automatically.
454
+
455
+ Please run the SQL manually:
456
+ 1. Open your Supabase dashboard → SQL Editor
457
+ 2. Paste the contents of: ${sqlPath}
458
+ 3. Click "Run"
459
+ 4. Come back here and press Enter to continue
460
+ `);
461
+ await ask(' Press Enter after running the SQL... ');
322
462
  }
323
- console.log(` ✓ ${success} migrations applied, ${skipped} skipped (may already exist)`);
324
463
 
325
464
  // 3. Write Claude Desktop config
326
465
  const configDir = path.join(os.homedir(), 'Library', 'Application Support', 'Claude');