docuking-mcp 2.3.0 → 2.5.0
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 +178 -35
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1388,22 +1388,23 @@ Git처럼 무엇을 변경했는지 명확히 작성해주세요.
|
|
|
1388
1388
|
const { isCoworker, coworkerFolder } = parseCoworkerFromApiKey(apiKey);
|
|
1389
1389
|
const coworkerFolderName = isCoworker ? `yy_Coworker_${coworkerFolder}` : null;
|
|
1390
1390
|
|
|
1391
|
-
// 작업 폴더 결정:
|
|
1391
|
+
// 작업 폴더 결정: 프로젝트 루트 기준 절대경로 사용
|
|
1392
|
+
// 오너: yy_All_Docu/, zz_ai_*/ 폴더들 Push
|
|
1393
|
+
// 협업자: yy_Coworker_{폴더명}/ 폴더만 Push
|
|
1392
1394
|
const mainFolderPath = path.join(localPath, 'yy_All_Docu');
|
|
1393
|
-
let workingPath;
|
|
1394
|
-
let serverPathPrefix = ''; // 서버에 저장될 때 경로 접두사
|
|
1395
1395
|
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
workingPath = path.join(localPath, coworkerFolderName);
|
|
1399
|
-
serverPathPrefix = `${coworkerFolderName}/`;
|
|
1396
|
+
// Push 대상 폴더 목록 (프로젝트 루트 기준)
|
|
1397
|
+
let pushTargetFolders = [];
|
|
1400
1398
|
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1399
|
+
if (isCoworker) {
|
|
1400
|
+
// 협업자: yy_Coworker_{폴더명}/ 폴더만 Push
|
|
1401
|
+
const coworkerPath = path.join(localPath, coworkerFolderName);
|
|
1402
|
+
if (!fs.existsSync(coworkerPath)) {
|
|
1403
|
+
fs.mkdirSync(coworkerPath, { recursive: true });
|
|
1404
1404
|
}
|
|
1405
|
+
pushTargetFolders.push({ localPath: coworkerPath, serverPrefix: coworkerFolderName });
|
|
1405
1406
|
} else {
|
|
1406
|
-
// 오너: yy_All_Docu/ 폴더
|
|
1407
|
+
// 오너: yy_All_Docu/ 폴더 확인
|
|
1407
1408
|
if (!fs.existsSync(mainFolderPath)) {
|
|
1408
1409
|
return {
|
|
1409
1410
|
content: [
|
|
@@ -1415,8 +1416,18 @@ docuking_init을 먼저 실행하세요.`,
|
|
|
1415
1416
|
],
|
|
1416
1417
|
};
|
|
1417
1418
|
}
|
|
1418
|
-
|
|
1419
|
-
|
|
1419
|
+
|
|
1420
|
+
// 오너 Push 대상: yy_All_Docu/ + zz_ai_*/ 폴더들
|
|
1421
|
+
pushTargetFolders.push({ localPath: mainFolderPath, serverPrefix: 'yy_All_Docu' });
|
|
1422
|
+
|
|
1423
|
+
// zz_ai_* 폴더들 찾기
|
|
1424
|
+
const rootEntries = fs.readdirSync(localPath, { withFileTypes: true });
|
|
1425
|
+
for (const entry of rootEntries) {
|
|
1426
|
+
if (entry.isDirectory() && entry.name.startsWith('zz_ai_')) {
|
|
1427
|
+
const zzPath = path.join(localPath, entry.name);
|
|
1428
|
+
pushTargetFolders.push({ localPath: zzPath, serverPrefix: entry.name });
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1420
1431
|
}
|
|
1421
1432
|
|
|
1422
1433
|
// .env 파일 자동 백업: _Infra_Config/ 폴더에 복사
|
|
@@ -1525,10 +1536,12 @@ docuking_init을 먼저 실행하세요.`,
|
|
|
1525
1536
|
// 파일 목록 수집
|
|
1526
1537
|
const filesToPush = [];
|
|
1527
1538
|
const excludedFiles = []; // 제외된 파일 목록
|
|
1539
|
+
const largeFiles = [];
|
|
1528
1540
|
|
|
1529
1541
|
if (filePath) {
|
|
1530
|
-
// 특정 파일만
|
|
1531
|
-
const
|
|
1542
|
+
// 특정 파일만 - 첫 번째 대상 폴더에서 찾기
|
|
1543
|
+
const targetFolder = pushTargetFolders[0];
|
|
1544
|
+
const fullPath = path.join(targetFolder.localPath, filePath);
|
|
1532
1545
|
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
|
|
1533
1546
|
const fileType = getFileType(filePath);
|
|
1534
1547
|
if (fileType === 'excluded') {
|
|
@@ -1542,20 +1555,36 @@ docuking_init을 먼저 실행하세요.`,
|
|
|
1542
1555
|
};
|
|
1543
1556
|
}
|
|
1544
1557
|
|
|
1545
|
-
// 서버 경로:
|
|
1546
|
-
const serverFilePath =
|
|
1558
|
+
// 서버 경로: 폴더prefix/파일경로 (예: yy_All_Docu/회의록/test.md)
|
|
1559
|
+
const serverFilePath = `${targetFolder.serverPrefix}/${filePath}`;
|
|
1547
1560
|
filesToPush.push({ path: filePath, serverPath: serverFilePath, fullPath, fileType });
|
|
1548
1561
|
}
|
|
1549
1562
|
} else {
|
|
1550
|
-
// 전체 파일 -
|
|
1551
|
-
const
|
|
1552
|
-
collectFiles(workingPath, '', filesToPush, excludedFiles, largeFiles);
|
|
1563
|
+
// 전체 파일 - 모든 대상 폴더에서 수집
|
|
1564
|
+
const emptyFolders = []; // 빈 폴더 목록
|
|
1553
1565
|
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1566
|
+
for (const targetFolder of pushTargetFolders) {
|
|
1567
|
+
if (!fs.existsSync(targetFolder.localPath)) continue;
|
|
1568
|
+
|
|
1569
|
+
const folderFiles = [];
|
|
1570
|
+
collectFilesSimple(targetFolder.localPath, '', folderFiles, excludedFiles, largeFiles);
|
|
1571
|
+
|
|
1572
|
+
// 서버 경로 추가: prefix/상대경로 (예: yy_All_Docu/회의록/test.md)
|
|
1573
|
+
for (const file of folderFiles) {
|
|
1574
|
+
file.serverPath = `${targetFolder.serverPrefix}/${file.path}`;
|
|
1575
|
+
filesToPush.push(file);
|
|
1576
|
+
}
|
|
1558
1577
|
|
|
1578
|
+
// 빈 폴더 수집
|
|
1579
|
+
const folderEmptyDirs = [];
|
|
1580
|
+
collectEmptyFolders(targetFolder.localPath, '', folderEmptyDirs);
|
|
1581
|
+
for (const emptyDir of folderEmptyDirs) {
|
|
1582
|
+
emptyFolders.push({
|
|
1583
|
+
localPath: emptyDir,
|
|
1584
|
+
serverPath: `${targetFolder.serverPrefix}/${emptyDir}`,
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1559
1588
|
|
|
1560
1589
|
// 대용량 파일 경고
|
|
1561
1590
|
if (largeFiles.length > 0) {
|
|
@@ -1831,6 +1860,35 @@ docuking_init을 먼저 실행하세요.`,
|
|
|
1831
1860
|
}
|
|
1832
1861
|
}
|
|
1833
1862
|
|
|
1863
|
+
// 5. 빈 폴더 생성
|
|
1864
|
+
let createdEmptyFolders = 0;
|
|
1865
|
+
if (typeof emptyFolders !== 'undefined' && emptyFolders.length > 0) {
|
|
1866
|
+
for (const folder of emptyFolders) {
|
|
1867
|
+
try {
|
|
1868
|
+
const folderResponse = await fetch(`${API_ENDPOINT}/files`, {
|
|
1869
|
+
method: 'POST',
|
|
1870
|
+
headers: {
|
|
1871
|
+
'Content-Type': 'application/json',
|
|
1872
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
1873
|
+
},
|
|
1874
|
+
body: JSON.stringify({
|
|
1875
|
+
projectId,
|
|
1876
|
+
path: folder.serverPath,
|
|
1877
|
+
type: 'folder',
|
|
1878
|
+
name: folder.localPath.split('/').pop(),
|
|
1879
|
+
}),
|
|
1880
|
+
});
|
|
1881
|
+
|
|
1882
|
+
if (folderResponse.ok) {
|
|
1883
|
+
createdEmptyFolders++;
|
|
1884
|
+
console.error(`[DocuKing] 빈 폴더 생성: ${folder.serverPath}`);
|
|
1885
|
+
}
|
|
1886
|
+
} catch (e) {
|
|
1887
|
+
console.error(`[DocuKing] 빈 폴더 생성 실패: ${folder.serverPath} - ${e.message}`);
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1834
1892
|
// Sync 완료 알림
|
|
1835
1893
|
try {
|
|
1836
1894
|
await fetch(`${API_ENDPOINT}/projects/${projectId}/sync/complete`, {
|
|
@@ -1852,6 +1910,9 @@ docuking_init을 먼저 실행하세요.`,
|
|
|
1852
1910
|
|
|
1853
1911
|
// 요약 정보
|
|
1854
1912
|
let summary = `\n📦 커밋 메시지: "${message}"\n\n📊 처리 결과:\n - 총 파일: ${total}개\n - 업로드: ${successCount}개\n - 이동: ${movedCount}개\n - 삭제: ${deleted}개\n - 스킵 (변경 없음): ${skippedCount}개\n - 실패: ${failCount}개`;
|
|
1913
|
+
if (createdEmptyFolders > 0) {
|
|
1914
|
+
summary += `\n - 빈 폴더 생성: ${createdEmptyFolders}개`;
|
|
1915
|
+
}
|
|
1855
1916
|
if (excludedCount > 0) {
|
|
1856
1917
|
summary += `\n - 제외 (압축/설치파일): ${excludedCount}개`;
|
|
1857
1918
|
}
|
|
@@ -2018,14 +2079,15 @@ async function handlePull(args) {
|
|
|
2018
2079
|
const data = await response.json();
|
|
2019
2080
|
|
|
2020
2081
|
// 서버 경로에 따라 로컬 저장 위치 결정
|
|
2082
|
+
// 프로젝트 루트 기준 절대경로 그대로 사용
|
|
2021
2083
|
// - yy_All_Docu/xxx → yy_All_Docu/xxx (오너 문서)
|
|
2022
|
-
// - yy_Coworker_xxx/yyy → yy_Coworker_xxx/yyy (협업자
|
|
2084
|
+
// - yy_Coworker_xxx/yyy → yy_Coworker_xxx/yyy (협업자 폴더)
|
|
2085
|
+
// - zz_ai_xxx/yyy → zz_ai_xxx/yyy (AI 폴더)
|
|
2023
2086
|
let fullPath;
|
|
2024
|
-
if (file.path.startsWith('yy_All_Docu/')
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
// 협업자 폴더: 루트에 별도 폴더로 저장
|
|
2087
|
+
if (file.path.startsWith('yy_All_Docu/') ||
|
|
2088
|
+
file.path.startsWith('yy_Coworker_') ||
|
|
2089
|
+
file.path.startsWith('zz_ai_')) {
|
|
2090
|
+
// 서버 경로 그대로 로컬에 저장
|
|
2029
2091
|
fullPath = path.join(localPath, file.path);
|
|
2030
2092
|
} else {
|
|
2031
2093
|
// 기타 (구버전 호환): yy_All_Docu/ 안에 저장
|
|
@@ -2229,7 +2291,80 @@ function hasAllDocuFolder(projectPath) {
|
|
|
2229
2291
|
return fs.existsSync(allDocuPath);
|
|
2230
2292
|
}
|
|
2231
2293
|
|
|
2232
|
-
// 유틸:
|
|
2294
|
+
// 유틸: 빈 폴더 수집 (재귀)
|
|
2295
|
+
function collectEmptyFolders(basePath, relativePath, results) {
|
|
2296
|
+
const fullPath = path.join(basePath, relativePath);
|
|
2297
|
+
if (!fs.existsSync(fullPath)) return;
|
|
2298
|
+
|
|
2299
|
+
const entries = fs.readdirSync(fullPath, { withFileTypes: true });
|
|
2300
|
+
|
|
2301
|
+
// 현재 폴더가 비어있으면 (파일 없고, 하위 폴더도 없음)
|
|
2302
|
+
const hasFiles = entries.some(e => e.isFile());
|
|
2303
|
+
const subDirs = entries.filter(e => e.isDirectory());
|
|
2304
|
+
|
|
2305
|
+
if (!hasFiles && subDirs.length === 0) {
|
|
2306
|
+
// 완전히 빈 폴더
|
|
2307
|
+
results.push(relativePath);
|
|
2308
|
+
} else {
|
|
2309
|
+
// 하위 폴더 탐색
|
|
2310
|
+
for (const entry of subDirs) {
|
|
2311
|
+
const entryRelPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
2312
|
+
collectEmptyFolders(basePath, entryRelPath, results);
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
// 유틸: 디렉토리 재귀 탐색 (zz_* 제외 없음, Push용)
|
|
2318
|
+
// excludedFiles: 제외된 파일 목록을 수집할 배열 (선택)
|
|
2319
|
+
// largeFiles: 대용량 파일 목록을 수집할 배열 (선택)
|
|
2320
|
+
function collectFilesSimple(basePath, relativePath, results, excludedFiles = null, largeFiles = null) {
|
|
2321
|
+
const fullPath = path.join(basePath, relativePath);
|
|
2322
|
+
if (!fs.existsSync(fullPath)) return;
|
|
2323
|
+
|
|
2324
|
+
const entries = fs.readdirSync(fullPath, { withFileTypes: true });
|
|
2325
|
+
|
|
2326
|
+
for (const entry of entries) {
|
|
2327
|
+
const entryRelPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
2328
|
+
|
|
2329
|
+
if (entry.isDirectory()) {
|
|
2330
|
+
collectFilesSimple(basePath, entryRelPath, results, excludedFiles, largeFiles);
|
|
2331
|
+
} else if (entry.isFile()) {
|
|
2332
|
+
const fileType = getFileType(entry.name);
|
|
2333
|
+
|
|
2334
|
+
// 제외 파일은 건너뜀
|
|
2335
|
+
if (fileType === 'excluded') {
|
|
2336
|
+
console.error(`[DocuKing] 제외됨: ${entryRelPath} (지원하지 않는 파일 형식)`);
|
|
2337
|
+
if (excludedFiles) {
|
|
2338
|
+
excludedFiles.push(entryRelPath);
|
|
2339
|
+
}
|
|
2340
|
+
continue;
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
// 파일 크기 확인
|
|
2344
|
+
const fileFullPath = path.join(fullPath, entry.name);
|
|
2345
|
+
const stats = fs.statSync(fileFullPath);
|
|
2346
|
+
const fileSizeMB = stats.size / (1024 * 1024);
|
|
2347
|
+
|
|
2348
|
+
// 대용량 파일은 별도 추적
|
|
2349
|
+
if (stats.size > MAX_FILE_SIZE_BYTES) {
|
|
2350
|
+
console.error(`[DocuKing] ⚠️ 대용량 파일 제외: ${entryRelPath} (${fileSizeMB.toFixed(1)}MB > ${MAX_FILE_SIZE_MB}MB)`);
|
|
2351
|
+
if (largeFiles) {
|
|
2352
|
+
largeFiles.push({ path: entryRelPath, sizeMB: fileSizeMB.toFixed(1) });
|
|
2353
|
+
}
|
|
2354
|
+
continue;
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
results.push({
|
|
2358
|
+
path: entryRelPath,
|
|
2359
|
+
fullPath: fileFullPath,
|
|
2360
|
+
fileType,
|
|
2361
|
+
sizeMB: fileSizeMB,
|
|
2362
|
+
});
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
// 유틸: 디렉토리 재귀 탐색 (기존 호환용)
|
|
2233
2368
|
// excludedFiles: 제외된 파일 목록을 수집할 배열 (선택)
|
|
2234
2369
|
// largeFiles: 대용량 파일 목록을 수집할 배열 (선택)
|
|
2235
2370
|
// zz_* 폴더는 로컬 전용이므로 동기화에서 제외
|
|
@@ -2378,16 +2513,24 @@ async function handleStatus(args) {
|
|
|
2378
2513
|
const mainFolderPath = path.join(localPath, 'yy_All_Docu');
|
|
2379
2514
|
|
|
2380
2515
|
if (isCoworker) {
|
|
2381
|
-
// 협업자: yy_Coworker_{폴더명}/ 폴더에서 파일 수집
|
|
2516
|
+
// 협업자: yy_Coworker_{폴더명}/ 폴더에서 파일 수집
|
|
2382
2517
|
const coworkerPath = path.join(localPath, coworkerFolderName);
|
|
2383
2518
|
if (fs.existsSync(coworkerPath)) {
|
|
2384
|
-
|
|
2519
|
+
collectFilesSimple(coworkerPath, '', localFiles);
|
|
2385
2520
|
}
|
|
2386
2521
|
pushableFiles = localFiles; // 협업자는 자기 폴더의 모든 파일 Push 가능
|
|
2387
2522
|
} else {
|
|
2388
|
-
// 오너: yy_All_Docu/ 폴더에서 파일 수집
|
|
2523
|
+
// 오너: yy_All_Docu/ + zz_ai_*/ 폴더에서 파일 수집
|
|
2389
2524
|
if (fs.existsSync(mainFolderPath)) {
|
|
2390
|
-
|
|
2525
|
+
collectFilesSimple(mainFolderPath, '', localFiles);
|
|
2526
|
+
}
|
|
2527
|
+
// zz_ai_* 폴더들도 수집
|
|
2528
|
+
const rootEntries = fs.readdirSync(localPath, { withFileTypes: true });
|
|
2529
|
+
for (const entry of rootEntries) {
|
|
2530
|
+
if (entry.isDirectory() && entry.name.startsWith('zz_ai_')) {
|
|
2531
|
+
const zzPath = path.join(localPath, entry.name);
|
|
2532
|
+
collectFilesSimple(zzPath, '', localFiles);
|
|
2533
|
+
}
|
|
2391
2534
|
}
|
|
2392
2535
|
pushableFiles = localFiles; // 오너는 모든 파일 Push 가능
|
|
2393
2536
|
}
|