zhizhi/scripts/sync-snapshot-to-notion.js

172 lines
5.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 🔄 铸渊系统快照 → Notion 同步
* sync-snapshot-to-notion.js
*
* 将 signal-log/system-snapshot.json 推送到 Notion 数据库,
* 让 Notion 侧(认知层)始终有仓库最新结构和系统状况的认知。
*
* 用法: NOTION_TOKEN=xxx SNAPSHOT_DB_ID=xxx node scripts/sync-snapshot-to-notion.js
*
* 环境变量:
* NOTION_TOKEN — Notion API Token
* SNAPSHOT_DB_ID — 快照数据库 IDNotion 侧)
* SIGNAL_LOG_DB_ID — 信号日志数据库 ID备用
*/
const fs = require('fs');
const path = require('path');
const https = require('https');
const ROOT = path.resolve(__dirname, '..');
const SNAPSHOT_PATH = path.join(ROOT, 'signal-log/system-snapshot.json');
function readSnapshot() {
try {
return JSON.parse(fs.readFileSync(SNAPSHOT_PATH, 'utf-8'));
} catch (err) {
console.error('❌ 无法读取快照:', err.message);
process.exit(1);
}
}
function notionRequest(method, endpoint, body) {
const token = process.env.NOTION_TOKEN;
if (!token) {
console.error('❌ NOTION_TOKEN 未设置');
process.exit(1);
}
return new Promise((resolve, reject) => {
const data = body ? JSON.stringify(body) : null;
const options = {
hostname: 'api.notion.com',
port: 443,
path: `/v1/${endpoint}`,
method,
headers: {
'Authorization': `Bearer ${token}`,
'Notion-Version': '2022-06-28',
'Content-Type': 'application/json',
...(data ? { 'Content-Length': Buffer.byteLength(data) } : {})
}
};
const req = https.request(options, (res) => {
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => {
try {
resolve({ status: res.statusCode, data: JSON.parse(body) });
} catch {
resolve({ status: res.statusCode, data: body });
}
});
});
req.on('error', reject);
if (data) req.write(data);
req.end();
});
}
function buildSnapshotPage(snapshot, dbId) {
const counts = snapshot.system_counts || {};
const health = snapshot.health || {};
const aliveList = (snapshot.alive_workflows || [])
.map(w => `${w.id} (${w.runs} runs, ${w.status})`).join('\n');
return {
parent: { database_id: dbId },
properties: {
'Name': {
title: [{ text: { content: `系统快照 · ${snapshot.generated_at?.split('T')[0] || 'unknown'}` } }]
},
'Status': {
select: { name: snapshot.consciousness_status || 'unknown' }
},
'Directive': {
rich_text: [{ text: { content: snapshot.last_directive || '' } }]
},
'Workflows Active': {
number: counts.workflows_total_active || 0
},
'Core Alive': {
number: counts.workflows_alive_core || 0
},
'Archived': {
number: counts.workflows_archived || 0
},
'Total Runs': {
number: counts.total_runs || 0
},
'Health': {
select: { name: health.overall || 'unknown' }
}
},
children: [
{
object: 'block',
type: 'heading_2',
heading_2: { rich_text: [{ text: { content: '🌊 铸渊系统快照' } }] }
},
{
object: 'block',
type: 'code',
code: {
rich_text: [{ text: { content: JSON.stringify(snapshot, null, 2).substring(0, 2000) } }],
language: 'json'
}
},
{
object: 'block',
type: 'heading_3',
heading_3: { rich_text: [{ text: { content: '存活 Workflow' } }] }
},
{
object: 'block',
type: 'paragraph',
paragraph: { rich_text: [{ text: { content: aliveList || '(none)' } }] }
}
]
};
}
async function syncToNotion() {
const snapshot = readSnapshot();
const dbId = process.env.SNAPSHOT_DB_ID || process.env.SIGNAL_LOG_DB_ID;
if (!dbId) {
console.log('⚠️ SNAPSHOT_DB_ID 未设置,快照同步跳过');
console.log('📋 快照内容预览:');
console.log(` 状态: ${snapshot.consciousness_status}`);
console.log(` 指令: ${snapshot.last_directive}`);
console.log(` Workflow: ${snapshot.system_counts?.workflows_total_active} active`);
console.log(` 健康: ${snapshot.health?.overall}`);
console.log('');
console.log('💡 要启用 Notion 同步,请设置:');
console.log(' SNAPSHOT_DB_ID=<notion-database-id>');
return;
}
console.log('🔄 正在同步快照到 Notion...');
const page = buildSnapshotPage(snapshot, dbId);
try {
const result = await notionRequest('POST', 'pages', page);
if (result.status === 200) {
console.log('✅ 快照已同步到 Notion');
console.log(` Page ID: ${result.data?.id}`);
} else {
console.error('❌ Notion 同步失败:', result.status, JSON.stringify(result.data).substring(0, 500));
}
} catch (err) {
console.error('❌ Notion 请求失败:', err.message);
}
}
if (require.main === module) {
syncToNotion();
}
module.exports = { syncToNotion, readSnapshot };