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.
- package/index.js +119 -21
- 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
|
-
|
|
1117
|
+
// 이름이 비어있는 경우도 처리 (sk_xxx_cw__)
|
|
1118
|
+
const coworkerMatch = apiKey.match(/^sk_[a-f0-9]+_cw_([^_]*)_/);
|
|
1118
1119
|
const isCoworker = !!coworkerMatch;
|
|
1119
|
-
|
|
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
|
-
|
|
1300
|
+
// 이름이 비어있는 경우도 처리 (sk_xxx_cw__)
|
|
1301
|
+
const coworkerMatch = apiKey.match(/^sk_[a-f0-9]+_cw_([^_]*)_/);
|
|
1272
1302
|
const isCoworker = !!coworkerMatch;
|
|
1273
|
-
|
|
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
|
|
1596
|
-
|
|
1597
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
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
|
-
|
|
2204
|
+
// 이름이 비어있는 경우도 처리 (sk_xxx_cw__)
|
|
2205
|
+
const coworkerMatch = apiKey.match(/^sk_[a-f0-9]+_cw_([^_]*)_/);
|
|
2109
2206
|
const isCoworker = !!coworkerMatch;
|
|
2110
|
-
|
|
2207
|
+
// 코워커 이름이 비어있으면 'Coworker'로 기본값 설정
|
|
2208
|
+
const coworkerName = coworkerMatch ? (coworkerMatch[1] || 'Coworker') : null;
|
|
2111
2209
|
const coworkerFolderName = isCoworker ? `yy_Coworker_${coworkerName}` : null;
|
|
2112
2210
|
|
|
2113
2211
|
// 권한 정보 구성
|