@ppdocs/mcp 2.8.4 → 3.0.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/cli.js CHANGED
@@ -14,19 +14,50 @@ function parseArgs(args) {
14
14
  for (let i = 0; i < args.length; i++) {
15
15
  const arg = args[i];
16
16
  if (arg === '-p' || arg === '--project') {
17
+ if (i + 1 >= args.length || args[i + 1].startsWith('-')) {
18
+ console.error('Error: -p/--project requires a value');
19
+ return null;
20
+ }
17
21
  opts.project = args[++i];
18
22
  }
19
23
  else if (arg === '-k' || arg === '--key') {
24
+ if (i + 1 >= args.length || args[i + 1].startsWith('-')) {
25
+ console.error('Error: -k/--key requires a value');
26
+ return null;
27
+ }
20
28
  opts.key = args[++i];
21
29
  }
22
30
  else if (arg === '-u' || arg === '--user') {
31
+ if (i + 1 >= args.length || args[i + 1].startsWith('-')) {
32
+ console.error('Error: -u/--user requires a value');
33
+ return null;
34
+ }
23
35
  opts.user = args[++i];
24
36
  }
25
37
  else if (arg === '--port') {
26
- opts.port = parseInt(args[++i], 10);
38
+ if (i + 1 >= args.length || args[i + 1].startsWith('-')) {
39
+ console.error('Error: --port requires a value');
40
+ return null;
41
+ }
42
+ const portVal = parseInt(args[++i], 10);
43
+ if (isNaN(portVal) || portVal < 1 || portVal > 65535) {
44
+ console.error('Error: --port must be a valid port number (1-65535)');
45
+ return null;
46
+ }
47
+ opts.port = portVal;
27
48
  }
28
49
  else if (arg === '--api') {
29
- opts.api = args[++i];
50
+ if (i + 1 >= args.length || args[i + 1].startsWith('-')) {
51
+ console.error('Error: --api requires a value');
52
+ return null;
53
+ }
54
+ const apiVal = args[++i].trim();
55
+ if (!apiVal) {
56
+ console.error('Error: --api value cannot be empty');
57
+ return null;
58
+ }
59
+ // 移除用户可能误加的协议前缀
60
+ opts.api = apiVal.replace(/^https?:\/\//, '');
30
61
  }
31
62
  else if (arg === '--codex') {
32
63
  opts.codex = true;
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * API URL 格式: http://localhost:20001/api/:projectId/:password/...
6
6
  */
7
- import type { DocData, DocNode, DocSearchResult, Task, TaskSummary, TaskLogType, TaskExperience } from './types.js';
7
+ import type { DocData, DocNode, DocSearchResult, Task, TaskSummary, TaskLogType, TaskExperience, FileInfo } from './types.js';
8
8
  interface TreeNode {
9
9
  path: string;
10
10
  name: string;
@@ -42,7 +42,7 @@ export declare class PpdocsApiClient {
42
42
  id: string;
43
43
  name: string;
44
44
  description: string;
45
- updated_at: string;
45
+ updatedAt: string;
46
46
  }[]>;
47
47
  /** 跨项目: 列出文档 */
48
48
  crossListDocs(target: string): Promise<DocNode[]>;
@@ -54,6 +54,30 @@ export declare class PpdocsApiClient {
54
54
  crossGetDocsByStatus(target: string, statusList: string[]): Promise<DocNode[]>;
55
55
  /** 跨项目: 获取规则 */
56
56
  crossGetRules(target: string, ruleType: string): Promise<string[]>;
57
+ /** 列出项目文件 */
58
+ listFiles(dir?: string): Promise<FileInfo[]>;
59
+ /** 读取项目文件 */
60
+ readFile(filePath: string): Promise<string>;
61
+ /** 下载项目目录 (zip) → 自动解压到 temp 目录 */
62
+ downloadDir(dir: string): Promise<{
63
+ localPath: string;
64
+ fileCount: number;
65
+ }>;
66
+ /** 上传本地目录到中心服务器 (打包 zip → POST) */
67
+ uploadFiles(localDir: string, remoteDir?: string): Promise<{
68
+ fileCount: number;
69
+ }>;
70
+ /** 清空项目文件存储区 */
71
+ clearFiles(): Promise<void>;
72
+ /** 跨项目: 列出文件 */
73
+ crossListFiles(target: string, dir?: string): Promise<FileInfo[]>;
74
+ /** 跨项目: 读取文件 */
75
+ crossReadFile(target: string, filePath: string): Promise<string>;
76
+ /** 跨项目: 下载目录 */
77
+ crossDownloadDir(target: string, dir: string): Promise<{
78
+ localPath: string;
79
+ fileCount: number;
80
+ }>;
57
81
  }
58
82
  export declare function initClient(apiUrl: string): void;
59
83
  export declare function listDocs(_projectId: string): Promise<DocNode[]>;
@@ -79,10 +103,26 @@ export declare function crossListProjects(): Promise<{
79
103
  id: string;
80
104
  name: string;
81
105
  description: string;
82
- updated_at: string;
106
+ updatedAt: string;
83
107
  }[]>;
84
108
  export declare function crossGetDoc(target: string, docPath: string): Promise<DocData | null>;
85
109
  export declare function crossGetTree(target: string): Promise<TreeNode[]>;
86
110
  export declare function crossGetDocsByStatus(target: string, statusList: string[]): Promise<DocNode[]>;
87
111
  export declare function crossGetRules(target: string, ruleType: string): Promise<string[]>;
112
+ export declare function listFiles(dir?: string): Promise<FileInfo[]>;
113
+ export declare function readFile(filePath: string): Promise<string>;
114
+ export declare function downloadDir(dir: string): Promise<{
115
+ localPath: string;
116
+ fileCount: number;
117
+ }>;
118
+ export declare function crossListFiles(target: string, dir?: string): Promise<FileInfo[]>;
119
+ export declare function crossReadFile(target: string, filePath: string): Promise<string>;
120
+ export declare function crossDownloadDir(target: string, dir: string): Promise<{
121
+ localPath: string;
122
+ fileCount: number;
123
+ }>;
124
+ export declare function uploadFiles(localDir: string, remoteDir?: string): Promise<{
125
+ fileCount: number;
126
+ }>;
127
+ export declare function clearFiles(): Promise<void>;
88
128
  export type { TreeNode };
@@ -4,6 +4,57 @@
4
4
  *
5
5
  * API URL 格式: http://localhost:20001/api/:projectId/:password/...
6
6
  */
7
+ // ============ 工具函数 ============
8
+ /** 清理路径前缀斜杠 */
9
+ function cleanPath(p) {
10
+ return p.replace(/^\//, '');
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
+ /** 下载 zip 并解压到 temp 目录 */
18
+ async function fetchAndExtractZip(url) {
19
+ const controller = new AbortController();
20
+ const timeout = setTimeout(() => controller.abort(), 60000);
21
+ try {
22
+ const response = await fetch(url, { signal: controller.signal });
23
+ if (!response.ok) {
24
+ const error = await response.json().catch(() => ({ error: response.statusText }));
25
+ throw new Error(error.error || `HTTP ${response.status}`);
26
+ }
27
+ const buffer = Buffer.from(await response.arrayBuffer());
28
+ const os = await import('os');
29
+ const path = await import('path');
30
+ const fs = await import('fs');
31
+ const AdmZip = (await import('adm-zip')).default;
32
+ const tempDir = path.join(os.tmpdir(), `ppdocs-files-${Date.now()}`);
33
+ fs.mkdirSync(tempDir, { recursive: true });
34
+ const zip = new AdmZip(buffer);
35
+ zip.extractAllTo(tempDir, true);
36
+ let fileCount = 0;
37
+ const countFiles = (dirPath) => {
38
+ for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
39
+ if (entry.isDirectory())
40
+ countFiles(path.join(dirPath, entry.name));
41
+ else
42
+ fileCount++;
43
+ }
44
+ };
45
+ countFiles(tempDir);
46
+ return { localPath: tempDir, fileCount };
47
+ }
48
+ catch (err) {
49
+ if (err instanceof Error && err.name === 'AbortError') {
50
+ throw new Error('Download timeout (60s)');
51
+ }
52
+ throw err;
53
+ }
54
+ finally {
55
+ clearTimeout(timeout);
56
+ }
57
+ }
7
58
  /** 从扁平文档列表构建目录树 */
8
59
  function buildTree(docs) {
9
60
  const root = [];
@@ -84,16 +135,13 @@ export class PpdocsApiClient {
84
135
  }
85
136
  async getDoc(docPath) {
86
137
  try {
87
- // 移除开头的斜杠,API 路径会自动处理
88
- const cleanPath = docPath.replace(/^\//, '');
89
- return await this.request(`/docs/${cleanPath}`);
138
+ return await this.request(`/docs/${cleanPath(docPath)}`);
90
139
  }
91
140
  catch {
92
141
  return null;
93
142
  }
94
143
  }
95
144
  async createDoc(docPath, doc) {
96
- const cleanPath = docPath.replace(/^\//, '');
97
145
  const payload = {
98
146
  summary: doc.summary || '',
99
147
  content: doc.content || '',
@@ -103,17 +151,15 @@ export class PpdocsApiClient {
103
151
  changes: '初始创建'
104
152
  }],
105
153
  bugfixes: doc.bugfixes || [],
106
- status: doc.status || '已完成' // 默认状态
154
+ status: doc.status || '已完成'
107
155
  };
108
- return this.request(`/docs/${cleanPath}`, {
156
+ return this.request(`/docs/${cleanPath(docPath)}`, {
109
157
  method: 'POST',
110
158
  body: JSON.stringify(payload)
111
159
  });
112
160
  }
113
161
  async updateDoc(docPath, updates) {
114
162
  try {
115
- const cleanPath = docPath.replace(/^\//, '');
116
- // 先获取现有文档
117
163
  const existing = await this.getDoc(docPath);
118
164
  if (!existing)
119
165
  return null;
@@ -124,7 +170,7 @@ export class PpdocsApiClient {
124
170
  bugfixes: updates.bugfixes ?? existing.bugfixes,
125
171
  status: updates.status ?? existing.status ?? '已完成'
126
172
  };
127
- return await this.request(`/docs/${cleanPath}`, {
173
+ return await this.request(`/docs/${cleanPath(docPath)}`, {
128
174
  method: 'PUT',
129
175
  body: JSON.stringify(payload)
130
176
  });
@@ -135,8 +181,7 @@ export class PpdocsApiClient {
135
181
  }
136
182
  async deleteDoc(docPath) {
137
183
  try {
138
- const cleanPath = docPath.replace(/^\//, '');
139
- await this.request(`/docs/${cleanPath}`, { method: 'DELETE' });
184
+ await this.request(`/docs/${cleanPath(docPath)}`, { method: 'DELETE' });
140
185
  return true;
141
186
  }
142
187
  catch {
@@ -244,8 +289,7 @@ export class PpdocsApiClient {
244
289
  /** 跨项目: 获取文档 */
245
290
  async crossGetDoc(target, docPath) {
246
291
  try {
247
- const cleanPath = docPath.replace(/^\//, '');
248
- return await this.request(`/cross/${encodeURIComponent(target)}/docs/${cleanPath}`);
292
+ return await this.request(`/cross/${encodeURIComponent(target)}/docs/${cleanPath(docPath)}`);
249
293
  }
250
294
  catch {
251
295
  return null;
@@ -272,6 +316,87 @@ export class PpdocsApiClient {
272
316
  return [];
273
317
  }
274
318
  }
319
+ // ============ 项目文件访问 ============
320
+ /** 列出项目文件 */
321
+ async listFiles(dir) {
322
+ const query = dir ? `?dir=${encodeURIComponent(dir)}` : '';
323
+ return this.request(`/files${query}`);
324
+ }
325
+ /** 读取项目文件 */
326
+ async readFile(filePath) {
327
+ return this.request(`/files/${cleanPath(filePath)}`);
328
+ }
329
+ /** 下载项目目录 (zip) → 自动解压到 temp 目录 */
330
+ async downloadDir(dir) {
331
+ return fetchAndExtractZip(`${this.baseUrl}/files-download/${cleanPath(dir)}`);
332
+ }
333
+ /** 上传本地目录到中心服务器 (打包 zip → POST) */
334
+ async uploadFiles(localDir, remoteDir) {
335
+ const fs = await import('fs');
336
+ const path = await import('path');
337
+ const AdmZip = (await import('adm-zip')).default;
338
+ if (!fs.existsSync(localDir) || !fs.statSync(localDir).isDirectory()) {
339
+ throw new Error(`本地目录不存在: ${localDir}`);
340
+ }
341
+ const zip = new AdmZip();
342
+ const addDir = (dirPath, zipPath) => {
343
+ for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
344
+ if (entry.name.startsWith('.'))
345
+ continue;
346
+ const fullPath = path.join(dirPath, entry.name);
347
+ if (entry.isDirectory()) {
348
+ if (EXCLUDED_DIRS.has(entry.name))
349
+ continue;
350
+ addDir(fullPath, zipPath ? `${zipPath}/${entry.name}` : entry.name);
351
+ }
352
+ else {
353
+ if (fs.statSync(fullPath).size > 10_485_760)
354
+ continue;
355
+ zip.addLocalFile(fullPath, zipPath || undefined);
356
+ }
357
+ }
358
+ };
359
+ addDir(localDir, '');
360
+ const buffer = zip.toBuffer();
361
+ if (buffer.length > 104_857_600) {
362
+ throw new Error('打包后超过 100MB 限制,请缩小目录范围');
363
+ }
364
+ const query = remoteDir ? `?dir=${encodeURIComponent(remoteDir)}` : '';
365
+ const response = await fetch(`${this.baseUrl}/files${query}`, {
366
+ method: 'POST',
367
+ headers: { 'Content-Type': 'application/octet-stream' },
368
+ body: new Uint8Array(buffer),
369
+ });
370
+ if (!response.ok) {
371
+ const error = await response.json().catch(() => ({ error: response.statusText }));
372
+ throw new Error(error.error || `HTTP ${response.status}`);
373
+ }
374
+ const result = await response.json();
375
+ return { fileCount: result.data?.fileCount || 0 };
376
+ }
377
+ /** 清空项目文件存储区 */
378
+ async clearFiles() {
379
+ const url = `${this.baseUrl}/files`;
380
+ const response = await fetch(url, { method: 'DELETE' });
381
+ if (!response.ok) {
382
+ const error = await response.json().catch(() => ({ error: response.statusText }));
383
+ throw new Error(error.error || `HTTP ${response.status}`);
384
+ }
385
+ }
386
+ // ============ 跨项目文件访问 (只读) ============
387
+ /** 跨项目: 列出文件 */
388
+ async crossListFiles(target, dir) {
389
+ const query = dir ? `?dir=${encodeURIComponent(dir)}` : '';
390
+ return this.request(`/cross/${encodeURIComponent(target)}/files${query}`);
391
+ }
392
+ /** 跨项目: 读取文件 */
393
+ async crossReadFile(target, filePath) {
394
+ return this.request(`/cross/${encodeURIComponent(target)}/files/${cleanPath(filePath)}`);
395
+ }
396
+ /** 跨项目: 下载目录 */
397
+ async crossDownloadDir(target, dir) {
398
+ return fetchAndExtractZip(`${this.baseUrl}/cross/${encodeURIComponent(target)}/files-download/${cleanPath(dir)}`);
399
+ }
275
400
  }
276
401
  // ============ 模块级 API ============
277
402
  let client = null;
@@ -348,3 +473,28 @@ export async function crossGetDocsByStatus(target, statusList) {
348
473
  export async function crossGetRules(target, ruleType) {
349
474
  return getClient().crossGetRules(target, ruleType);
350
475
  }
476
+ // ============ 项目文件访问 ============
477
+ export async function listFiles(dir) {
478
+ return getClient().listFiles(dir);
479
+ }
480
+ export async function readFile(filePath) {
481
+ return getClient().readFile(filePath);
482
+ }
483
+ export async function downloadDir(dir) {
484
+ return getClient().downloadDir(dir);
485
+ }
486
+ export async function crossListFiles(target, dir) {
487
+ return getClient().crossListFiles(target, dir);
488
+ }
489
+ export async function crossReadFile(target, filePath) {
490
+ return getClient().crossReadFile(target, filePath);
491
+ }
492
+ export async function crossDownloadDir(target, dir) {
493
+ return getClient().crossDownloadDir(target, dir);
494
+ }
495
+ export async function uploadFiles(localDir, remoteDir) {
496
+ return getClient().uploadFiles(localDir, remoteDir);
497
+ }
498
+ export async function clearFiles() {
499
+ return getClient().clearFiles();
500
+ }
@@ -44,6 +44,15 @@ export interface Project {
44
44
  name: string;
45
45
  description?: string;
46
46
  updatedAt: string;
47
+ createdAt?: string;
48
+ projectPath?: string;
49
+ }
50
+ export interface FileInfo {
51
+ name: string;
52
+ path: string;
53
+ isDir: boolean;
54
+ size: number;
55
+ modifiedAt?: string;
47
56
  }
48
57
  export type TaskLogType = 'progress' | 'issue' | 'solution' | 'reference';
49
58
  export interface TaskLog {
@@ -30,4 +30,8 @@ export declare function formatTreeText(tree: TreeNode[]): string;
30
30
  * 统计树中的文档数
31
31
  */
32
32
  export declare function countTreeDocs(tree: TreeNode[]): number;
33
+ /**
34
+ * 格式化文件大小
35
+ */
36
+ export declare function formatFileSize(bytes: number): string;
33
37
  export {};
@@ -81,3 +81,14 @@ export function countTreeDocs(tree) {
81
81
  traverse(tree);
82
82
  return count;
83
83
  }
84
+ // ==================== 文件大小格式化 ====================
85
+ /**
86
+ * 格式化文件大小
87
+ */
88
+ export function formatFileSize(bytes) {
89
+ if (bytes < 1024)
90
+ return `${bytes} B`;
91
+ if (bytes < 1024 * 1024)
92
+ return `${(bytes / 1024).toFixed(1)} KB`;
93
+ return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
94
+ }
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import * as storage from '../storage/httpClient.js';
3
3
  import { decodeObjectStrings, getRules, RULE_TYPE_LABELS } from '../utils.js';
4
- import { wrap, formatDocMarkdown, formatTreeText, countTreeDocs } from './helpers.js';
4
+ import { wrap, formatDocMarkdown, formatTreeText, countTreeDocs, formatFileSize } from './helpers.js';
5
5
  export function registerTools(server, projectId, _user) {
6
6
  // ===================== 跨项目访问 =====================
7
7
  // 0. 列出所有可访问的项目
@@ -393,4 +393,85 @@ export function registerTools(server, projectId, _user) {
393
393
  }
394
394
  return wrap(`✅ 任务已归档: ${task.title}`);
395
395
  });
396
+ // ===================== 项目文件访问 =====================
397
+ // 上传本地目录到中心服务器
398
+ server.tool('project_upload', '上传本地项目目录到中心服务器(打包zip上传,自动排除node_modules/.git等)。上传后其他客户端可通过跨项目访问下载', {
399
+ localDir: z.string().describe('本地目录的绝对路径(如"/home/user/project/src")'),
400
+ remoteDir: z.string().optional().describe('上传到服务器的目标子目录(如"src"),不填则上传到根目录'),
401
+ }, async (args) => {
402
+ try {
403
+ const result = await storage.uploadFiles(args.localDir, args.remoteDir);
404
+ return wrap(`✅ 上传成功\n\n- 文件数量: ${result.fileCount}\n- 目标目录: ${args.remoteDir || '/'}\n\n其他客户端可通过 project_read_file / project_download_dir 访问`);
405
+ }
406
+ catch (e) {
407
+ return wrap(`❌ ${String(e)}`);
408
+ }
409
+ });
410
+ // 清空项目文件存储区
411
+ server.tool('project_clear_files', '清空当前项目在中心服务器上的文件存储区', {}, async () => {
412
+ try {
413
+ await storage.clearFiles();
414
+ return wrap('✅ 文件存储已清空');
415
+ }
416
+ catch (e) {
417
+ return wrap(`❌ ${String(e)}`);
418
+ }
419
+ });
420
+ // 列出项目文件
421
+ server.tool('project_list_files', '列出项目源码目录下的文件。需要先上传文件或项目已关联源码目录', {
422
+ dir: z.string().optional().describe('子目录路径(如"src/components"),不填则列出根目录'),
423
+ targetProject: z.string().optional().describe('目标项目ID或名称(不填=当前项目,跨项目只读)')
424
+ }, async (args) => {
425
+ try {
426
+ const files = args.targetProject
427
+ ? await storage.crossListFiles(args.targetProject, args.dir)
428
+ : await storage.listFiles(args.dir);
429
+ if (files.length === 0) {
430
+ return wrap('目录为空');
431
+ }
432
+ const lines = files.map(f => {
433
+ const icon = f.isDir ? '📁' : '📄';
434
+ const size = f.isDir ? '' : ` (${formatFileSize(f.size)})`;
435
+ return `${icon} ${f.name}${size}`;
436
+ });
437
+ const prefix = args.targetProject ? `[跨项目: ${args.targetProject}]\n\n` : '';
438
+ const dirLabel = args.dir || '/';
439
+ return wrap(`${prefix}📂 ${dirLabel} (${files.length} 项)\n\n${lines.join('\n')}`);
440
+ }
441
+ catch (e) {
442
+ return wrap(`❌ ${String(e)}`);
443
+ }
444
+ });
445
+ // 读取项目文件
446
+ server.tool('project_read_file', '读取项目源码文件内容(文本,限制1MB)。需要先上传文件或项目已关联源码目录', {
447
+ path: z.string().describe('文件路径(如"src/main.ts")'),
448
+ targetProject: z.string().optional().describe('目标项目ID或名称(不填=当前项目,跨项目只读)')
449
+ }, async (args) => {
450
+ try {
451
+ const content = args.targetProject
452
+ ? await storage.crossReadFile(args.targetProject, args.path)
453
+ : await storage.readFile(args.path);
454
+ const prefix = args.targetProject ? `[跨项目: ${args.targetProject}]\n\n` : '';
455
+ return wrap(`${prefix}📄 ${args.path}\n\n\`\`\`\n${content}\n\`\`\``);
456
+ }
457
+ catch (e) {
458
+ return wrap(`❌ ${String(e)}`);
459
+ }
460
+ });
461
+ // 下载项目目录 (zip → 自动解压)
462
+ server.tool('project_download_dir', '下载项目源码目录(自动打包zip并解压到本地临时目录)。需要先上传文件或项目已关联源码目录', {
463
+ dir: z.string().describe('目录路径(如"src")'),
464
+ targetProject: z.string().optional().describe('目标项目ID或名称(不填=当前项目,跨项目只读)')
465
+ }, async (args) => {
466
+ try {
467
+ const result = args.targetProject
468
+ ? await storage.crossDownloadDir(args.targetProject, args.dir)
469
+ : await storage.downloadDir(args.dir);
470
+ const prefix = args.targetProject ? `[跨项目: ${args.targetProject}]\n\n` : '';
471
+ return wrap(`${prefix}✅ 目录已下载并解压\n\n- 本地路径: ${result.localPath}\n- 文件数量: ${result.fileCount}\n\n可直接读取该路径下的文件`);
472
+ }
473
+ catch (e) {
474
+ return wrap(`❌ ${String(e)}`);
475
+ }
476
+ });
396
477
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ppdocs/mcp",
3
- "version": "2.8.4",
3
+ "version": "3.0.0",
4
4
  "description": "ppdocs MCP Server - Knowledge Graph for Claude",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -30,12 +30,12 @@
30
30
  ],
31
31
  "dependencies": {
32
32
  "@modelcontextprotocol/sdk": "^1.0.0",
33
- "proper-lockfile": "^4.1.2",
33
+ "adm-zip": "^0.5.16",
34
34
  "zod": "^4.1.13"
35
35
  },
36
36
  "devDependencies": {
37
+ "@types/adm-zip": "^0.5.7",
37
38
  "@types/node": "^22.0.0",
38
- "@types/proper-lockfile": "^4.1.4",
39
39
  "typescript": "^5.7.0"
40
40
  }
41
41
  }