Skip to main content
Glama

Scratchpad MCP

by pc035860
merge-scratchpads.cjs8.39 kB
#!/usr/bin/env node /** * Scratchpads 專門合併工具 * * 針對已存在相同 workflow ID 的情況,安全地合併 scratchpads * * 使用方法: * node scripts/merge-scratchpads.cjs --source=scratchpad.db --target=scratchpad.v6.db */ const Database = require('better-sqlite3'); const path = require('path'); const fs = require('fs'); const crypto = require('crypto'); // 命令行參數解析 const args = process.argv.slice(2); const config = { source: null, target: null, dryRun: false }; args.forEach(arg => { if (arg.startsWith('--source=')) { config.source = arg.substring(9); } else if (arg.startsWith('--target=')) { config.target = arg.substring(9); } else if (arg === '--dry-run') { config.dryRun = true; } }); // 驗證參數 if (!config.source || !config.target) { console.error('❌ 請指定來源和目標資料庫:'); console.error(' --source=/path/to/source.db'); console.error(' --target=/path/to/target.db'); console.error(' [--dry-run] # 只檢查不執行'); process.exit(1); } if (!fs.existsSync(config.source)) { console.error(`❌ 來源資料庫不存在:${config.source}`); process.exit(1); } if (!fs.existsSync(config.target)) { console.error(`❌ 目標資料庫不存在:${config.target}`); process.exit(1); } console.log('🔄 Scratchpads 專門合併工具'); console.log(`📁 來源資料庫: ${config.source}`); console.log(`📁 目標資料庫: ${config.target}`); if (config.dryRun) { console.log('🔍 模式: 乾運行(只檢查不執行)'); } // 檢查資料庫使用狀況 function checkDatabaseInUse(dbPath) { try { const { execSync } = require('child_process'); const lsofResult = execSync( `lsof "${dbPath}" "${dbPath}-shm" "${dbPath}-wal" 2>/dev/null || true`, { encoding: 'utf8' } ); if (lsofResult.trim()) { console.error(`❌ 資料庫正在使用中:${dbPath}`); console.error('請先關閉所有 MCP 伺服器!'); return true; } return false; } catch (error) { console.warn(`⚠️ 無法檢查 ${dbPath} 使用狀況`); return false; } } // 分析 scratchpads 合併情況 function analyzeScratchpads(sourceDb, targetDb) { console.log('\n📊 分析 scratchpads 合併情況...'); // 1. 載入來源 scratchpads const sourceScratchpads = sourceDb.prepare(` SELECT s.*, w.name as workflow_name FROM scratchpads s JOIN workflows w ON s.workflow_id = w.id ORDER BY w.name, s.created_at `).all(); // 2. 載入目標 scratchpads const targetScratchpads = targetDb.prepare(` SELECT id, workflow_id, title FROM scratchpads `).all(); const targetScratchpadIds = new Set(targetScratchpads.map(s => s.id)); const targetWorkflowIds = new Set( targetDb.prepare('SELECT id FROM workflows').all().map(w => w.id) ); console.log(`📋 來源 scratchpads: ${sourceScratchpads.length} 筆`); console.log(`📋 目標 scratchpads: ${targetScratchpads.length} 筆`); // 3. 分析每個 scratchpad const analysis = { canMerge: [], needNewId: [], missingWorkflow: [], duplicate: [] }; sourceScratchpads.forEach(scratchpad => { const hasWorkflow = targetWorkflowIds.has(scratchpad.workflow_id); const hasId = targetScratchpadIds.has(scratchpad.id); if (!hasWorkflow) { analysis.missingWorkflow.push(scratchpad); } else if (hasId) { analysis.duplicate.push(scratchpad); } else { analysis.canMerge.push(scratchpad); } }); // 4. 顯示分析結果 console.log('\n🔍 分析結果:'); console.log(` ✅ 可直接合併: ${analysis.canMerge.length} 筆`); console.log(` ⚠️ 重複 ID: ${analysis.duplicate.length} 筆`); console.log(` ❌ 缺少 workflow: ${analysis.missingWorkflow.length} 筆`); if (analysis.missingWorkflow.length > 0) { console.log('\n❌ 缺少的 workflows:'); const missingWorkflows = new Set(analysis.missingWorkflow.map(s => s.workflow_name)); missingWorkflows.forEach(name => console.log(` - ${name}`)); } if (analysis.duplicate.length > 0) { console.log('\n⚠️ 重複的 scratchpads:'); analysis.duplicate.forEach(s => { console.log(` - ${s.title} (${s.workflow_name})`); }); } if (analysis.canMerge.length > 0) { console.log('\n✅ 可合併的 scratchpads:'); analysis.canMerge.forEach(s => { console.log(` - ${s.title} (${s.workflow_name})`); }); } return analysis; } // 執行 scratchpads 合併 function mergeScratchpads(sourceDb, targetDb, analysis) { if (config.dryRun) { console.log('\n🔍 乾運行模式,不執行實際合併'); return { merged: 0, skipped: analysis.duplicate.length + analysis.missingWorkflow.length }; } console.log('\n🚀 開始合併 scratchpads...'); const insertScratchpad = targetDb.prepare(` INSERT INTO scratchpads (id, workflow_id, title, content, created_at, updated_at, size_bytes) VALUES (?, ?, ?, ?, ?, ?, ?) `); let merged = 0; let errors = 0; targetDb.exec('BEGIN TRANSACTION'); try { analysis.canMerge.forEach(scratchpad => { try { insertScratchpad.run( scratchpad.id, scratchpad.workflow_id, scratchpad.title, scratchpad.content, scratchpad.created_at, scratchpad.updated_at, scratchpad.size_bytes ); merged++; console.log(` ✅ ${scratchpad.title} (${scratchpad.workflow_name})`); } catch (error) { errors++; console.warn(` ❌ ${scratchpad.title}: ${error.message}`); } }); if (errors === 0) { targetDb.exec('COMMIT'); console.log(`\n🎉 成功合併 ${merged} 個 scratchpads!`); } else { targetDb.exec('ROLLBACK'); console.error(`\n❌ 合併失敗,發生 ${errors} 個錯誤,已回滾變更`); return { merged: 0, skipped: analysis.canMerge.length }; } } catch (error) { targetDb.exec('ROLLBACK'); console.error(`\n❌ 合併過程發生錯誤:${error.message}`); return { merged: 0, skipped: analysis.canMerge.length }; } return { merged, skipped: analysis.duplicate.length + analysis.missingWorkflow.length, errors }; } // 重建 FTS5 索引 function rebuildFTS5Index(db) { console.log('\n🔍 重建 FTS5 索引...'); try { // 重建索引內容 db.exec(`INSERT INTO scratchpads_fts(scratchpads_fts) VALUES('rebuild')`); const ftsCount = db.prepare('SELECT COUNT(*) as count FROM scratchpads_fts').get(); console.log(`✅ FTS5 索引重建完成,記錄數:${ftsCount.count}`); } catch (error) { console.warn(`⚠️ FTS5 索引重建失敗:${error.message}`); console.warn('搜尋功能可能受影響,但基本功能正常'); } } // 主程序 async function main() { // 檢查資料庫使用狀況 if (checkDatabaseInUse(config.source) || checkDatabaseInUse(config.target)) { process.exit(1); } const sourceDb = new Database(config.source, { readonly: true }); const targetDb = new Database(config.target, { readonly: config.dryRun }); try { // 1. 分析合併情況 const analysis = analyzeScratchpads(sourceDb, targetDb); if (analysis.canMerge.length === 0) { console.log('\n✨ 沒有可合併的 scratchpads,工作完成!'); return; } // 2. 執行合併 const result = mergeScratchpads(sourceDb, targetDb, analysis); // 3. 重建索引(非乾運行模式) if (!config.dryRun && result.merged > 0) { rebuildFTS5Index(targetDb); } // 4. 最終結果 console.log('\n📊 最終結果:'); console.log(` ✅ 已合併: ${result.merged} 個 scratchpads`); console.log(` ⚠️ 已跳過: ${result.skipped} 個 scratchpads`); if (result.merged > 0) { const finalCount = targetDb.prepare('SELECT COUNT(*) as count FROM scratchpads').get(); console.log(` 📋 目標資料庫總計: ${finalCount.count} 個 scratchpads`); } } catch (error) { console.error('\n❌ 執行過程發生錯誤:'); console.error(error.message); process.exit(1); } finally { sourceDb.close(); targetDb.close(); } } main().catch(console.error);

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/pc035860/scratchpad-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server