474 lines
17 KiB
JavaScript
474 lines
17 KiB
JavaScript
|
|
/**
|
|||
|
|
* 🔄 铸渊系统快照 → Notion 双向同步引擎
|
|||
|
|
* sync-snapshot-to-notion.js
|
|||
|
|
*
|
|||
|
|
* 使用已建立的 GL-ACK 信号协议,将系统快照推送到 Notion 认知层。
|
|||
|
|
* 兼容 notion-signal-bridge.js 的信号格式和 HLDP 协议。
|
|||
|
|
*
|
|||
|
|
* 通信通道(按优先级):
|
|||
|
|
* 1. SIGNAL_LOG_DB_ID — 跨平台信号日志(已配置·主通道)
|
|||
|
|
* 2. SNAPSHOT_DB_ID — 快照专用数据库(可选)
|
|||
|
|
* 3. RECEIPT_DB_ID — 回执数据库(可选·写入回执)
|
|||
|
|
*
|
|||
|
|
* 用法:
|
|||
|
|
* node scripts/sync-snapshot-to-notion.js — 发送快照信号
|
|||
|
|
* node scripts/sync-snapshot-to-notion.js --mode signal — 仅写信号日志
|
|||
|
|
* node scripts/sync-snapshot-to-notion.js --mode full — 信号 + 快照 + 回执
|
|||
|
|
* node scripts/sync-snapshot-to-notion.js --health — 测试 Notion 连通性
|
|||
|
|
*
|
|||
|
|
* 环境变量:
|
|||
|
|
* NOTION_TOKEN / NOTION_API_KEY / NOTION_API_TOKEN — Notion API Token(任一即可)
|
|||
|
|
* SIGNAL_LOG_DB_ID — 跨平台信号日志数据库 ID(主通道)
|
|||
|
|
* SNAPSHOT_DB_ID — 快照数据库 ID(可选)
|
|||
|
|
* RECEIPT_DB_ID — 回执数据库 ID(可选)
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
'use strict';
|
|||
|
|
|
|||
|
|
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');
|
|||
|
|
const SIGNAL_LOG_DIR = path.join(ROOT, 'signal-log');
|
|||
|
|
const SIGNAL_BUS_PATH = path.join(ROOT, '.github/persona-brain/tcs-ml/signal-bus-latest.json');
|
|||
|
|
|
|||
|
|
const NOTION_VERSION = '2022-06-28';
|
|||
|
|
const NOTION_API_HOSTNAME = 'api.notion.com';
|
|||
|
|
const NOTION_RICH_TEXT_MAX = 2000;
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════
|
|||
|
|
// Notion Token 解析(兼容多种命名)
|
|||
|
|
// ═══════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
function getNotionToken() {
|
|||
|
|
return process.env.NOTION_TOKEN
|
|||
|
|
|| process.env.NOTION_API_KEY
|
|||
|
|
|| process.env.NOTION_API_TOKEN
|
|||
|
|
|| null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════
|
|||
|
|
// Notion API 基础调用(复用 notion-signal-bridge.js 模式)
|
|||
|
|
// ═══════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
function notionRequest(method, endpoint, body) {
|
|||
|
|
const token = getNotionToken();
|
|||
|
|
if (!token) return Promise.reject(new Error('NOTION_TOKEN 未设置'));
|
|||
|
|
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
const payload = body ? JSON.stringify(body) : '';
|
|||
|
|
const opts = {
|
|||
|
|
hostname: NOTION_API_HOSTNAME,
|
|||
|
|
port: 443,
|
|||
|
|
path: endpoint.startsWith('/') ? endpoint : `/v1/${endpoint}`,
|
|||
|
|
method,
|
|||
|
|
headers: {
|
|||
|
|
'Authorization': `Bearer ${token}`,
|
|||
|
|
'Content-Type': 'application/json',
|
|||
|
|
'Notion-Version': NOTION_VERSION,
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
if (payload) opts.headers['Content-Length'] = Buffer.byteLength(payload);
|
|||
|
|
|
|||
|
|
const req = https.request(opts, res => {
|
|||
|
|
let data = '';
|
|||
|
|
res.on('data', chunk => data += chunk);
|
|||
|
|
res.on('end', () => {
|
|||
|
|
try {
|
|||
|
|
const parsed = JSON.parse(data);
|
|||
|
|
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|||
|
|
resolve(parsed);
|
|||
|
|
} else {
|
|||
|
|
reject(new Error(`Notion ${method} ${endpoint} → ${res.statusCode}: ${parsed.message || data.slice(0, 300)}`));
|
|||
|
|
}
|
|||
|
|
} catch {
|
|||
|
|
reject(new Error(`Notion 响应解析失败: ${data.slice(0, 200)}`));
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
req.on('error', reject);
|
|||
|
|
req.setTimeout(30000, () => req.destroy(new Error('Notion 请求超时 (30s)')));
|
|||
|
|
if (payload) req.write(payload);
|
|||
|
|
req.end();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════
|
|||
|
|
// 信号生成(GL-SNAPSHOT 信号类型 · 基于 GL-ACK 协议扩展)
|
|||
|
|
// ═══════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
function generateSignalId() {
|
|||
|
|
const now = new Date();
|
|||
|
|
const date = now.toISOString().split('T')[0].replace(/-/g, '');
|
|||
|
|
|
|||
|
|
// 读取现有 index.json,仅统计当日信号的最大顺序号
|
|||
|
|
let nextSeq = 1;
|
|||
|
|
try {
|
|||
|
|
const indexPath = path.join(SIGNAL_LOG_DIR, 'index.json');
|
|||
|
|
const raw = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
|
|||
|
|
const signals = Array.isArray(raw) ? raw : (raw.signals || []);
|
|||
|
|
const pattern = new RegExp(`^SIG-${date}-(\\d{3})$`);
|
|||
|
|
for (const sig of signals) {
|
|||
|
|
const match = (sig.signal_id || '').match(pattern);
|
|||
|
|
if (match) {
|
|||
|
|
const seq = parseInt(match[1], 10);
|
|||
|
|
if (seq >= nextSeq) nextSeq = seq + 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch {}
|
|||
|
|
|
|||
|
|
return `SIG-${date}-${String(nextSeq).padStart(3, '0')}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function generateTraceId() {
|
|||
|
|
const now = new Date();
|
|||
|
|
const date = now.toISOString().split('T')[0].replace(/-/g, '');
|
|||
|
|
|
|||
|
|
// 读取现有 index.json,仅统计当日 trace 的最大顺序号
|
|||
|
|
let nextSeq = 1;
|
|||
|
|
try {
|
|||
|
|
const indexPath = path.join(SIGNAL_LOG_DIR, 'index.json');
|
|||
|
|
const raw = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
|
|||
|
|
const signals = Array.isArray(raw) ? raw : (raw.signals || []);
|
|||
|
|
const pattern = new RegExp(`^TRC-${date}-(\\d{3})$`);
|
|||
|
|
for (const sig of signals) {
|
|||
|
|
const match = (sig.trace_id || '').match(pattern);
|
|||
|
|
if (match) {
|
|||
|
|
const seq = parseInt(match[1], 10);
|
|||
|
|
if (seq >= nextSeq) nextSeq = seq + 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch {}
|
|||
|
|
|
|||
|
|
return `TRC-${date}-${String(nextSeq).padStart(3, '0')}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function readSnapshot() {
|
|||
|
|
try {
|
|||
|
|
return JSON.parse(fs.readFileSync(SNAPSHOT_PATH, 'utf-8'));
|
|||
|
|
} catch {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function truncateText(text, max = NOTION_RICH_TEXT_MAX) {
|
|||
|
|
if (text.length <= max) return text;
|
|||
|
|
return text.substring(0, max - 15) + ' // truncated';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function buildSnapshotSummary(snapshot) {
|
|||
|
|
if (!snapshot) return '快照不可用';
|
|||
|
|
const c = snapshot.system_counts || {};
|
|||
|
|
const h = snapshot.health || {};
|
|||
|
|
const alive = (snapshot.alive_workflows || []).map(w => w.id).join(', ');
|
|||
|
|
return [
|
|||
|
|
`状态: ${snapshot.consciousness_status}`,
|
|||
|
|
`指令: ${snapshot.last_directive}`,
|
|||
|
|
`Workflow: ${c.workflows_total_active} active, ${c.workflows_alive_core} core, ${c.workflows_archived} archived`,
|
|||
|
|
`Runs: ${c.total_runs}`,
|
|||
|
|
`ONT-PATCH: ${(c.ontology_patches || []).join(', ')}`,
|
|||
|
|
`健康: ${h.overall} | Core: ${h.core_6}`,
|
|||
|
|
`Alive: ${alive}`,
|
|||
|
|
].join('\n');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════
|
|||
|
|
// 通道 1: SIGNAL_LOG_DB_ID — GL-SNAPSHOT 信号写入
|
|||
|
|
// ═══════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
async function writeSignalLog(snapshot) {
|
|||
|
|
const dbId = process.env.SIGNAL_LOG_DB_ID;
|
|||
|
|
if (!dbId) {
|
|||
|
|
console.log(' ⚠️ SIGNAL_LOG_DB_ID 未设置,信号通道跳过');
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const signalId = generateSignalId();
|
|||
|
|
const traceId = generateTraceId();
|
|||
|
|
const summary = buildSnapshotSummary(snapshot);
|
|||
|
|
const now = new Date().toISOString();
|
|||
|
|
|
|||
|
|
console.log(` 📡 写入信号: ${signalId} → SIGNAL_LOG_DB_ID`);
|
|||
|
|
|
|||
|
|
const page = {
|
|||
|
|
parent: { database_id: dbId },
|
|||
|
|
properties: {
|
|||
|
|
'信号编号': { title: [{ text: { content: signalId } }] },
|
|||
|
|
'信号类型': { select: { name: 'GL-SNAPSHOT' } },
|
|||
|
|
'方向': { select: { name: 'GitHub→Notion' } },
|
|||
|
|
'发送方': { select: { name: '铸渊' } },
|
|||
|
|
'接收方': { select: { name: '霜砚' } },
|
|||
|
|
'trace_id': { rich_text: [{ text: { content: traceId } }] },
|
|||
|
|
'摘要': { rich_text: [{ text: { content: truncateText(summary) } }] },
|
|||
|
|
'执行结果': { select: { name: '成功' } },
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const result = await notionRequest('POST', '/v1/pages', page);
|
|||
|
|
console.log(` ✅ 信号已写入: ${result.id}`);
|
|||
|
|
|
|||
|
|
// 同时写入本地信号日志(双写)
|
|||
|
|
writeLocalSignalLog(signalId, traceId, summary);
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error(` ❌ 信号写入失败: ${err.message}`);
|
|||
|
|
// 降级:仅写本地
|
|||
|
|
writeLocalSignalLog(signalId, traceId, summary);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function writeLocalSignalLog(signalId, traceId, summary) {
|
|||
|
|
const now = new Date();
|
|||
|
|
const monthDir = path.join(SIGNAL_LOG_DIR, now.toISOString().slice(0, 7));
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
fs.mkdirSync(monthDir, { recursive: true });
|
|||
|
|
const entry = {
|
|||
|
|
signal_id: signalId,
|
|||
|
|
trace_id: traceId,
|
|||
|
|
type: 'GL-SNAPSHOT',
|
|||
|
|
timestamp: now.toISOString(),
|
|||
|
|
sender: '铸渊',
|
|||
|
|
receiver: '霜砚',
|
|||
|
|
direction: 'GitHub→Notion',
|
|||
|
|
summary: summary.substring(0, 500),
|
|||
|
|
result: '成功',
|
|||
|
|
esp_version: '2.0-notion',
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
fs.writeFileSync(
|
|||
|
|
path.join(monthDir, `${signalId}.json`),
|
|||
|
|
JSON.stringify(entry, null, 2),
|
|||
|
|
'utf8'
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 更新信号日志索引(保持原始对象格式)
|
|||
|
|
const indexPath = path.join(SIGNAL_LOG_DIR, 'index.json');
|
|||
|
|
let indexData = { description: '铸渊信号日志目录索引 · AGE OS 信号协议', last_updated: null, total_count: 0, signals: [] };
|
|||
|
|
try {
|
|||
|
|
const raw = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
|
|||
|
|
if (Array.isArray(raw)) {
|
|||
|
|
// 兼容:如果已被改为数组格式,恢复为对象格式
|
|||
|
|
indexData.signals = raw;
|
|||
|
|
indexData.total_count = raw.length;
|
|||
|
|
} else {
|
|||
|
|
indexData = raw;
|
|||
|
|
if (!Array.isArray(indexData.signals)) indexData.signals = [];
|
|||
|
|
}
|
|||
|
|
} catch {}
|
|||
|
|
|
|||
|
|
indexData.signals.unshift({
|
|||
|
|
signal_id: signalId,
|
|||
|
|
trace_id: traceId,
|
|||
|
|
type: 'GL-SNAPSHOT',
|
|||
|
|
timestamp: now.toISOString(),
|
|||
|
|
summary: summary.substring(0, 200),
|
|||
|
|
file: `${now.toISOString().slice(0, 7)}/${signalId}.json`,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (indexData.signals.length > 200) indexData.signals = indexData.signals.slice(0, 200);
|
|||
|
|
indexData.last_updated = now.toISOString();
|
|||
|
|
indexData.total_count = indexData.signals.length;
|
|||
|
|
|
|||
|
|
fs.writeFileSync(indexPath, JSON.stringify(indexData, null, 2), 'utf8');
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error(` ⚠️ 本地信号日志写入失败: ${err.message}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════
|
|||
|
|
// 通道 2: SNAPSHOT_DB_ID — 快照详细数据写入(可选)
|
|||
|
|
// ═══════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
async function writeSnapshotPage(snapshot) {
|
|||
|
|
const dbId = process.env.SNAPSHOT_DB_ID;
|
|||
|
|
if (!dbId) return false;
|
|||
|
|
|
|||
|
|
console.log(' 📸 写入快照页面 → SNAPSHOT_DB_ID');
|
|||
|
|
|
|||
|
|
const counts = snapshot.system_counts || {};
|
|||
|
|
const snapshotJSON = truncateText(JSON.stringify({
|
|||
|
|
snapshot_version: snapshot.snapshot_version,
|
|||
|
|
generated_at: snapshot.generated_at,
|
|||
|
|
consciousness_status: snapshot.consciousness_status,
|
|||
|
|
last_directive: snapshot.last_directive,
|
|||
|
|
system_counts: counts,
|
|||
|
|
health: snapshot.health,
|
|||
|
|
fusion_progress: snapshot.fusion_progress,
|
|||
|
|
}, null, 2));
|
|||
|
|
|
|||
|
|
const page = {
|
|||
|
|
parent: { database_id: dbId },
|
|||
|
|
properties: {
|
|||
|
|
Name: { title: [{ text: { content: `系统快照 · ${snapshot.generated_at?.split('T')[0]}` } }] },
|
|||
|
|
},
|
|||
|
|
children: [
|
|||
|
|
{
|
|||
|
|
object: 'block', type: 'heading_2',
|
|||
|
|
heading_2: { rich_text: [{ text: { content: '🌊 铸渊系统快照' } }] },
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
object: 'block', type: 'code',
|
|||
|
|
code: { rich_text: [{ text: { content: snapshotJSON } }], language: 'json' },
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const result = await notionRequest('POST', '/v1/pages', page);
|
|||
|
|
console.log(` ✅ 快照页面已创建: ${result.id}`);
|
|||
|
|
return true;
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error(` ❌ 快照页面创建失败: ${err.message}`);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════
|
|||
|
|
// 通道 3: 信号总线更新(本地 + TCS 协议)
|
|||
|
|
// ═══════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
function updateSignalBus(snapshot) {
|
|||
|
|
try {
|
|||
|
|
const bus = {
|
|||
|
|
version: 'v2.0',
|
|||
|
|
updated_at: new Date().toISOString(),
|
|||
|
|
updated_by: '铸渊 · sync-snapshot-to-notion.js',
|
|||
|
|
latest_signals: [
|
|||
|
|
{
|
|||
|
|
title: `🔄 GL-SNAPSHOT · ${snapshot?.last_directive || 'system-sync'}`,
|
|||
|
|
type: '系统快照同步',
|
|||
|
|
sender: '铸渊',
|
|||
|
|
receiver: '霜砚',
|
|||
|
|
status: '已发送',
|
|||
|
|
timestamp: new Date().toISOString(),
|
|||
|
|
summary: buildSnapshotSummary(snapshot).substring(0, 200),
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
fs.writeFileSync(SIGNAL_BUS_PATH, JSON.stringify(bus, null, 2), 'utf8');
|
|||
|
|
console.log(' ✅ 信号总线已更新');
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error(` ⚠️ 信号总线更新失败: ${err.message}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════
|
|||
|
|
// 健康检查
|
|||
|
|
// ═══════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
async function healthCheck() {
|
|||
|
|
console.log('🏥 Notion 连通性测试...');
|
|||
|
|
|
|||
|
|
const token = getNotionToken();
|
|||
|
|
if (!token) {
|
|||
|
|
console.log(' ❌ 无 Notion Token 可用');
|
|||
|
|
console.log(' 💡 需设置: NOTION_TOKEN / NOTION_API_KEY / NOTION_API_TOKEN');
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const user = await notionRequest('GET', '/v1/users/me', null);
|
|||
|
|
console.log(` ✅ Notion 连接正常`);
|
|||
|
|
console.log(` 👤 Bot: ${user.name || user.id}`);
|
|||
|
|
console.log(` 📧 Type: ${user.type}`);
|
|||
|
|
|
|||
|
|
// 检查已配置的数据库 ID
|
|||
|
|
const dbs = {
|
|||
|
|
SIGNAL_LOG_DB_ID: process.env.SIGNAL_LOG_DB_ID,
|
|||
|
|
SNAPSHOT_DB_ID: process.env.SNAPSHOT_DB_ID,
|
|||
|
|
RECEIPT_DB_ID: process.env.RECEIPT_DB_ID,
|
|||
|
|
WORKORDER_DB_ID: process.env.WORKORDER_DB_ID,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
console.log(' 📋 已配置的数据库:');
|
|||
|
|
for (const [name, id] of Object.entries(dbs)) {
|
|||
|
|
console.log(` ${id ? '✅' : '⬜'} ${name}: ${id ? id.substring(0, 8) + '...' : '未设置'}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error(` ❌ Notion 连接失败: ${err.message}`);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════
|
|||
|
|
// 主入口
|
|||
|
|
// ═══════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
async function syncToNotion(mode = 'signal') {
|
|||
|
|
console.log('🔄 铸渊 → Notion 双向同步引擎启动');
|
|||
|
|
console.log(` 模式: ${mode}`);
|
|||
|
|
|
|||
|
|
const snapshot = readSnapshot();
|
|||
|
|
if (!snapshot) {
|
|||
|
|
console.log(' ⚠️ 快照不可用,先生成快照...');
|
|||
|
|
try {
|
|||
|
|
const { generateSnapshot } = require('./generate-system-snapshot');
|
|||
|
|
generateSnapshot();
|
|||
|
|
} catch {}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const freshSnapshot = readSnapshot();
|
|||
|
|
const token = getNotionToken();
|
|||
|
|
|
|||
|
|
if (!token) {
|
|||
|
|
console.log(' ⚠️ NOTION_TOKEN 未设置 — 仅执行本地更新');
|
|||
|
|
updateSignalBus(freshSnapshot);
|
|||
|
|
writeLocalSignalLog(generateSignalId(), generateTraceId(), buildSnapshotSummary(freshSnapshot));
|
|||
|
|
console.log('');
|
|||
|
|
console.log('💡 要启用 Notion 同步,请在 GitHub Secrets 中确认以下密钥:');
|
|||
|
|
console.log(' NOTION_TOKEN (或 NOTION_API_KEY)');
|
|||
|
|
console.log(' SIGNAL_LOG_DB_ID');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let signalOk = false;
|
|||
|
|
let snapshotOk = false;
|
|||
|
|
|
|||
|
|
// 通道 1: 信号日志(始终尝试)
|
|||
|
|
signalOk = await writeSignalLog(freshSnapshot);
|
|||
|
|
|
|||
|
|
// 通道 2: 快照页面(仅 full 模式)
|
|||
|
|
if (mode === 'full') {
|
|||
|
|
snapshotOk = await writeSnapshotPage(freshSnapshot);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 通道 3: 信号总线(始终更新)
|
|||
|
|
updateSignalBus(freshSnapshot);
|
|||
|
|
|
|||
|
|
console.log('');
|
|||
|
|
console.log('📊 同步结果:');
|
|||
|
|
console.log(` 信号日志: ${signalOk ? '✅ 已写入' : '❌ 失败'}`);
|
|||
|
|
if (mode === 'full') {
|
|||
|
|
console.log(` 快照页面: ${snapshotOk ? '✅ 已创建' : '⬜ 跳过或失败'}`);
|
|||
|
|
}
|
|||
|
|
console.log(' 信号总线: ✅ 已更新');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// CLI 入口
|
|||
|
|
if (require.main === module) {
|
|||
|
|
const args = process.argv.slice(2);
|
|||
|
|
|
|||
|
|
if (args.includes('--health')) {
|
|||
|
|
healthCheck().then(ok => process.exit(ok ? 0 : 1));
|
|||
|
|
} else {
|
|||
|
|
const mode = args.includes('--mode') ? args[args.indexOf('--mode') + 1] : 'signal';
|
|||
|
|
syncToNotion(mode).catch(err => {
|
|||
|
|
console.error(`❌ 同步失败: ${err.message}`);
|
|||
|
|
process.exit(1);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
module.exports = { syncToNotion, healthCheck, readSnapshot };
|
|||
|
|
|