From c156a14cf6ca95b56d22efe4ed7d2129a1470e8d Mon Sep 17 00:00:00 2001 From: Jay Date: Tue, 21 Apr 2026 12:25:41 +0900 Subject: [PATCH] feat: Add custom local brain path support with full 2-way sync --- package.json | 5 +++++ src/extension.ts | 40 ++++++++++++++++++++++++++-------------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 272c0eb..39fd64c 100644 --- a/package.json +++ b/package.json @@ -120,6 +120,11 @@ "type": "string", "default": "", "description": "🧠 Second Brain β€” 지식 μ €μž₯μ†Œ GitHub URL (예: https://github.com/user/my-knowledge). 여기에 μž…λ ₯ν•œ κΉƒν—ˆλΈŒμ˜ λ§ˆν¬λ‹€μš΄(.md) νŒŒμΌλ“€μ΄ AI의 지식 기반이 λ©λ‹ˆλ‹€." + }, + "connectAiLab.localBrainPath": { + "type": "string", + "default": "", + "description": "πŸ“ 둜컬 동기화 폴더 경둜 β€” (선택) μˆ¨κΉ€ 폴더 λŒ€μ‹ , λ‚΄ PC의 νŠΉμ • 폴더(예: /Users/jay/Desktop/MyBrain)λ₯Ό μ§€μ •ν•˜λ©΄ ν•΄λ‹Ή 폴더가 κΉƒν—ˆλΈŒμ™€ μ™„λ²½νžˆ μ–‘λ°©ν–₯ 동기화(Auto Pull & Push) λ©λ‹ˆλ‹€." } } } diff --git a/src/extension.ts b/src/extension.ts index e22cb54..a891e16 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -23,9 +23,21 @@ function getConfig() { maxTreeFiles: cfg.get('maxContextFiles', 200), timeout: cfg.get('requestTimeout', 300) * 1000, secondBrainRepo: cfg.get('secondBrainRepo', ''), + localBrainPath: cfg.get('localBrainPath', '') }; } +function _getBrainDir(): string { + const { localBrainPath } = getConfig(); + if (localBrainPath && localBrainPath.trim() !== '') { + if (localBrainPath.startsWith('~/')) { + return path.join(os.homedir(), localBrainPath.substring(2)); + } + return localBrainPath.trim(); + } + return path.join(os.homedir(), '.connect-ai-brain'); +} + const EXCLUDED_DIRS = new Set([ 'node_modules', '.git', '.vscode', 'out', 'dist', 'build', '.next', '.cache', '__pycache__', '.DS_Store', 'coverage', @@ -141,7 +153,7 @@ export function activate(context: vscode.ExtensionContext) { } // Step 2: λ‘λ‡Œ 폴더 μžλ™ 생성 - const brainDir = path.join(os.homedir(), '.connect-ai-brain'); + const brainDir = _getBrainDir(); if (!fs.existsSync(brainDir)) { fs.mkdirSync(brainDir, { recursive: true }); } @@ -177,7 +189,7 @@ export function activate(context: vscode.ExtensionContext) { } if (req.method === 'GET' && req.url === '/ping') { - const brainDir = path.join(os.homedir(), '.connect-ai-brain'); + const brainDir = _getBrainDir(); const brainCount = fs.existsSync(brainDir) ? provider._findBrainFiles(brainDir).length : 0; res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ status: 'ok', msg: 'Connect AI Bridge Ready', config: getConfig(), brain: { fileCount: brainCount, enabled: provider._brainEnabled } })); @@ -339,7 +351,7 @@ export function activate(context: vscode.ExtensionContext) { req.on('end', async () => { try { const parsed = JSON.parse(body); - const brainDir = path.join(os.homedir(), '.connect-ai-brain'); + const brainDir = _getBrainDir(); if (!fs.existsSync(brainDir)) { fs.mkdirSync(brainDir, { recursive: true }); } @@ -464,7 +476,7 @@ async function showBrainNetwork(context: vscode.ExtensionContext) { ); // Scan real Second Brain files locally instead of current workspace - const brainDir = path.join(os.homedir(), '.connect-ai-brain'); + const brainDir = _getBrainDir(); const realClusters: Record = {}; let filesFound = 0; @@ -479,7 +491,7 @@ async function showBrainNetwork(context: vscode.ExtensionContext) { walkDir(fullPath); } else if (entry.isFile() && fullPath.endsWith('.md')) { const folderName = path.basename(dir); - const groupName = folderName === '.connect-ai-brain' ? 'Brain Root' : folderName; + const groupName = folderName === path.basename(_getBrainDir()) ? 'Brain Root' : folderName; if (!realClusters[groupName]) realClusters[groupName] = []; realClusters[groupName].push(entry.name.replace('.md', '')); filesFound++; @@ -887,7 +899,7 @@ class SidebarChatProvider implements vscode.WebviewViewProvider { private async _handleInjectLocalBrain(files: any[]) { if (!this._view) return; - const brainDir = path.join(os.homedir(), '.connect-ai-brain'); + const brainDir = _getBrainDir(); if (!fs.existsSync(brainDir)) { vscode.window.showErrorMessage("Second Brain이 μ—°λ™λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. μ±„νŒ…μ°½ βš™λ²„νŠΌμ΄λ‚˜ ν—€λ”μ—μ„œ πŸ§ λ²„νŠΌμ„ λˆ„λ₯Έ ν›„ κΉƒν—ˆλΈŒ λ ˆν¬μ§€ν† λ¦¬λ₯Ό λ¨Όμ € μ—°λ™ν•΄μ£Όμ„Έμš”."); return; @@ -995,7 +1007,7 @@ class SidebarChatProvider implements vscode.WebviewViewProvider { private async _handleBrainMenu() { if (!this._view) { return; } - const brainDir = path.join(os.homedir(), '.connect-ai-brain'); + const brainDir = _getBrainDir(); const isSynced = fs.existsSync(brainDir); const { secondBrainRepo } = getConfig(); const statusLabel = this._brainEnabled ? '🟒 ON' : 'πŸ”΄ OFF'; @@ -1112,7 +1124,7 @@ class SidebarChatProvider implements vscode.WebviewViewProvider { } this._isSyncingBrain = true; - const brainDir = path.join(os.homedir(), '.connect-ai-brain'); + const brainDir = _getBrainDir(); try { this._view.webview.postMessage({ type: 'response', value: '🧠 **Second Brain 동기화 μ‹œμž‘ 쀑... κΉƒν—ˆλΈŒμ—μ„œ 지식을 λ³΅μ œν•©λ‹ˆλ‹€.**' }); @@ -1211,7 +1223,7 @@ class SidebarChatProvider implements vscode.WebviewViewProvider { // λͺ©μ°¨(인덱슀)만 생성 β€” λ‚΄μš©μ€ AIκ°€ 으둜 직접 μ—΄λžŒ private _getSecondBrainContext(): string { - const brainDir = path.join(os.homedir(), '.connect-ai-brain'); + const brainDir = _getBrainDir(); if (!fs.existsSync(brainDir)) return ''; const files = this._findBrainFiles(brainDir); @@ -1246,7 +1258,7 @@ class SidebarChatProvider implements vscode.WebviewViewProvider { // AIκ°€ νƒœκ·Έλ‘œ μš”μ²­ν•œ 파일의 μ‹€μ œ λ‚΄μš©μ„ μ½μ–΄μ„œ λ°˜ν™˜ private _readBrainFile(filename: string): string { - const brainDir = path.join(os.homedir(), '.connect-ai-brain'); + const brainDir = _getBrainDir(); if (!fs.existsSync(brainDir)) return '[ERROR] Second Brain이 λ™κΈ°ν™”λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. 🧠 λ²„νŠΌμ„ λ¨Όμ € λˆŒλŸ¬μ£Όμ„Έμš”.'; // μ •ν™•ν•œ 경둜 λ§€μΉ­ μ‹œλ„ @@ -1858,7 +1870,7 @@ class SidebarChatProvider implements vscode.WebviewViewProvider { fs.mkdirSync(dir, { recursive: true }); } fs.writeFileSync(absPath, content, 'utf-8'); - if (absPath.includes('.connect-ai-brain')) brainModified = true; + if (absPath.startsWith(_getBrainDir())) brainModified = true; report.push(`βœ… 생성: ${relPath}`); if (!firstCreatedFile) { firstCreatedFile = absPath; } } catch (err: any) { @@ -1902,7 +1914,7 @@ class SidebarChatProvider implements vscode.WebviewViewProvider { if (editCount > 0) { fs.writeFileSync(absPath, fileContent, 'utf-8'); - if (absPath.includes('.connect-ai-brain')) brainModified = true; + if (absPath.startsWith(_getBrainDir())) brainModified = true; report.push(`✏️ νŽΈμ§‘ μ™„λ£Œ: ${relPath} (${editCount}건 μˆ˜μ •)`); // Open edited file vscode.window.showTextDocument(vscode.Uri.file(absPath), { preview: false }); @@ -1925,7 +1937,7 @@ class SidebarChatProvider implements vscode.WebviewViewProvider { } else { fs.unlinkSync(absPath); } - if (absPath.includes('.connect-ai-brain')) brainModified = true; + if (absPath.startsWith(_getBrainDir())) brainModified = true; report.push(`πŸ—‘οΈ μ‚­μ œ: ${relPath}`); } else { report.push(`⚠️ μ‚­μ œ μŠ€ν‚΅: ${relPath} β€” 파일이 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.`); @@ -2061,7 +2073,7 @@ class SidebarChatProvider implements vscode.WebviewViewProvider { // Auto-Push Second Brain changes to Cloud if (brainModified) { try { - const brainDir = path.join(os.homedir(), '.connect-ai-brain'); + const brainDir = _getBrainDir(); const { execSync } = require('child_process'); execSync(`git add .`, { cwd: brainDir }); execSync(`git commit -m "[P-Reinforce] Auto-synced structured knowledge"`, { cwd: brainDir });