@ppdocs/mcp 3.2.38 → 3.4.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 CHANGED
@@ -45,21 +45,7 @@ async function main() {
45
45
  });
46
46
  const transport = new StdioServerTransport();
47
47
  await server.connect(transport);
48
- // [M2 修复] beacon 变量提升到外部,支持优雅关闭
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
  }
@@ -29,6 +29,8 @@ export declare class PpdocsApiClient {
29
29
  saveReference(reference: Reference): Promise<boolean>;
30
30
  deleteReference(refId: string): Promise<boolean>;
31
31
  readReferenceFile(path: string): Promise<string>;
32
+ /** 复制本地文件到参考文件库, 返回相对路径列表 */
33
+ copyReferenceFiles(paths: string[], targetDir?: string): Promise<string[]>;
32
34
  listTasks(status?: 'active' | 'archived'): Promise<TaskSummary[]>;
33
35
  getTask(taskId: string, mode?: 'smart' | 'full'): Promise<Task | null>;
34
36
  createTask(task: {
@@ -67,14 +69,6 @@ export declare class PpdocsApiClient {
67
69
  localPath: string;
68
70
  fileCount: number;
69
71
  }>;
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
72
  /** 跨项目: 列出文件 */
79
73
  crossListFiles(target: string, dir?: string): Promise<FileInfo[]>;
80
74
  /** 跨项目: 读取文件 */
@@ -126,10 +120,6 @@ export declare class PpdocsApiClient {
126
120
  localPath: string;
127
121
  fileCount: number;
128
122
  }>;
129
- /** 上传本地目录或单文件到公共文件池 */
130
- publicFilesUpload(localPath: string, remoteDir?: string): Promise<{
131
- fileCount: number;
132
- }>;
133
123
  meetingJoin(agentId: string, agentType: string): Promise<unknown>;
134
124
  meetingLeave(agentId: string): Promise<unknown>;
135
125
  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();
@@ -157,6 +152,13 @@ export class PpdocsApiClient {
157
152
  async readReferenceFile(path) {
158
153
  return this.request(`/refs/files/${cleanPath(path)}`);
159
154
  }
155
+ /** 复制本地文件到参考文件库, 返回相对路径列表 */
156
+ async copyReferenceFiles(paths, targetDir = '.') {
157
+ return this.request('/refs/copy-in', {
158
+ method: 'POST',
159
+ body: JSON.stringify({ paths, target_dir: targetDir }),
160
+ });
161
+ }
160
162
  // ============ 任务管理 ============
161
163
  async listTasks(status) {
162
164
  const query = status ? `?status=${status}` : '';
@@ -288,74 +290,6 @@ export class PpdocsApiClient {
288
290
  async download(remotePath, localPath) {
289
291
  return fetchAndExtractZip(`${this.baseUrl}/files-download/${cleanPath(remotePath)}`, localPath);
290
292
  }
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
293
  // ============ 跨项目文件访问 (只读) ============
360
294
  /** 跨项目: 列出文件 */
361
295
  async crossListFiles(target, dir) {
@@ -493,58 +427,6 @@ export class PpdocsApiClient {
493
427
  async publicFilesDownload(remotePath, localPath) {
494
428
  return fetchAndExtractZip(`${this.serverUrl}/api/public-files/download/${cleanPath(remotePath)}`, localPath);
495
429
  }
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
430
  // ============ 多AI协作会议 ============
549
431
  async meetingJoin(agentId, agentType) {
550
432
  return this.request('/meeting/join', {
@@ -103,9 +103,9 @@ function formatDetailView(d) {
103
103
  }
104
104
  export function registerDiscussionTools(server, ctx) {
105
105
  const client = () => getClient();
106
- server.tool('kg_discuss', '💬 跨项目讨论 — action: list|read|create|reply|complete|close|delete|history。权限: 仅成员可读写,仅发起人可删除/归档', {
107
- action: z.enum(['list', 'read', 'create', 'reply', 'complete', 'close', 'delete', 'history'])
108
- .describe('操作类型'),
106
+ server.tool('kg_discuss', '💬 跨项目讨论 — action 可选: 省略时无 id/ids 则列出讨论,有 id/ids 则读取详情。亦可显式指定 list|read|create|reply|complete|close|delete|history。权限: 仅成员可读写,仅发起人可删除/归档', {
107
+ action: z.enum(['list', 'read', 'create', 'reply', 'complete', 'close', 'delete', 'history']).optional()
108
+ .describe('操作类型(可选:省略时根据 id/ids 自动为 read,否则为 list)'),
109
109
  id: z.string().optional()
110
110
  .describe('讨论哈希ID (read/reply/complete/close/delete)'),
111
111
  ids: z.array(z.string()).optional()
@@ -128,7 +128,13 @@ export function registerDiscussionTools(server, ctx) {
128
128
  const decoded = decodeObjectStrings(args);
129
129
  const me = sender(ctx);
130
130
  const myPid = ctx.projectId;
131
- switch (decoded.action) {
131
+ // 智能默认:未指定 action 时,有 id/ids → read,否则 → list
132
+ let action = decoded.action;
133
+ if (!action) {
134
+ const hasIds = Boolean(decoded.id) || (Array.isArray(decoded.ids) && decoded.ids.length > 0);
135
+ action = hasIds ? 'read' : 'list';
136
+ }
137
+ switch (action) {
132
138
  // ============ 公开操作 ============
133
139
  case 'list': {
134
140
  const active = await client().discussionList();
@@ -249,7 +255,7 @@ export function registerDiscussionTools(server, ctx) {
249
255
  return wrap(`✅ 讨论已删除 (ID: ${decoded.id})`);
250
256
  }
251
257
  default:
252
- return wrap(`❌ 未知 action: ${decoded.action}`);
258
+ return wrap(`❌ 未知 action: ${action}`);
253
259
  }
254
260
  }));
255
261
  }
@@ -1,7 +1,6 @@
1
1
  /**
2
- * 📁 kg_files (5→1)
3
- * 合并: project_list_files, project_read_file, project_upload, project_download
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;
@@ -1,26 +1,21 @@
1
1
  /**
2
- * 📁 kg_files (5→1)
3
- * 合并: project_list_files, project_read_file, project_upload, project_download
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', '📁 项目文件操作 — 浏览目录、读取文件、上传下载。action: list(目录浏览)|read(读取文件)|upload(上传)|download(下载文件)|public_list(公共文件池浏览)|public_read(读取公共文件)|public_upload(上传到公共池)|public_download(下载公共文件)|public_delete(删除公共文件)|public_mkdir(创建公共池子目录)|public_rename(重命名公共文件)', {
12
- action: z.enum(['list', 'read', 'upload', 'download', 'public_list', 'public_read', 'public_upload', 'public_download', 'public_delete', 'public_mkdir', 'public_rename'])
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');
@@ -91,6 +91,11 @@ export function registerReferenceTools(server) {
91
91
  return wrap('❌ save 需要 title');
92
92
  const existing = await client().getReference(decoded.id);
93
93
  const now = new Date().toISOString();
94
+ // 直接传递原始路径给后端, Rust save_reference 会自动:
95
+ // 1. 检测绝对路径文件
96
+ // 2. 复制到公共文件池/外部参考/{项目名}/
97
+ // 3. 复制到 ref-files/ (供 read_file API 读取)
98
+ // 4. 替换为相对路径保存
94
99
  await client().saveReference({
95
100
  id: decoded.id,
96
101
  title: decoded.title,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ppdocs/mcp",
3
- "version": "3.2.38",
3
+ "version": "3.4.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"
@@ -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
- }
@@ -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
- }