docuking-mcp 2.0.3 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.js +107 -15
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1154,6 +1154,33 @@ docuking_init 호출 시 apiKey 파라미터를 포함해주세요.`,
1154
1154
  }
1155
1155
  }
1156
1156
 
1157
+ // .gitignore에 DocuKing 폴더 자동 추가
1158
+ const gitignorePath = path.join(localPath, '.gitignore');
1159
+ const docukingIgnore = `
1160
+ # DocuKing 문서 폴더 (문서는 DocuKing으로 관리, 서버에 암호화 저장)
1161
+ yy_All_Docu/
1162
+ zz_ai_Talk/
1163
+ zz_ai_Todo/
1164
+ zzz_ai_Plan/
1165
+ `;
1166
+
1167
+ try {
1168
+ if (fs.existsSync(gitignorePath)) {
1169
+ const currentContent = fs.readFileSync(gitignorePath, 'utf-8');
1170
+ // 이미 추가되어 있는지 확인
1171
+ if (!currentContent.includes('yy_All_Docu/')) {
1172
+ fs.appendFileSync(gitignorePath, docukingIgnore);
1173
+ console.log('[DocuKing] .gitignore에 DocuKing 폴더 추가됨');
1174
+ }
1175
+ } else {
1176
+ // .gitignore가 없으면 생성
1177
+ fs.writeFileSync(gitignorePath, docukingIgnore.trim() + '\n');
1178
+ console.log('[DocuKing] .gitignore 생성됨');
1179
+ }
1180
+ } catch (e) {
1181
+ console.error('[DocuKing] .gitignore 업데이트 실패:', e.message);
1182
+ }
1183
+
1157
1184
  let coworkerFolderName = null;
1158
1185
  let coworkerFolderPath = null;
1159
1186
 
@@ -1596,9 +1623,24 @@ docuking_init을 먼저 실행하세요.`,
1596
1623
  // 4. 서버에만 있고 로컬에 없는 파일 삭제 (Git rm과 동일)
1597
1624
  let deleted = 0;
1598
1625
  const deletedFiles = [];
1599
- if (serverAllPaths.length > 0 && !isCoworker) {
1600
- // 코워커가 아닌 경우에만 삭제 수행 (오너 전용)
1601
- const pathsToDelete = serverAllPaths.filter(p => !processedLocalPaths.has(p));
1626
+ if (serverAllPaths.length > 0) {
1627
+ let pathsToDelete;
1628
+
1629
+ if (isCoworker) {
1630
+ // 코워커: 자기 폴더(yy_Coworker_{이름}/) 안의 파일만 삭제 가능
1631
+ const coworkerFolderPrefix = `yy_Coworker_${coworkerName}/`;
1632
+ pathsToDelete = serverAllPaths
1633
+ .filter(p => p.startsWith(coworkerFolderPrefix))
1634
+ .filter(p => !processedLocalPaths.has(p));
1635
+ if (pathsToDelete.length > 0) {
1636
+ console.log(`[DocuKing] 코워커 삭제 대상: ${coworkerFolderPrefix}* (${pathsToDelete.length}개)`);
1637
+ }
1638
+ } else {
1639
+ // 오너: 코워커 폴더 제외하고 삭제 (코워커 폴더는 코워커만 관리)
1640
+ pathsToDelete = serverAllPaths
1641
+ .filter(p => !p.startsWith('yy_Coworker_'))
1642
+ .filter(p => !processedLocalPaths.has(p));
1643
+ }
1602
1644
 
1603
1645
  if (pathsToDelete.length > 0) {
1604
1646
  console.log(`[DocuKing] 서버에만 있는 파일 ${pathsToDelete.length}개 삭제 중...`);
@@ -1727,6 +1769,11 @@ async function handlePull(args) {
1727
1769
  };
1728
1770
  }
1729
1771
 
1772
+ // 코워커 판별 (Push와 동일한 로직)
1773
+ const coworkerMatch = apiKey.match(/^sk_[a-f0-9]+_cw_([^_]*)_/);
1774
+ const isCoworker = !!coworkerMatch;
1775
+ const coworkerName = coworkerMatch ? (coworkerMatch[1] || 'Coworker') : null;
1776
+
1730
1777
  // 프로젝트 정보 조회 (로컬 config에서)
1731
1778
  const projectInfo = getProjectInfo(localPath);
1732
1779
  if (projectInfo.error) {
@@ -1796,6 +1843,9 @@ async function handlePull(args) {
1796
1843
 
1797
1844
  // 파일 다운로드
1798
1845
  const results = [];
1846
+ const skippedFiles = []; // 로컬 수정으로 스킵된 파일 (내 영역)
1847
+ const updatedFiles = []; // 서버에서 업데이트된 파일 (남의 영역)
1848
+
1799
1849
  for (const file of files) {
1800
1850
  try {
1801
1851
  const response = await fetch(
@@ -1815,31 +1865,67 @@ async function handlePull(args) {
1815
1865
  const data = await response.json();
1816
1866
 
1817
1867
  // 서버 경로를 그대로 yy_All_Docu/ 아래에 저장
1818
- // 협업자 폴더(yy_이름/)도 yy_All_Docu/ 안에 있으므로 동일하게 처리
1819
1868
  const fullPath = path.join(mainFolderPath, file.path);
1820
1869
 
1821
- // 디렉토리 생성
1822
- fs.mkdirSync(path.dirname(fullPath), { recursive: true });
1870
+ // 영역인지 판별 (Push 권한과 동일한 기준)
1871
+ const isMyArea = isCoworker
1872
+ ? file.path.startsWith(`yy_Coworker_${coworkerName}/`)
1873
+ : !file.path.startsWith('yy_Coworker_');
1823
1874
 
1824
- // 인코딩에 따라 저장
1875
+ // 인코딩에 따라 서버 content 준비
1825
1876
  const content = data.file?.content || data.content || '';
1826
1877
  const encoding = data.file?.encoding || data.encoding || 'utf-8';
1827
-
1828
- if (encoding === 'base64') {
1829
- // Base64 디코딩 후 바이너리로 저장
1830
- const buffer = Buffer.from(content, 'base64');
1831
- fs.writeFileSync(fullPath, buffer);
1832
- } else {
1833
- // UTF-8 텍스트로 저장
1834
- fs.writeFileSync(fullPath, content, 'utf-8');
1878
+ const serverBuffer = encoding === 'base64'
1879
+ ? Buffer.from(content, 'base64')
1880
+ : Buffer.from(content, 'utf-8');
1881
+
1882
+ // 로컬 파일이 있으면 해시 비교
1883
+ if (fs.existsSync(fullPath)) {
1884
+ const localContent = fs.readFileSync(fullPath);
1885
+ const localHash = crypto.createHash('sha256').update(localContent).digest('hex');
1886
+ const serverHash = crypto.createHash('sha256').update(serverBuffer).digest('hex');
1887
+
1888
+ if (localHash !== serverHash) {
1889
+ if (isMyArea) {
1890
+ // 내 영역: 로컬이 수정됨 → 스킵 (로컬 보호)
1891
+ console.log(`[DocuKing] ⚠ 스킵: ${file.path} (내 영역, 로컬 수정됨)`);
1892
+ skippedFiles.push(file.path);
1893
+ continue;
1894
+ } else {
1895
+ // 남의 영역: 서버가 최신 → 덮어쓰기
1896
+ console.log(`[DocuKing] ↓ 업데이트: ${file.path} (남의 영역, 서버 최신)`);
1897
+ updatedFiles.push(file.path);
1898
+ // 아래로 진행하여 저장
1899
+ }
1900
+ } else {
1901
+ // 동일하면 스킵
1902
+ results.push(`⊘ ${file.path} (동일)`);
1903
+ continue;
1904
+ }
1835
1905
  }
1836
1906
 
1907
+ // 디렉토리 생성
1908
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
1909
+
1910
+ // 파일 저장
1911
+ fs.writeFileSync(fullPath, serverBuffer);
1912
+
1837
1913
  results.push(`✓ ${file.path}`);
1838
1914
  } catch (e) {
1839
1915
  results.push(`✗ ${file.path}: ${e.message}`);
1840
1916
  }
1841
1917
  }
1842
1918
 
1919
+ // 결과 요약
1920
+ if (skippedFiles.length > 0) {
1921
+ results.push(`\n⚠ 내 영역 로컬 수정 보호 (${skippedFiles.length}개):`);
1922
+ skippedFiles.forEach(f => results.push(` - ${f}`));
1923
+ }
1924
+ if (updatedFiles.length > 0) {
1925
+ results.push(`\n↓ 남의 영역 서버 최신 반영 (${updatedFiles.length}개):`);
1926
+ updatedFiles.forEach(f => results.push(` - ${f}`));
1927
+ }
1928
+
1843
1929
  return {
1844
1930
  content: [
1845
1931
  {
@@ -2020,6 +2106,12 @@ function collectFiles(basePath, relativePath, results, excludedFiles = null, lar
2020
2106
  console.error(`[DocuKing] 제외됨: ${entryRelPath}/ (zz_* 폴더 - 로컬 전용)`);
2021
2107
  continue;
2022
2108
  }
2109
+ // yy_Coworker_* 폴더는 코워커 전용 - 오너는 수집하지 않음
2110
+ // (코워커 Push는 handlePush에서 자기 폴더만 수집하므로 여기서는 무조건 제외)
2111
+ if (entry.name.startsWith('yy_Coworker_')) {
2112
+ console.error(`[DocuKing] 제외됨: ${entryRelPath}/ (코워커 전용 폴더)`);
2113
+ continue;
2114
+ }
2023
2115
  collectFiles(basePath, entryRelPath, results, excludedFiles, largeFiles);
2024
2116
  } else if (entry.isFile()) {
2025
2117
  const fileType = getFileType(entry.name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docuking-mcp",
3
- "version": "2.0.3",
3
+ "version": "2.0.4",
4
4
  "description": "DocuKing MCP Server - AI 시대의 문서 협업 플랫폼",
5
5
  "type": "module",
6
6
  "main": "index.js",