172 lines
5.0 KiB
JavaScript
172 lines
5.0 KiB
JavaScript
/**
|
||
* 🔄 铸渊系统快照 → 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 — 快照数据库 ID(Notion 侧)
|
||
* 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 };
|