@ppdocs/mcp 3.2.38 → 3.3.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/dist/index.js +1 -15
- package/dist/storage/httpClient.d.ts +0 -12
- package/dist/storage/httpClient.js +0 -125
- package/dist/tools/files.d.ts +2 -3
- package/dist/tools/files.js +4 -21
- package/package.json +1 -5
- package/dist/sync/beacon.d.ts +0 -26
- package/dist/sync/beacon.js +0 -186
package/dist/index.js
CHANGED
|
@@ -45,21 +45,7 @@ async function main() {
|
|
|
45
45
|
});
|
|
46
46
|
const transport = new StdioServerTransport();
|
|
47
47
|
await server.connect(transport);
|
|
48
|
-
|
|
49
|
-
let beacon = null;
|
|
50
|
-
// 启动后台代码同步引擎 (Code Beacon) — 仅在有配置时启动
|
|
51
|
-
if (config) {
|
|
52
|
-
try {
|
|
53
|
-
const { SyncBeacon } = await import('./sync/beacon.js');
|
|
54
|
-
beacon = new SyncBeacon(process.cwd(), config.projectId);
|
|
55
|
-
beacon.start();
|
|
56
|
-
}
|
|
57
|
-
catch (err) {
|
|
58
|
-
console.error(`[Code Beacon] Failed to start:`, err);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
// 优雅关闭:进程退出时停止 watcher,防止上传截断
|
|
62
|
-
const shutdown = () => { beacon?.stop(); process.exit(0); };
|
|
48
|
+
const shutdown = () => { process.exit(0); };
|
|
63
49
|
process.on('SIGINT', shutdown);
|
|
64
50
|
process.on('SIGTERM', shutdown);
|
|
65
51
|
}
|
|
@@ -67,14 +67,6 @@ export declare class PpdocsApiClient {
|
|
|
67
67
|
localPath: string;
|
|
68
68
|
fileCount: number;
|
|
69
69
|
}>;
|
|
70
|
-
/** 上传本地目录或单文件到中心服务器 (打包 zip → POST) */
|
|
71
|
-
uploadFiles(localPath: string, remoteDir?: string): Promise<{
|
|
72
|
-
fileCount: number;
|
|
73
|
-
}>;
|
|
74
|
-
/** 上传已打包好的 zip 数据到本项目 files/ (供 Code Beacon 使用) */
|
|
75
|
-
uploadRawZip(zipData: Buffer | Uint8Array | any, remoteDir?: string): Promise<{
|
|
76
|
-
fileCount: number;
|
|
77
|
-
}>;
|
|
78
70
|
/** 跨项目: 列出文件 */
|
|
79
71
|
crossListFiles(target: string, dir?: string): Promise<FileInfo[]>;
|
|
80
72
|
/** 跨项目: 读取文件 */
|
|
@@ -126,10 +118,6 @@ export declare class PpdocsApiClient {
|
|
|
126
118
|
localPath: string;
|
|
127
119
|
fileCount: number;
|
|
128
120
|
}>;
|
|
129
|
-
/** 上传本地目录或单文件到公共文件池 */
|
|
130
|
-
publicFilesUpload(localPath: string, remoteDir?: string): Promise<{
|
|
131
|
-
fileCount: number;
|
|
132
|
-
}>;
|
|
133
121
|
meetingJoin(agentId: string, agentType: string): Promise<unknown>;
|
|
134
122
|
meetingLeave(agentId: string): Promise<unknown>;
|
|
135
123
|
meetingPost(agentId: string, content: string, msgType?: string): Promise<unknown>;
|
|
@@ -9,11 +9,6 @@
|
|
|
9
9
|
function cleanPath(p) {
|
|
10
10
|
return p.replace(/^\//, '');
|
|
11
11
|
}
|
|
12
|
-
/** 排除的目录 (与 Rust 端 EXCLUDED_DIRS 保持一致) */
|
|
13
|
-
const EXCLUDED_DIRS = new Set([
|
|
14
|
-
'node_modules', '.git', 'target', 'dist', 'build', '.next',
|
|
15
|
-
'__pycache__', '.venv', 'venv', '.idea', '.vs', '.vscode',
|
|
16
|
-
]);
|
|
17
12
|
/** 下载 zip 并解压到指定目录或 temp 目录 */
|
|
18
13
|
async function fetchAndExtractZip(url, localPath) {
|
|
19
14
|
const controller = new AbortController();
|
|
@@ -288,74 +283,6 @@ export class PpdocsApiClient {
|
|
|
288
283
|
async download(remotePath, localPath) {
|
|
289
284
|
return fetchAndExtractZip(`${this.baseUrl}/files-download/${cleanPath(remotePath)}`, localPath);
|
|
290
285
|
}
|
|
291
|
-
/** 上传本地目录或单文件到中心服务器 (打包 zip → POST) */
|
|
292
|
-
async uploadFiles(localPath, remoteDir) {
|
|
293
|
-
const fs = await import('fs');
|
|
294
|
-
const path = await import('path');
|
|
295
|
-
const AdmZip = (await import('adm-zip')).default;
|
|
296
|
-
if (!fs.existsSync(localPath)) {
|
|
297
|
-
throw new Error(`本地路径不存在: ${localPath}`);
|
|
298
|
-
}
|
|
299
|
-
const zip = new AdmZip();
|
|
300
|
-
const stat = fs.statSync(localPath);
|
|
301
|
-
if (stat.isDirectory()) {
|
|
302
|
-
const addDir = (dirPath, zipPath) => {
|
|
303
|
-
for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
|
|
304
|
-
if (entry.name.startsWith('.'))
|
|
305
|
-
continue;
|
|
306
|
-
const fullPath = path.join(dirPath, entry.name);
|
|
307
|
-
if (entry.isDirectory()) {
|
|
308
|
-
if (EXCLUDED_DIRS.has(entry.name))
|
|
309
|
-
continue;
|
|
310
|
-
addDir(fullPath, zipPath ? `${zipPath}/${entry.name}` : entry.name);
|
|
311
|
-
}
|
|
312
|
-
else {
|
|
313
|
-
if (fs.statSync(fullPath).size > 10_485_760)
|
|
314
|
-
continue;
|
|
315
|
-
zip.addLocalFile(fullPath, zipPath || undefined);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
};
|
|
319
|
-
addDir(localPath, '');
|
|
320
|
-
}
|
|
321
|
-
else {
|
|
322
|
-
if (stat.size > 10_485_760)
|
|
323
|
-
throw new Error('文件超过 10MB 限制');
|
|
324
|
-
zip.addLocalFile(localPath);
|
|
325
|
-
}
|
|
326
|
-
const buffer = zip.toBuffer();
|
|
327
|
-
if (buffer.length > 104_857_600) {
|
|
328
|
-
throw new Error('打包后超过 100MB 限制,请缩小目录范围');
|
|
329
|
-
}
|
|
330
|
-
const query = remoteDir ? `?dir=${encodeURIComponent(remoteDir)}` : '';
|
|
331
|
-
const response = await fetch(`${this.baseUrl}/files${query}`, {
|
|
332
|
-
method: 'POST',
|
|
333
|
-
headers: { 'Content-Type': 'application/octet-stream' },
|
|
334
|
-
body: new Uint8Array(buffer),
|
|
335
|
-
});
|
|
336
|
-
if (!response.ok) {
|
|
337
|
-
const error = await response.json().catch(() => ({ error: response.statusText }));
|
|
338
|
-
throw new Error(error.error || `HTTP ${response.status}`);
|
|
339
|
-
}
|
|
340
|
-
const result = await response.json();
|
|
341
|
-
return { fileCount: result.data?.fileCount || 0 };
|
|
342
|
-
}
|
|
343
|
-
// ============ 直接上传已打包的 ZIP ============
|
|
344
|
-
/** 上传已打包好的 zip 数据到本项目 files/ (供 Code Beacon 使用) */
|
|
345
|
-
async uploadRawZip(zipData, remoteDir) {
|
|
346
|
-
const query = remoteDir ? `?dir=${encodeURIComponent(remoteDir)}` : '';
|
|
347
|
-
const response = await fetch(`${this.baseUrl}/files${query}`, {
|
|
348
|
-
method: 'POST',
|
|
349
|
-
headers: { 'Content-Type': 'application/octet-stream' },
|
|
350
|
-
body: Buffer.isBuffer(zipData) ? new Uint8Array(zipData) : zipData,
|
|
351
|
-
});
|
|
352
|
-
if (!response.ok) {
|
|
353
|
-
const error = await response.json().catch(() => ({ error: response.statusText }));
|
|
354
|
-
throw new Error(error.error || `HTTP ${response.status}`);
|
|
355
|
-
}
|
|
356
|
-
const result = await response.json();
|
|
357
|
-
return { fileCount: result.data?.fileCount || 0 };
|
|
358
|
-
}
|
|
359
286
|
// ============ 跨项目文件访问 (只读) ============
|
|
360
287
|
/** 跨项目: 列出文件 */
|
|
361
288
|
async crossListFiles(target, dir) {
|
|
@@ -493,58 +420,6 @@ export class PpdocsApiClient {
|
|
|
493
420
|
async publicFilesDownload(remotePath, localPath) {
|
|
494
421
|
return fetchAndExtractZip(`${this.serverUrl}/api/public-files/download/${cleanPath(remotePath)}`, localPath);
|
|
495
422
|
}
|
|
496
|
-
/** 上传本地目录或单文件到公共文件池 */
|
|
497
|
-
async publicFilesUpload(localPath, remoteDir) {
|
|
498
|
-
const fs = await import('fs');
|
|
499
|
-
const path = await import('path');
|
|
500
|
-
const AdmZip = (await import('adm-zip')).default;
|
|
501
|
-
if (!fs.existsSync(localPath)) {
|
|
502
|
-
throw new Error(`本地路径不存在: ${localPath}`);
|
|
503
|
-
}
|
|
504
|
-
const zip = new AdmZip();
|
|
505
|
-
const stat = fs.statSync(localPath);
|
|
506
|
-
if (stat.isDirectory()) {
|
|
507
|
-
const addDir = (dirPath, zipPath) => {
|
|
508
|
-
for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
|
|
509
|
-
if (entry.name.startsWith('.'))
|
|
510
|
-
continue;
|
|
511
|
-
const fullPath = path.join(dirPath, entry.name);
|
|
512
|
-
if (entry.isDirectory()) {
|
|
513
|
-
if (EXCLUDED_DIRS.has(entry.name))
|
|
514
|
-
continue;
|
|
515
|
-
addDir(fullPath, zipPath ? `${zipPath}/${entry.name}` : entry.name);
|
|
516
|
-
}
|
|
517
|
-
else {
|
|
518
|
-
if (fs.statSync(fullPath).size > 10_485_760)
|
|
519
|
-
continue;
|
|
520
|
-
zip.addLocalFile(fullPath, zipPath || undefined);
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
};
|
|
524
|
-
addDir(localPath, '');
|
|
525
|
-
}
|
|
526
|
-
else {
|
|
527
|
-
if (stat.size > 10_485_760)
|
|
528
|
-
throw new Error('文件超过 10MB 限制');
|
|
529
|
-
zip.addLocalFile(localPath);
|
|
530
|
-
}
|
|
531
|
-
const buffer = zip.toBuffer();
|
|
532
|
-
if (buffer.length > 104_857_600) {
|
|
533
|
-
throw new Error('打包后超过 100MB 限制');
|
|
534
|
-
}
|
|
535
|
-
const query = remoteDir ? `?dir=${encodeURIComponent(remoteDir)}` : '';
|
|
536
|
-
const response = await fetch(`${this.serverUrl}/api/public-files/upload${query}`, {
|
|
537
|
-
method: 'POST',
|
|
538
|
-
headers: { 'Content-Type': 'application/octet-stream' },
|
|
539
|
-
body: new Uint8Array(buffer),
|
|
540
|
-
});
|
|
541
|
-
if (!response.ok) {
|
|
542
|
-
const error = await response.json().catch(() => ({ error: response.statusText }));
|
|
543
|
-
throw new Error(error.error || `HTTP ${response.status}`);
|
|
544
|
-
}
|
|
545
|
-
const result = await response.json();
|
|
546
|
-
return { fileCount: result.data?.fileCount || 0 };
|
|
547
|
-
}
|
|
548
423
|
// ============ 多AI协作会议 ============
|
|
549
424
|
async meetingJoin(agentId, agentType) {
|
|
550
425
|
return this.request('/meeting/join', {
|
package/dist/tools/files.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 📁 kg_files (
|
|
3
|
-
*
|
|
4
|
-
* + 公共文件池: public_list, public_read, public_upload, public_download, public_delete
|
|
2
|
+
* 📁 kg_files — 文件浏览/读取/下载 (上传已移除,全部本地化)
|
|
3
|
+
* 公共文件池: public_list, public_read, public_download, public_delete, public_mkdir, public_rename
|
|
5
4
|
*/
|
|
6
5
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
7
6
|
export declare function registerFileTools(server: McpServer): void;
|
package/dist/tools/files.js
CHANGED
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 📁 kg_files (
|
|
3
|
-
*
|
|
4
|
-
* + 公共文件池: public_list, public_read, public_upload, public_download, public_delete
|
|
2
|
+
* 📁 kg_files — 文件浏览/读取/下载 (上传已移除,全部本地化)
|
|
3
|
+
* 公共文件池: public_list, public_read, public_download, public_delete, public_mkdir, public_rename
|
|
5
4
|
*/
|
|
6
5
|
import { z } from 'zod';
|
|
7
6
|
import { getClient } from '../storage/httpClient.js';
|
|
8
7
|
import { wrap, safeTool, crossPrefix, formatFileSize } from './shared.js';
|
|
9
8
|
export function registerFileTools(server) {
|
|
10
9
|
const client = () => getClient();
|
|
11
|
-
server.tool('kg_files', '📁 项目文件操作 —
|
|
12
|
-
action: z.enum(['list', 'read', '
|
|
10
|
+
server.tool('kg_files', '📁 项目文件操作 — 浏览目录、读取文件、下载。action: list(目录浏览)|read(读取文件)|download(下载文件)|public_list(公共文件池浏览)|public_read(读取公共文件)|public_download(下载公共文件)|public_delete(删除公共文件)|public_mkdir(创建公共池子目录)|public_rename(重命名公共文件)', {
|
|
11
|
+
action: z.enum(['list', 'read', 'download', 'public_list', 'public_read', 'public_download', 'public_delete', 'public_mkdir', 'public_rename'])
|
|
13
12
|
.describe('操作类型'),
|
|
14
13
|
path: z.string().optional()
|
|
15
14
|
.describe('文件路径 (read/download/public_read/public_download/public_delete/public_mkdir, 如"src/main.ts")'),
|
|
16
15
|
dir: z.string().optional()
|
|
17
16
|
.describe('子目录路径 (list/public_list, 如"src/components")'),
|
|
18
|
-
localDir: z.string().optional()
|
|
19
|
-
.describe('本地路径(目录或单文件) (upload/public_upload)'),
|
|
20
17
|
localPath: z.string().optional()
|
|
21
18
|
.describe('本地保存路径 (download/public_download)'),
|
|
22
|
-
remoteDir: z.string().optional()
|
|
23
|
-
.describe('远程目标子目录 (upload/public_upload)'),
|
|
24
19
|
newName: z.string().optional()
|
|
25
20
|
.describe('新名称 (public_rename)'),
|
|
26
21
|
targetProject: z.string().optional()
|
|
@@ -51,12 +46,6 @@ export function registerFileTools(server) {
|
|
|
51
46
|
const prefix = args.targetProject ? crossPrefix(args.targetProject) : '';
|
|
52
47
|
return wrap(`${prefix}📄 ${args.path}\n\n\`\`\`\n${content}\n\`\`\``);
|
|
53
48
|
}
|
|
54
|
-
case 'upload': {
|
|
55
|
-
if (!args.localDir)
|
|
56
|
-
return wrap('❌ upload 需要 localDir');
|
|
57
|
-
const result = await client().uploadFiles(args.localDir, args.remoteDir);
|
|
58
|
-
return wrap(`✅ 上传成功\n\n- 文件数量: ${result.fileCount}\n- 目标目录: ${args.remoteDir || '/'}`);
|
|
59
|
-
}
|
|
60
49
|
case 'download': {
|
|
61
50
|
if (!args.path)
|
|
62
51
|
return wrap('❌ download 需要 path');
|
|
@@ -85,12 +74,6 @@ export function registerFileTools(server) {
|
|
|
85
74
|
const content = await client().publicFilesRead(args.path);
|
|
86
75
|
return wrap(`📦 公共文件 ${args.path}\n\n\`\`\`\n${content}\n\`\`\``);
|
|
87
76
|
}
|
|
88
|
-
case 'public_upload': {
|
|
89
|
-
if (!args.localDir)
|
|
90
|
-
return wrap('❌ public_upload 需要 localDir');
|
|
91
|
-
const result = await client().publicFilesUpload(args.localDir, args.remoteDir);
|
|
92
|
-
return wrap(`✅ 已上传到公共文件池\n\n- 文件数量: ${result.fileCount}\n- 目标目录: ${args.remoteDir || '/'}`);
|
|
93
|
-
}
|
|
94
77
|
case 'public_download': {
|
|
95
78
|
if (!args.path)
|
|
96
79
|
return wrap('❌ public_download 需要 path');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ppdocs/mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "ppdocs MCP Server - Knowledge Graph for Claude",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -33,15 +33,11 @@
|
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
35
35
|
"adm-zip": "^0.5.16",
|
|
36
|
-
"archiver": "^7.0.1",
|
|
37
|
-
"chokidar": "^5.0.0",
|
|
38
36
|
"express": "^5.1.0",
|
|
39
37
|
"zod": "^4.1.13"
|
|
40
38
|
},
|
|
41
39
|
"devDependencies": {
|
|
42
40
|
"@types/adm-zip": "^0.5.7",
|
|
43
|
-
"@types/archiver": "^7.0.0",
|
|
44
|
-
"@types/chokidar": "^1.7.5",
|
|
45
41
|
"@types/express": "^5.0.0",
|
|
46
42
|
"@types/node": "^22.0.0",
|
|
47
43
|
"typescript": "^5.7.0"
|
package/dist/sync/beacon.d.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
export declare class SyncBeacon {
|
|
2
|
-
private cwd;
|
|
3
|
-
private projectId;
|
|
4
|
-
private debounceMs;
|
|
5
|
-
private watcher;
|
|
6
|
-
private isSyncing;
|
|
7
|
-
private pendingSync;
|
|
8
|
-
private debounceTimer;
|
|
9
|
-
constructor(cwd: string, projectId: string, debounceMs?: number);
|
|
10
|
-
/**
|
|
11
|
-
* 启动同步引擎
|
|
12
|
-
*/
|
|
13
|
-
start(): void;
|
|
14
|
-
/**
|
|
15
|
-
* 停止同步引擎
|
|
16
|
-
*/
|
|
17
|
-
stop(): void;
|
|
18
|
-
/**
|
|
19
|
-
* 触发同步计算
|
|
20
|
-
*/
|
|
21
|
-
private triggerSync;
|
|
22
|
-
/**
|
|
23
|
-
* 核心: 打包并推送全量快照
|
|
24
|
-
*/
|
|
25
|
-
private performSync;
|
|
26
|
-
}
|
package/dist/sync/beacon.js
DELETED
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as os from 'os';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import archiver from 'archiver';
|
|
5
|
-
import * as chokidar from 'chokidar';
|
|
6
|
-
import { getClient } from '../storage/httpClient.js';
|
|
7
|
-
// 需要排除的大文件/敏感目录
|
|
8
|
-
const EXCLUDED_DIRS = [
|
|
9
|
-
'node_modules',
|
|
10
|
-
'.git',
|
|
11
|
-
'.next',
|
|
12
|
-
'dist',
|
|
13
|
-
'build',
|
|
14
|
-
'out',
|
|
15
|
-
'target',
|
|
16
|
-
'__pycache__',
|
|
17
|
-
'.venv',
|
|
18
|
-
'venv',
|
|
19
|
-
'.idea',
|
|
20
|
-
'.vscode',
|
|
21
|
-
'.vs',
|
|
22
|
-
'.ppdocs',
|
|
23
|
-
'.cursor',
|
|
24
|
-
'.claude',
|
|
25
|
-
'.gemini',
|
|
26
|
-
];
|
|
27
|
-
export class SyncBeacon {
|
|
28
|
-
cwd;
|
|
29
|
-
projectId;
|
|
30
|
-
debounceMs;
|
|
31
|
-
watcher = null;
|
|
32
|
-
isSyncing = false;
|
|
33
|
-
pendingSync = false;
|
|
34
|
-
debounceTimer = null;
|
|
35
|
-
constructor(cwd, projectId, debounceMs = 15000 // 默认 15 秒防抖
|
|
36
|
-
) {
|
|
37
|
-
this.cwd = cwd;
|
|
38
|
-
this.projectId = projectId;
|
|
39
|
-
this.debounceMs = debounceMs;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* 启动同步引擎
|
|
43
|
-
*/
|
|
44
|
-
start() {
|
|
45
|
-
if (this.watcher)
|
|
46
|
-
return;
|
|
47
|
-
console.error(`[Code Beacon] Started monitoring project: ${this.projectId}`);
|
|
48
|
-
// [M1 修复] 首次启动延迟 3 秒,让 MCP 工具注册和事件循环先稳定
|
|
49
|
-
setTimeout(() => this.triggerSync(), 3000);
|
|
50
|
-
// 配置监听器
|
|
51
|
-
this.watcher = chokidar.watch(this.cwd, {
|
|
52
|
-
ignored: (filePath) => {
|
|
53
|
-
const basename = path.basename(filePath);
|
|
54
|
-
if (EXCLUDED_DIRS.includes(basename))
|
|
55
|
-
return true;
|
|
56
|
-
if (basename.startsWith('.') && basename !== '.env.example' && !filePath.includes('.cursorrules') && !filePath.includes('ppdocs.md'))
|
|
57
|
-
return true;
|
|
58
|
-
return false;
|
|
59
|
-
},
|
|
60
|
-
persistent: true,
|
|
61
|
-
ignoreInitial: true,
|
|
62
|
-
});
|
|
63
|
-
// 绑定事件
|
|
64
|
-
const scheduleSync = (evt, p) => {
|
|
65
|
-
if (this.debounceTimer) {
|
|
66
|
-
clearTimeout(this.debounceTimer);
|
|
67
|
-
}
|
|
68
|
-
this.debounceTimer = setTimeout(() => {
|
|
69
|
-
console.error(`[Code Beacon] Changes detected (${evt}: ${path.basename(p)}), scheduling sync...`);
|
|
70
|
-
this.triggerSync();
|
|
71
|
-
}, this.debounceMs);
|
|
72
|
-
};
|
|
73
|
-
this.watcher
|
|
74
|
-
.on('add', (p) => scheduleSync('add', p))
|
|
75
|
-
.on('change', (p) => scheduleSync('change', p))
|
|
76
|
-
.on('unlink', (p) => scheduleSync('delete', p))
|
|
77
|
-
.on('unlinkDir', (p) => scheduleSync('delete dir', p));
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* 停止同步引擎
|
|
81
|
-
*/
|
|
82
|
-
stop() {
|
|
83
|
-
if (this.debounceTimer) {
|
|
84
|
-
clearTimeout(this.debounceTimer);
|
|
85
|
-
this.debounceTimer = null;
|
|
86
|
-
}
|
|
87
|
-
if (this.watcher) {
|
|
88
|
-
this.watcher.close();
|
|
89
|
-
this.watcher = null;
|
|
90
|
-
console.error(`[Code Beacon] Stopped monitoring project: ${this.projectId}`);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* 触发同步计算
|
|
95
|
-
*/
|
|
96
|
-
async triggerSync() {
|
|
97
|
-
if (this.isSyncing) {
|
|
98
|
-
this.pendingSync = true;
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
this.isSyncing = true;
|
|
102
|
-
this.pendingSync = false;
|
|
103
|
-
try {
|
|
104
|
-
await this.performSync();
|
|
105
|
-
}
|
|
106
|
-
catch (error) {
|
|
107
|
-
console.error(`[Code Beacon] Sync error:`, error);
|
|
108
|
-
}
|
|
109
|
-
finally {
|
|
110
|
-
this.isSyncing = false;
|
|
111
|
-
if (this.pendingSync) {
|
|
112
|
-
this.triggerSync();
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* 核心: 打包并推送全量快照
|
|
118
|
-
*/
|
|
119
|
-
async performSync() {
|
|
120
|
-
// [C3 修复] getClient() 在未初始化时 throw,不会返回 null
|
|
121
|
-
let client;
|
|
122
|
-
try {
|
|
123
|
-
client = getClient();
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
console.warn('[Code Beacon] API Client not ready, skipping sync.');
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
return new Promise((resolve, reject) => {
|
|
130
|
-
try {
|
|
131
|
-
// 使用系统临时目录存放打包文件,避免 .ppdocs(是配置文件非目录)冲突
|
|
132
|
-
const tmpFile = path.join(os.tmpdir(), `beacon-${this.projectId}-${Date.now()}.zip`);
|
|
133
|
-
const output = fs.createWriteStream(tmpFile);
|
|
134
|
-
const archive = archiver('zip', {
|
|
135
|
-
zlib: { level: 3 } // 低压缩率,追求速度
|
|
136
|
-
});
|
|
137
|
-
// [C1 修复] 监听 WriteStream 的 error 事件,防止 Node 进程崩溃
|
|
138
|
-
output.on('error', (err) => {
|
|
139
|
-
console.error('[Code Beacon] WriteStream error:', err);
|
|
140
|
-
reject(err);
|
|
141
|
-
});
|
|
142
|
-
output.on('close', async () => {
|
|
143
|
-
try {
|
|
144
|
-
const data = await fs.promises.readFile(tmpFile);
|
|
145
|
-
// [C2 修复] 使用本项目标准上传接口,而非 crossUploadFiles
|
|
146
|
-
await client.uploadRawZip(data);
|
|
147
|
-
console.error(`[Code Beacon] Snapshot synced: ${archive.pointer()} bytes`);
|
|
148
|
-
}
|
|
149
|
-
catch (e) {
|
|
150
|
-
console.error('[Code Beacon] Upload failed:', e);
|
|
151
|
-
}
|
|
152
|
-
finally {
|
|
153
|
-
try {
|
|
154
|
-
if (fs.existsSync(tmpFile))
|
|
155
|
-
fs.promises.unlink(tmpFile);
|
|
156
|
-
}
|
|
157
|
-
catch { }
|
|
158
|
-
resolve();
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
archive.on('error', (err) => {
|
|
162
|
-
reject(err);
|
|
163
|
-
});
|
|
164
|
-
archive.pipe(output);
|
|
165
|
-
// 遍历所有非隐藏、非排除的文件打包
|
|
166
|
-
archive.glob('**/*', {
|
|
167
|
-
cwd: this.cwd,
|
|
168
|
-
ignore: [
|
|
169
|
-
...EXCLUDED_DIRS.map(d => `${d}/**`),
|
|
170
|
-
'.ppdocs',
|
|
171
|
-
'.*'
|
|
172
|
-
],
|
|
173
|
-
dot: false
|
|
174
|
-
});
|
|
175
|
-
// [A2 修复] 排除 .env(敏感凭证),仅包含安全的 IDE 配置
|
|
176
|
-
if (fs.existsSync(path.join(this.cwd, '.cursorrules'))) {
|
|
177
|
-
archive.file(path.join(this.cwd, '.cursorrules'), { name: '.cursorrules' });
|
|
178
|
-
}
|
|
179
|
-
archive.finalize();
|
|
180
|
-
}
|
|
181
|
-
catch (err) {
|
|
182
|
-
reject(err);
|
|
183
|
-
}
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
}
|