wayfind 2.0.34 → 2.0.36
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/bin/connectors/notion.js +146 -2
- package/bin/team-context.js +11 -9
- package/doctor.sh +46 -0
- package/package.json +1 -1
package/bin/connectors/notion.js
CHANGED
|
@@ -249,11 +249,23 @@ async function configure() {
|
|
|
249
249
|
.map((d) => d.trim())
|
|
250
250
|
.filter(Boolean);
|
|
251
251
|
|
|
252
|
+
// Optional: page IDs for full content extraction
|
|
253
|
+
console.log('');
|
|
254
|
+
console.log('Optional: specific page IDs to extract full content from (comma-separated).');
|
|
255
|
+
console.log('These pages will have their body text included in signals, not just metadata.');
|
|
256
|
+
console.log('Find page IDs in the URL: notion.so/<workspace>/<page-id>');
|
|
257
|
+
const pageInput = await ask('Page IDs: ');
|
|
258
|
+
const pages = pageInput
|
|
259
|
+
.split(',')
|
|
260
|
+
.map((p) => p.trim().replace(/-/g, ''))
|
|
261
|
+
.filter(Boolean);
|
|
262
|
+
|
|
252
263
|
const channelConfig = {
|
|
253
264
|
transport: 'https',
|
|
254
265
|
token,
|
|
255
266
|
token_env: 'NOTION_TOKEN',
|
|
256
267
|
databases: databases.length > 0 ? databases : null,
|
|
268
|
+
pages: pages.length > 0 ? pages : null,
|
|
257
269
|
last_pull: null,
|
|
258
270
|
};
|
|
259
271
|
|
|
@@ -261,8 +273,12 @@ async function configure() {
|
|
|
261
273
|
console.log('Notion connector configured.');
|
|
262
274
|
if (databases.length > 0) {
|
|
263
275
|
console.log(`Monitoring ${databases.length} database(s).`);
|
|
264
|
-
}
|
|
265
|
-
|
|
276
|
+
}
|
|
277
|
+
if (pages.length > 0) {
|
|
278
|
+
console.log(`Extracting content from ${pages.length} page(s).`);
|
|
279
|
+
}
|
|
280
|
+
if (databases.length === 0 && pages.length === 0) {
|
|
281
|
+
console.log('Monitoring all shared pages (metadata only).');
|
|
266
282
|
}
|
|
267
283
|
console.log('');
|
|
268
284
|
|
|
@@ -298,6 +314,37 @@ async function pull(config, since) {
|
|
|
298
314
|
dbEntries.push(...entries.map((e) => ({ ...e, _databaseId: dbId })));
|
|
299
315
|
}
|
|
300
316
|
|
|
317
|
+
// Fetch content for targeted pages
|
|
318
|
+
const targetedPageIds = config.pages || [];
|
|
319
|
+
const pageContents = {};
|
|
320
|
+
if (targetedPageIds.length > 0) {
|
|
321
|
+
for (const pageId of targetedPageIds) {
|
|
322
|
+
try {
|
|
323
|
+
const content = await fetchPageContent(token, pageId);
|
|
324
|
+
if (content && content.trim()) {
|
|
325
|
+
pageContents[pageId] = content;
|
|
326
|
+
}
|
|
327
|
+
} catch {
|
|
328
|
+
// Skip pages that fail — may have been deleted or unshared
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// Also fetch targeted pages that aren't in the recent pages list
|
|
332
|
+
const recentPageIds = new Set(pages.map((p) => p.id.replace(/-/g, '')));
|
|
333
|
+
for (const pageId of targetedPageIds) {
|
|
334
|
+
if (!recentPageIds.has(pageId.replace(/-/g, ''))) {
|
|
335
|
+
try {
|
|
336
|
+
const endpoint = `/pages/${pageId}`;
|
|
337
|
+
const page = await notionGet(token, endpoint);
|
|
338
|
+
if (page && page.id) {
|
|
339
|
+
pages.push(page);
|
|
340
|
+
}
|
|
341
|
+
} catch {
|
|
342
|
+
// Skip — page may not exist
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
301
348
|
// Fetch comment counts for active pages (top 20 by recency)
|
|
302
349
|
const activePages = pages.slice(0, 20);
|
|
303
350
|
const commentCounts = {};
|
|
@@ -314,6 +361,7 @@ async function pull(config, since) {
|
|
|
314
361
|
|
|
315
362
|
// Analyze
|
|
316
363
|
const analysis = analyzeActivity(pages, dbEntries, commentCounts, sinceDate, todayDate, userMap);
|
|
364
|
+
analysis.pageContents = pageContents;
|
|
317
365
|
|
|
318
366
|
// Generate markdown
|
|
319
367
|
const md = generateMarkdown(analysis, sinceDate, todayDate, timestamp, userMap);
|
|
@@ -496,6 +544,86 @@ async function fetchComments(token, pageId) {
|
|
|
496
544
|
}
|
|
497
545
|
}
|
|
498
546
|
|
|
547
|
+
// ── Page content extraction ────────────────────────────────────────────────
|
|
548
|
+
|
|
549
|
+
async function fetchPageContent(token, pageId, maxChars = 5000) {
|
|
550
|
+
const blocks = [];
|
|
551
|
+
let cursor = undefined;
|
|
552
|
+
const MAX_REQUESTS = 5;
|
|
553
|
+
let requests = 0;
|
|
554
|
+
|
|
555
|
+
while (requests < MAX_REQUESTS) {
|
|
556
|
+
requests++;
|
|
557
|
+
const endpoint = `/blocks/${pageId}/children?page_size=100` + (cursor ? `&start_cursor=${cursor}` : '');
|
|
558
|
+
let response;
|
|
559
|
+
try {
|
|
560
|
+
response = await notionGet(token, endpoint);
|
|
561
|
+
} catch {
|
|
562
|
+
break;
|
|
563
|
+
}
|
|
564
|
+
const results = Array.isArray(response.results) ? response.results : [];
|
|
565
|
+
blocks.push(...results);
|
|
566
|
+
if (!response.has_more) break;
|
|
567
|
+
cursor = response.next_cursor;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Convert blocks to markdown
|
|
571
|
+
const lines = [];
|
|
572
|
+
let totalChars = 0;
|
|
573
|
+
|
|
574
|
+
for (const block of blocks) {
|
|
575
|
+
if (totalChars >= maxChars) break;
|
|
576
|
+
const line = blockToMarkdown(block);
|
|
577
|
+
if (line !== null) {
|
|
578
|
+
lines.push(line);
|
|
579
|
+
totalChars += line.length;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
return lines.join('\n');
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function blockToMarkdown(block) {
|
|
587
|
+
const type = block.type;
|
|
588
|
+
if (!type) return null;
|
|
589
|
+
|
|
590
|
+
const richTextToPlain = (rt) =>
|
|
591
|
+
Array.isArray(rt) ? rt.map((t) => t.plain_text || '').join('') : '';
|
|
592
|
+
|
|
593
|
+
const data = block[type];
|
|
594
|
+
if (!data) return null;
|
|
595
|
+
|
|
596
|
+
switch (type) {
|
|
597
|
+
case 'paragraph':
|
|
598
|
+
return richTextToPlain(data.rich_text);
|
|
599
|
+
case 'heading_1':
|
|
600
|
+
return '# ' + richTextToPlain(data.rich_text);
|
|
601
|
+
case 'heading_2':
|
|
602
|
+
return '## ' + richTextToPlain(data.rich_text);
|
|
603
|
+
case 'heading_3':
|
|
604
|
+
return '### ' + richTextToPlain(data.rich_text);
|
|
605
|
+
case 'bulleted_list_item':
|
|
606
|
+
return '- ' + richTextToPlain(data.rich_text);
|
|
607
|
+
case 'numbered_list_item':
|
|
608
|
+
return '1. ' + richTextToPlain(data.rich_text);
|
|
609
|
+
case 'to_do':
|
|
610
|
+
return (data.checked ? '- [x] ' : '- [ ] ') + richTextToPlain(data.rich_text);
|
|
611
|
+
case 'toggle':
|
|
612
|
+
return '> ' + richTextToPlain(data.rich_text);
|
|
613
|
+
case 'callout':
|
|
614
|
+
return '> ' + richTextToPlain(data.rich_text);
|
|
615
|
+
case 'quote':
|
|
616
|
+
return '> ' + richTextToPlain(data.rich_text);
|
|
617
|
+
case 'code':
|
|
618
|
+
return '```\n' + richTextToPlain(data.rich_text) + '\n```';
|
|
619
|
+
case 'divider':
|
|
620
|
+
return '---';
|
|
621
|
+
default:
|
|
622
|
+
// Skip unsupported block types (image, embed, file, etc.)
|
|
623
|
+
return null;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
499
627
|
// ── Property extraction ─────────────────────────────────────────────────────
|
|
500
628
|
|
|
501
629
|
function extractTitle(page) {
|
|
@@ -700,6 +828,22 @@ function generateMarkdown(analysis, sinceDate, todayDate, timestamp, userMap) {
|
|
|
700
828
|
lines.push('');
|
|
701
829
|
}
|
|
702
830
|
|
|
831
|
+
// Targeted page content
|
|
832
|
+
const pageContents = analysis.pageContents || {};
|
|
833
|
+
if (Object.keys(pageContents).length > 0) {
|
|
834
|
+
lines.push('## Page Content');
|
|
835
|
+
lines.push('');
|
|
836
|
+
for (const [pageId, content] of Object.entries(pageContents)) {
|
|
837
|
+
// Find the page title from the pages list
|
|
838
|
+
const page = analysis.pages.find((p) => p.id.replace(/-/g, '') === pageId.replace(/-/g, ''));
|
|
839
|
+
const title = page ? extractTitle(page) : `Page ${pageId.slice(0, 8)}`;
|
|
840
|
+
lines.push(`### ${sanitizeForMarkdown(title)}`);
|
|
841
|
+
lines.push('');
|
|
842
|
+
lines.push(content);
|
|
843
|
+
lines.push('');
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
703
847
|
// Summary
|
|
704
848
|
lines.push('## Summary');
|
|
705
849
|
lines.push('');
|
package/bin/team-context.js
CHANGED
|
@@ -4322,17 +4322,17 @@ function ensureContainerConfig() {
|
|
|
4322
4322
|
changed = true;
|
|
4323
4323
|
}
|
|
4324
4324
|
|
|
4325
|
-
//
|
|
4326
|
-
//
|
|
4325
|
+
// Override container-specific paths in digest config — the mounted connectors.json
|
|
4326
|
+
// may have host paths that don't exist inside the container
|
|
4327
4327
|
if (config.digest) {
|
|
4328
|
-
const
|
|
4328
|
+
const containerPaths = {
|
|
4329
4329
|
store_path: process.env.TEAM_CONTEXT_STORE_PATH || contentStore.DEFAULT_STORE_PATH,
|
|
4330
4330
|
journal_dir: process.env.TEAM_CONTEXT_JOURNALS_DIR || '/data/journals',
|
|
4331
4331
|
signals_dir: process.env.TEAM_CONTEXT_SIGNALS_DIR || contentStore.DEFAULT_SIGNALS_DIR,
|
|
4332
4332
|
team_context_dir: process.env.TEAM_CONTEXT_TEAM_CONTEXT_DIR || '',
|
|
4333
4333
|
};
|
|
4334
|
-
for (const [key, val] of Object.entries(
|
|
4335
|
-
if (
|
|
4334
|
+
for (const [key, val] of Object.entries(containerPaths)) {
|
|
4335
|
+
if (config.digest[key] !== val) {
|
|
4336
4336
|
config.digest[key] = val;
|
|
4337
4337
|
changed = true;
|
|
4338
4338
|
}
|
|
@@ -4356,14 +4356,14 @@ function ensureContainerConfig() {
|
|
|
4356
4356
|
changed = true;
|
|
4357
4357
|
}
|
|
4358
4358
|
|
|
4359
|
-
//
|
|
4359
|
+
// Override container-specific paths in bot config (same reason as digest above)
|
|
4360
4360
|
if (config.slack_bot) {
|
|
4361
|
-
const
|
|
4361
|
+
const botPaths = {
|
|
4362
4362
|
store_path: process.env.TEAM_CONTEXT_STORE_PATH || contentStore.DEFAULT_STORE_PATH,
|
|
4363
4363
|
journal_dir: process.env.TEAM_CONTEXT_JOURNALS_DIR || '/data/journals',
|
|
4364
4364
|
};
|
|
4365
|
-
for (const [key, val] of Object.entries(
|
|
4366
|
-
if (
|
|
4365
|
+
for (const [key, val] of Object.entries(botPaths)) {
|
|
4366
|
+
if (config.slack_bot[key] !== val) {
|
|
4367
4367
|
config.slack_bot[key] = val;
|
|
4368
4368
|
changed = true;
|
|
4369
4369
|
}
|
|
@@ -4414,11 +4414,13 @@ function ensureContainerConfig() {
|
|
|
4414
4414
|
// Notion connector
|
|
4415
4415
|
if (!config.notion && process.env.NOTION_TOKEN) {
|
|
4416
4416
|
const databases = process.env.TEAM_CONTEXT_NOTION_DATABASES;
|
|
4417
|
+
const pages = process.env.TEAM_CONTEXT_NOTION_PAGES;
|
|
4417
4418
|
config.notion = {
|
|
4418
4419
|
transport: 'https',
|
|
4419
4420
|
token: process.env.NOTION_TOKEN,
|
|
4420
4421
|
token_env: 'NOTION_TOKEN',
|
|
4421
4422
|
databases: databases ? databases.split(',').map((d) => d.trim()) : null,
|
|
4423
|
+
pages: pages ? pages.split(',').map((p) => p.trim().replace(/-/g, '')) : null,
|
|
4422
4424
|
last_pull: null,
|
|
4423
4425
|
};
|
|
4424
4426
|
changed = true;
|
package/doctor.sh
CHANGED
|
@@ -466,6 +466,52 @@ check_storage_backend() {
|
|
|
466
466
|
else
|
|
467
467
|
[ "$VERBOSE" = true ] && info "No connectors.json — signal freshness check skipped"
|
|
468
468
|
fi
|
|
469
|
+
|
|
470
|
+
# ── 4. Embedding coverage ───────────────────────────────────────────────
|
|
471
|
+
local EMBED_RESULT
|
|
472
|
+
EMBED_RESULT=$(node -e "
|
|
473
|
+
try {
|
|
474
|
+
const storage = require('$SCRIPT_DIR/bin/storage/index.js');
|
|
475
|
+
const storePath = '$WAYFIND_DIR/content-store';
|
|
476
|
+
const backend = storage.getBackend(storePath);
|
|
477
|
+
const idx = backend.loadIndex();
|
|
478
|
+
if (!idx || !idx.entries) { console.log('SKIP'); process.exit(0); }
|
|
479
|
+
let total = 0, embedded = 0;
|
|
480
|
+
for (const e of Object.values(idx.entries)) { total++; if (e.hasEmbedding) embedded++; }
|
|
481
|
+
if (total === 0) { console.log('SKIP'); process.exit(0); }
|
|
482
|
+
const pct = Math.round(100 * embedded / total);
|
|
483
|
+
const hasKey = !!(process.env.OPENAI_API_KEY || process.env.AZURE_OPENAI_EMBEDDING_ENDPOINT);
|
|
484
|
+
console.log(pct + ':' + embedded + ':' + total + ':' + hasKey);
|
|
485
|
+
} catch (e) {
|
|
486
|
+
console.log('SKIP:' + e.message.split('\n')[0]);
|
|
487
|
+
}
|
|
488
|
+
" 2>/dev/null) || EMBED_RESULT="SKIP"
|
|
489
|
+
|
|
490
|
+
if [[ "$EMBED_RESULT" != SKIP* ]]; then
|
|
491
|
+
IFS=':' read -r PCT EMBEDDED TOTAL HAS_KEY <<< "$EMBED_RESULT"
|
|
492
|
+
if [ "$PCT" -ge 90 ]; then
|
|
493
|
+
ok "Embeddings: $EMBEDDED/$TOTAL entries ($PCT%)"
|
|
494
|
+
elif [ "$PCT" -ge 50 ]; then
|
|
495
|
+
warn "Embeddings: only $EMBEDDED/$TOTAL entries ($PCT%) — search quality degraded"
|
|
496
|
+
info "Run: wayfind reindex (with OPENAI_API_KEY or AZURE_OPENAI_EMBEDDING_* set)"
|
|
497
|
+
ISSUES=$((ISSUES + 1))
|
|
498
|
+
elif [ "$PCT" -gt 0 ]; then
|
|
499
|
+
err "Embeddings: $EMBEDDED/$TOTAL entries ($PCT%) — semantic search mostly broken"
|
|
500
|
+
info "Run: wayfind reindex (with OPENAI_API_KEY or AZURE_OPENAI_EMBEDDING_* set)"
|
|
501
|
+
ISSUES=$((ISSUES + 1))
|
|
502
|
+
else
|
|
503
|
+
if [ "$HAS_KEY" = "true" ]; then
|
|
504
|
+
err "Embeddings: 0/$TOTAL entries — embedding API key is set but no embeddings generated"
|
|
505
|
+
info "Run: wayfind reindex to generate embeddings"
|
|
506
|
+
ISSUES=$((ISSUES + 1))
|
|
507
|
+
else
|
|
508
|
+
warn "Embeddings: 0/$TOTAL entries — no embedding API key configured"
|
|
509
|
+
info "Set OPENAI_API_KEY or AZURE_OPENAI_EMBEDDING_* then run: wayfind reindex"
|
|
510
|
+
ISSUES=$((ISSUES + 1))
|
|
511
|
+
fi
|
|
512
|
+
fi
|
|
513
|
+
fi
|
|
514
|
+
|
|
469
515
|
set -e # Restore errexit
|
|
470
516
|
}
|
|
471
517
|
|
package/package.json
CHANGED