Skip to main content
Glama
MUSE-CODE-SPACE

Vibe Coding Documentation MCP (MUSE)

profileStorage.js7.53 kB
/** * 프로젝트 프로파일 스토리지 모듈 * 프로젝트별 설정을 저장하고 관리 * v2.7: Project Profile */ import { promises as fs } from 'fs'; import * as path from 'path'; import { logger } from './logger.js'; const profileLogger = logger.child({ module: 'profileStorage' }); // 기본 저장 경로 const DEFAULT_STORAGE_DIR = process.env.VIBE_CODING_STORAGE_DIR || path.join(process.env.HOME || process.env.USERPROFILE || '.', '.vibe-coding-mcp', 'profiles'); let storageDir = DEFAULT_STORAGE_DIR; let activeProfileId = null; /** * 스토리지 초기화 */ export async function initializeProfileStorage(customDir) { if (customDir) { storageDir = customDir; } try { await fs.mkdir(storageDir, { recursive: true }); // 활성 프로파일 ID 로드 const activeFile = path.join(storageDir, '.active'); try { activeProfileId = (await fs.readFile(activeFile, 'utf-8')).trim(); } catch { activeProfileId = null; } profileLogger.info('Profile storage initialized', { path: storageDir }); } catch (error) { profileLogger.error('Failed to initialize profile storage', error); throw error; } } /** * 프로파일 ID 생성 */ function generateProfileId() { const timestamp = Date.now().toString(36); const random = Math.random().toString(36).substring(2, 8); return `profile_${timestamp}_${random}`; } /** * 프로파일 파일 경로 */ function getProfilePath(profileId) { return path.join(storageDir, `${profileId}.json`); } /** * 프로파일 생성 */ export async function createProfile(data) { await initializeProfileStorage(); const profileId = generateProfileId(); const now = new Date().toISOString(); const profile = { id: profileId, createdAt: now, updatedAt: now, ...data }; const filePath = getProfilePath(profileId); try { await fs.writeFile(filePath, JSON.stringify(profile, null, 2), 'utf-8'); profileLogger.info('Profile created', { profileId, name: profile.name }); return profile; } catch (error) { profileLogger.error('Failed to create profile', error); throw error; } } /** * 프로파일 조회 */ export async function getProfile(profileId) { const filePath = getProfilePath(profileId); try { const content = await fs.readFile(filePath, 'utf-8'); return JSON.parse(content); } catch (error) { if (error.code === 'ENOENT') { return null; } profileLogger.error('Failed to read profile', error); throw error; } } /** * 프로파일 업데이트 */ export async function updateProfile(profileId, updates) { const existing = await getProfile(profileId); if (!existing) { throw new Error(`Profile not found: ${profileId}`); } const updated = { ...existing, ...updates, updatedAt: new Date().toISOString() }; const filePath = getProfilePath(profileId); try { await fs.writeFile(filePath, JSON.stringify(updated, null, 2), 'utf-8'); profileLogger.info('Profile updated', { profileId }); return updated; } catch (error) { profileLogger.error('Failed to update profile', error); throw error; } } /** * 프로파일 삭제 */ export async function deleteProfile(profileId) { const filePath = getProfilePath(profileId); try { await fs.unlink(filePath); // 활성 프로파일이면 해제 if (activeProfileId === profileId) { await setActiveProfile(null); } profileLogger.info('Profile deleted', { profileId }); return true; } catch (error) { if (error.code === 'ENOENT') { return false; } profileLogger.error('Failed to delete profile', error); throw error; } } /** * 모든 프로파일 목록 */ export async function listProfiles(options) { await initializeProfileStorage(); const { limit = 50, offset = 0, sortBy = 'updatedAt', sortOrder = 'desc' } = options || {}; try { const files = await fs.readdir(storageDir); const profileFiles = files.filter(f => f.endsWith('.json') && !f.startsWith('.')); const profiles = []; for (const file of profileFiles) { try { const content = await fs.readFile(path.join(storageDir, file), 'utf-8'); const data = JSON.parse(content); profiles.push({ id: data.id, name: data.name, description: data.description, createdAt: data.createdAt, updatedAt: data.updatedAt, isActive: data.id === activeProfileId }); } catch { continue; } } // 정렬 profiles.sort((a, b) => { const aVal = a[sortBy] || ''; const bVal = b[sortBy] || ''; const cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0; return sortOrder === 'desc' ? -cmp : cmp; }); // 페이지네이션 const total = profiles.length; const paginated = profiles.slice(offset, offset + limit); return { profiles: paginated, total }; } catch (error) { profileLogger.error('Failed to list profiles', error); throw error; } } /** * 활성 프로파일 설정 */ export async function setActiveProfile(profileId) { await initializeProfileStorage(); const activeFile = path.join(storageDir, '.active'); try { if (profileId) { // 프로파일 존재 확인 const profile = await getProfile(profileId); if (!profile) { throw new Error(`Profile not found: ${profileId}`); } await fs.writeFile(activeFile, profileId, 'utf-8'); activeProfileId = profileId; profileLogger.info('Active profile set', { profileId }); } else { try { await fs.unlink(activeFile); } catch { /* ignore */ } activeProfileId = null; profileLogger.info('Active profile cleared'); } } catch (error) { profileLogger.error('Failed to set active profile', error); throw error; } } /** * 활성 프로파일 조회 */ export async function getActiveProfile() { await initializeProfileStorage(); if (!activeProfileId) { return null; } return getProfile(activeProfileId); } /** * 이름으로 프로파일 검색 */ export async function findProfileByName(name) { const { profiles } = await listProfiles({ limit: 1000 }); const found = profiles.find(p => p.name.toLowerCase() === name.toLowerCase()); if (found) { return getProfile(found.id); } return null; } /** * 프로파일 복제 */ export async function cloneProfile(profileId, newName) { const source = await getProfile(profileId); if (!source) { throw new Error(`Profile not found: ${profileId}`); } const { id, createdAt, updatedAt, ...profileData } = source; return createProfile({ ...profileData, name: newName, description: `Cloned from ${source.name}` }); } //# sourceMappingURL=profileStorage.js.map

Latest Blog Posts

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/MUSE-CODE-SPACE/vibe-coding-mcp'

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