docuking-mcp 2.0.2 → 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 +119 -21
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1114,9 +1114,11 @@ docuking_init 호출 시 apiKey 파라미터를 포함해주세요.`,
1114
1114
  }
1115
1115
 
1116
1116
  // Co-worker 권한은 API Key 형식에서 판단 (sk_xxx_cw_이름_)
1117
- const coworkerMatch = apiKey.match(/^sk_[a-f0-9]+_cw_([^_]+)_/);
1117
+ // 이름이 비어있는 경우도 처리 (sk_xxx_cw__)
1118
+ const coworkerMatch = apiKey.match(/^sk_[a-f0-9]+_cw_([^_]*)_/);
1118
1119
  const isCoworker = !!coworkerMatch;
1119
- const coworkerName = coworkerMatch ? coworkerMatch[1] : null;
1120
+ // 코워커 이름이 비어있으면 'Coworker'로 기본값 설정
1121
+ const coworkerName = coworkerMatch ? (coworkerMatch[1] || 'Coworker') : null;
1120
1122
 
1121
1123
  // .docuking/config.json에 설정 저장
1122
1124
  saveLocalConfig(localPath, {
@@ -1152,6 +1154,33 @@ docuking_init 호출 시 apiKey 파라미터를 포함해주세요.`,
1152
1154
  }
1153
1155
  }
1154
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
+
1155
1184
  let coworkerFolderName = null;
1156
1185
  let coworkerFolderPath = null;
1157
1186
 
@@ -1268,9 +1297,11 @@ Git처럼 무엇을 변경했는지 명확히 작성해주세요.
1268
1297
  const projectName = projectInfo.projectName;
1269
1298
 
1270
1299
  // Co-worker 권한은 API Key 형식에서 판단 (sk_xxx_cw_이름_)
1271
- const coworkerMatch = apiKey.match(/^sk_[a-f0-9]+_cw_([^_]+)_/);
1300
+ // 이름이 비어있는 경우도 처리 (sk_xxx_cw__)
1301
+ const coworkerMatch = apiKey.match(/^sk_[a-f0-9]+_cw_([^_]*)_/);
1272
1302
  const isCoworker = !!coworkerMatch;
1273
- const coworkerName = coworkerMatch ? coworkerMatch[1] : null;
1303
+ // 코워커 이름이 비어있으면 'Coworker'로 기본값 설정
1304
+ const coworkerName = coworkerMatch ? (coworkerMatch[1] || 'Coworker') : null;
1274
1305
  const coworkerFolderName = isCoworker ? `yy_Coworker_${coworkerName}` : null;
1275
1306
 
1276
1307
  // 작업 폴더 결정: 모두 yy_All_Docu/ 사용, 협업자는 yy_All_Docu/yy_Coworker_{이름}/ 에서 작업
@@ -1592,9 +1623,24 @@ docuking_init을 먼저 실행하세요.`,
1592
1623
  // 4. 서버에만 있고 로컬에 없는 파일 삭제 (Git rm과 동일)
1593
1624
  let deleted = 0;
1594
1625
  const deletedFiles = [];
1595
- if (serverAllPaths.length > 0 && !isCoworker) {
1596
- // 코워커가 아닌 경우에만 삭제 수행 (오너 전용)
1597
- 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
+ }
1598
1644
 
1599
1645
  if (pathsToDelete.length > 0) {
1600
1646
  console.log(`[DocuKing] 서버에만 있는 파일 ${pathsToDelete.length}개 삭제 중...`);
@@ -1723,6 +1769,11 @@ async function handlePull(args) {
1723
1769
  };
1724
1770
  }
1725
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
+
1726
1777
  // 프로젝트 정보 조회 (로컬 config에서)
1727
1778
  const projectInfo = getProjectInfo(localPath);
1728
1779
  if (projectInfo.error) {
@@ -1792,6 +1843,9 @@ async function handlePull(args) {
1792
1843
 
1793
1844
  // 파일 다운로드
1794
1845
  const results = [];
1846
+ const skippedFiles = []; // 로컬 수정으로 스킵된 파일 (내 영역)
1847
+ const updatedFiles = []; // 서버에서 업데이트된 파일 (남의 영역)
1848
+
1795
1849
  for (const file of files) {
1796
1850
  try {
1797
1851
  const response = await fetch(
@@ -1811,31 +1865,67 @@ async function handlePull(args) {
1811
1865
  const data = await response.json();
1812
1866
 
1813
1867
  // 서버 경로를 그대로 yy_All_Docu/ 아래에 저장
1814
- // 협업자 폴더(yy_이름/)도 yy_All_Docu/ 안에 있으므로 동일하게 처리
1815
1868
  const fullPath = path.join(mainFolderPath, file.path);
1816
1869
 
1817
- // 디렉토리 생성
1818
- 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_');
1819
1874
 
1820
- // 인코딩에 따라 저장
1875
+ // 인코딩에 따라 서버 content 준비
1821
1876
  const content = data.file?.content || data.content || '';
1822
1877
  const encoding = data.file?.encoding || data.encoding || 'utf-8';
1823
-
1824
- if (encoding === 'base64') {
1825
- // Base64 디코딩 후 바이너리로 저장
1826
- const buffer = Buffer.from(content, 'base64');
1827
- fs.writeFileSync(fullPath, buffer);
1828
- } else {
1829
- // UTF-8 텍스트로 저장
1830
- 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
+ }
1831
1905
  }
1832
1906
 
1907
+ // 디렉토리 생성
1908
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
1909
+
1910
+ // 파일 저장
1911
+ fs.writeFileSync(fullPath, serverBuffer);
1912
+
1833
1913
  results.push(`✓ ${file.path}`);
1834
1914
  } catch (e) {
1835
1915
  results.push(`✗ ${file.path}: ${e.message}`);
1836
1916
  }
1837
1917
  }
1838
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
+
1839
1929
  return {
1840
1930
  content: [
1841
1931
  {
@@ -2016,6 +2106,12 @@ function collectFiles(basePath, relativePath, results, excludedFiles = null, lar
2016
2106
  console.error(`[DocuKing] 제외됨: ${entryRelPath}/ (zz_* 폴더 - 로컬 전용)`);
2017
2107
  continue;
2018
2108
  }
2109
+ // yy_Coworker_* 폴더는 코워커 전용 - 오너는 수집하지 않음
2110
+ // (코워커 Push는 handlePush에서 자기 폴더만 수집하므로 여기서는 무조건 제외)
2111
+ if (entry.name.startsWith('yy_Coworker_')) {
2112
+ console.error(`[DocuKing] 제외됨: ${entryRelPath}/ (코워커 전용 폴더)`);
2113
+ continue;
2114
+ }
2019
2115
  collectFiles(basePath, entryRelPath, results, excludedFiles, largeFiles);
2020
2116
  } else if (entry.isFile()) {
2021
2117
  const fileType = getFileType(entry.name);
@@ -2105,9 +2201,11 @@ async function handleStatus(args) {
2105
2201
  const projectName = projectInfo.projectName;
2106
2202
 
2107
2203
  // Co-worker 권한은 API Key 형식에서 판단 (sk_xxx_cw_이름_)
2108
- const coworkerMatch = apiKey.match(/^sk_[a-f0-9]+_cw_([^_]+)_/);
2204
+ // 이름이 비어있는 경우도 처리 (sk_xxx_cw__)
2205
+ const coworkerMatch = apiKey.match(/^sk_[a-f0-9]+_cw_([^_]*)_/);
2109
2206
  const isCoworker = !!coworkerMatch;
2110
- const coworkerName = coworkerMatch ? coworkerMatch[1] : null;
2207
+ // 코워커 이름이 비어있으면 'Coworker'로 기본값 설정
2208
+ const coworkerName = coworkerMatch ? (coworkerMatch[1] || 'Coworker') : null;
2111
2209
  const coworkerFolderName = isCoworker ? `yy_Coworker_${coworkerName}` : null;
2112
2210
 
2113
2211
  // 권한 정보 구성
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docuking-mcp",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "description": "DocuKing MCP Server - AI 시대의 문서 협업 플랫폼",
5
5
  "type": "module",
6
6
  "main": "index.js",