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.
- package/index.js +107 -15
- 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
|
|
1600
|
-
|
|
1601
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
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);
|