import axios from 'axios'; import { NeuralEvent } from '../NeuralStream.js'; const NOTION_API_KEY = process.env.NOTION_API_TOKEN; // Use the ID from the user's link if env is missing, but prioritize env const NOTION_DB_ID = process.env.NOTION_BLACKBOARD_DB || '2cb12238-fd44-812e-84fc-e2eccb857517'; export class NotionService { private static instance: NotionService; private isEnabled: boolean = false; private constructor() { this.isEnabled = !!NOTION_API_KEY; if (this.isEnabled) { console.log('📝 [NOTION] Service Enabled'); } else { console.warn('⚠️ [NOTION] Service Disabled (Missing NOTION_API_TOKEN)'); } } public static getInstance(): NotionService { if (!NotionService.instance) { NotionService.instance = new NotionService(); } return NotionService.instance; } /** * Pushes a high-priority event to the Notion Command Center */ public async logEvent(event: NeuralEvent): Promise { if (!this.isEnabled) return; // Rate limiting / Spam protection // Only log HIGH or CRITICAL events, or specific important types const importantTypes = ['SECURITY_THREAT', 'KNOWLEDGE_GAP', 'SYSTEM_ERROR', 'DELEGATION']; if (event.severity !== 'CRITICAL' && event.severity !== 'HIGH' && !importantTypes.includes(event.type)) { return; } try { const title = `[${event.type}] ${event.source || 'System'}`; const payloadString = typeof event.payload === 'string' ? event.payload : JSON.stringify(event.payload, null, 2); await axios.post( 'https://api.notion.com/v1/pages', { parent: { database_id: NOTION_DB_ID }, properties: { // "Name" or "title" is the standard title property in Notion databases. // We try "Name" first as it's the default. Name: { title: [ { text: { content: title.substring(0, 100) // Title limit } } ] }, // We attempt to set tags/status if they exist, but wrap in try/catch logic implicitly by API ignoring unknown props (usually throws 400) // To be safe, we'll just send the title and content blocks for now to avoid schema validation errors // unless we are sure about the schema. // Strategy: Send minimal valid payload. }, children: [ { object: 'block', type: 'callout', callout: { rich_text: [{ type: 'text', text: { content: `${event.severity || 'INFO'} | ${new Date().toLocaleTimeString()}` } }], icon: { emoji: this.getEmojiForEvent(event) } } }, { object: 'block', type: 'code', code: { language: 'json', rich_text: [{ type: 'text', text: { content: payloadString.substring(0, 2000) } }] } } ] }, { headers: { 'Authorization': `Bearer ${NOTION_API_KEY}`, 'Notion-Version': '2022-06-28', 'Content-Type': 'application/json' } } ); // console.log(`📝 [NOTION] Logged: ${title}`); } catch (error: any) { if (error.response) { console.warn(`⚠️ [NOTION] API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`); } } } private getEmojiForEvent(event: NeuralEvent): string { if (event.type === 'SECURITY_THREAT') return '🚨'; if (event.type === 'SYSTEM_ERROR') return '🔥'; if (event.type === 'KNOWLEDGE_GAP') return '🧩'; if (event.type === 'THOUGHT') return '🧠'; if (event.severity === 'CRITICAL') return '⚡'; return '📝'; } } export const notionService = NotionService.getInstance();