@zzp123/mcp-zentao 1.3.0 → 1.3.2
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/api/zentaoApi.d.ts +1 -2
- package/dist/api/zentaoApi.js +0 -17
- package/dist/index.js +86 -52
- package/dist/types/zentao.d.ts +0 -6
- package/package.json +1 -1
- package/scripts/get-clipboard-base64.js +93 -0
package/dist/api/zentaoApi.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AssignFeedbackRequest, Bug,
|
|
1
|
+
import { AssignFeedbackRequest, Bug, BugStatus, Build, CloseFeedbackRequest, CreateBuildRequest, CreateBugRequest, CreateExecutionRequest, CreateFeedbackRequest, CreatePlanRequest, CreateProductRequest, CreateProgramRequest, CreateProjectRequest, CreateStoryRequest, CreateTaskRequest, CreateTestCaseRequest, CreateTicketRequest, CreateUserRequest, Execution, Feedback, FileUploadResponse, Module, ModuleType, Plan, Product, Program, Project, Release, Story, Task, TaskStatus, TaskUpdate, TestCase, Ticket, UpdateBuildRequest, UpdateBugRequest, UpdateExecutionRequest, UpdateFeedbackRequest, UpdatePlanRequest, UpdateProductRequest, UpdateProgramRequest, UpdateProjectRequest, UpdateStoryRequest, UpdateTestCaseRequest, UpdateTicketRequest, UpdateUserRequest, UploadFileRequest, UserDetail, ZentaoConfig } from '../types/zentao';
|
|
2
2
|
export declare class ZentaoAPI {
|
|
3
3
|
private config;
|
|
4
4
|
private client;
|
|
@@ -13,7 +13,6 @@ export declare class ZentaoAPI {
|
|
|
13
13
|
getBugDetail(bugId: number): Promise<Bug>;
|
|
14
14
|
updateTask(taskId: number, update: TaskUpdate): Promise<Task>;
|
|
15
15
|
finishTask(taskId: number, update?: TaskUpdate): Promise<Task>;
|
|
16
|
-
resolveBug(bugId: number, resolution: BugResolution): Promise<Bug>;
|
|
17
16
|
createTask(task: CreateTaskRequest): Promise<Task>;
|
|
18
17
|
getPrograms(order?: string): Promise<Program[]>;
|
|
19
18
|
getProductPlans(productId: number, branch?: number, begin?: string, end?: string, status?: string, parent?: number): Promise<Plan[]>;
|
package/dist/api/zentaoApi.js
CHANGED
|
@@ -188,23 +188,6 @@ export class ZentaoAPI {
|
|
|
188
188
|
throw error;
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
|
-
async resolveBug(bugId, resolution) {
|
|
192
|
-
try {
|
|
193
|
-
console.log(`正在解决Bug ${bugId}...`);
|
|
194
|
-
const response = await this.request('PUT', `/bugs/${bugId}`, undefined, {
|
|
195
|
-
status: 'resolved',
|
|
196
|
-
assignedTo: this.config.username,
|
|
197
|
-
...resolution,
|
|
198
|
-
resolvedDate: new Date().toISOString(),
|
|
199
|
-
});
|
|
200
|
-
console.log('Bug解决响应:', response);
|
|
201
|
-
return response;
|
|
202
|
-
}
|
|
203
|
-
catch (error) {
|
|
204
|
-
console.error('解决Bug失败:', error);
|
|
205
|
-
throw error;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
191
|
async createTask(task) {
|
|
209
192
|
try {
|
|
210
193
|
console.log('正在创建新任务...');
|
package/dist/index.js
CHANGED
|
@@ -143,23 +143,6 @@ server.tool("finishTask", {
|
|
|
143
143
|
content: [{ type: "text", text: JSON.stringify(task, null, 2) }]
|
|
144
144
|
};
|
|
145
145
|
});
|
|
146
|
-
// Add resolveBug tool
|
|
147
|
-
server.tool("resolveBug", {
|
|
148
|
-
bugId: z.number(),
|
|
149
|
-
resolution: z.object({
|
|
150
|
-
resolution: z.enum(['fixed', 'notrepro', 'duplicate', 'bydesign', 'willnotfix', 'tostory', 'external']),
|
|
151
|
-
resolvedBuild: z.string().optional(),
|
|
152
|
-
duplicateBug: z.number().optional(),
|
|
153
|
-
comment: z.string().optional()
|
|
154
|
-
})
|
|
155
|
-
}, async ({ bugId, resolution }) => {
|
|
156
|
-
if (!zentaoApi)
|
|
157
|
-
throw new Error("Please initialize Zentao API first");
|
|
158
|
-
const bug = await zentaoApi.resolveBug(bugId, resolution);
|
|
159
|
-
return {
|
|
160
|
-
content: [{ type: "text", text: JSON.stringify(bug, null, 2) }]
|
|
161
|
-
};
|
|
162
|
-
});
|
|
163
146
|
// Add getPrograms tool
|
|
164
147
|
server.tool("getPrograms", {
|
|
165
148
|
order: z.string().optional()
|
|
@@ -990,49 +973,100 @@ server.tool("uploadFile", {
|
|
|
990
973
|
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
991
974
|
});
|
|
992
975
|
server.tool("uploadImageFromClipboard", {
|
|
993
|
-
base64Data: z.string(),
|
|
994
976
|
filename: z.string().optional(),
|
|
995
977
|
uid: z.string().optional()
|
|
996
|
-
}, async ({
|
|
978
|
+
}, async ({ filename, uid }) => {
|
|
997
979
|
if (!zentaoApi)
|
|
998
980
|
throw new Error("Please initialize Zentao API first");
|
|
999
981
|
const fs = await import('fs');
|
|
1000
982
|
const path = await import('path');
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
983
|
+
const { execSync } = await import('child_process');
|
|
984
|
+
try {
|
|
985
|
+
// 读取系统剪贴板图片
|
|
986
|
+
let base64Data;
|
|
987
|
+
let fileBuffer;
|
|
988
|
+
let finalFilename;
|
|
989
|
+
// Windows: 使用 PowerShell 读取剪贴板
|
|
990
|
+
if (process.platform === 'win32') {
|
|
991
|
+
const psScript = `
|
|
992
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
993
|
+
$img = [System.Windows.Forms.Clipboard]::GetImage()
|
|
994
|
+
if ($img -ne $null) {
|
|
995
|
+
$ms = New-Object System.IO.MemoryStream
|
|
996
|
+
$img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png)
|
|
997
|
+
[Convert]::ToBase64String($ms.ToArray())
|
|
998
|
+
} else {
|
|
999
|
+
Write-Error "NoImage"
|
|
1000
|
+
}
|
|
1001
|
+
`;
|
|
1002
|
+
const result = execSync(`powershell -Command "${psScript.replace(/\n/g, ' ')}"`, {
|
|
1003
|
+
encoding: 'utf8',
|
|
1004
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1005
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
1006
|
+
}).trim();
|
|
1007
|
+
if (!result || result.includes('NoImage') || result.includes('Error')) {
|
|
1008
|
+
throw new Error('剪贴板中没有图片。请先复制图片,不要粘贴到输入框,直接告诉我"上传"。');
|
|
1009
|
+
}
|
|
1010
|
+
base64Data = result;
|
|
1011
|
+
fileBuffer = Buffer.from(base64Data, 'base64');
|
|
1012
|
+
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
1013
|
+
}
|
|
1014
|
+
// macOS: 使用 pngpaste
|
|
1015
|
+
else if (process.platform === 'darwin') {
|
|
1016
|
+
const tempFile = path.join(process.cwd(), '.temp_clipboard.png');
|
|
1017
|
+
try {
|
|
1018
|
+
execSync(`pngpaste "${tempFile}"`);
|
|
1019
|
+
fileBuffer = fs.readFileSync(tempFile);
|
|
1020
|
+
fs.unlinkSync(tempFile);
|
|
1021
|
+
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
1022
|
+
}
|
|
1023
|
+
catch {
|
|
1024
|
+
throw new Error('剪贴板中没有图片。请先安装 pngpaste: brew install pngpaste');
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
// Linux: 使用 xclip
|
|
1028
|
+
else {
|
|
1029
|
+
try {
|
|
1030
|
+
const buffer = execSync('xclip -selection clipboard -t image/png -o', {
|
|
1031
|
+
maxBuffer: 10 * 1024 * 1024
|
|
1032
|
+
});
|
|
1033
|
+
fileBuffer = buffer;
|
|
1034
|
+
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
1035
|
+
}
|
|
1036
|
+
catch {
|
|
1037
|
+
throw new Error('剪贴板中没有图片。请先安装 xclip: sudo apt-get install xclip');
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
// 创建 img 文件夹
|
|
1041
|
+
const imgDir = path.join(process.cwd(), 'img');
|
|
1042
|
+
if (!fs.existsSync(imgDir)) {
|
|
1043
|
+
fs.mkdirSync(imgDir, { recursive: true });
|
|
1044
|
+
}
|
|
1045
|
+
// 保存到 img 文件夹
|
|
1046
|
+
const savedPath = path.join(imgDir, finalFilename);
|
|
1047
|
+
fs.writeFileSync(savedPath, fileBuffer);
|
|
1048
|
+
// 上传到禅道
|
|
1049
|
+
const result = await zentaoApi.uploadFile({
|
|
1050
|
+
file: fileBuffer,
|
|
1051
|
+
filename: finalFilename,
|
|
1052
|
+
uid
|
|
1053
|
+
});
|
|
1054
|
+
const response = {
|
|
1055
|
+
upload: result,
|
|
1056
|
+
savedPath: savedPath,
|
|
1057
|
+
message: `图片已保存到本地并上传到禅道`
|
|
1058
|
+
};
|
|
1059
|
+
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
1015
1060
|
}
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1061
|
+
catch (error) {
|
|
1062
|
+
return {
|
|
1063
|
+
content: [{
|
|
1064
|
+
type: "text",
|
|
1065
|
+
text: `错误: ${error.message}\n\n提示:\n1. 确保已复制图片到剪贴板\n2. 不要粘贴到输入框,直接说"上传剪贴板图片"\n3. 或使用命令行: upload.bat`
|
|
1066
|
+
}],
|
|
1067
|
+
isError: true
|
|
1068
|
+
};
|
|
1020
1069
|
}
|
|
1021
|
-
// 保存到 img 文件夹
|
|
1022
|
-
const savedPath = path.join(imgDir, finalFilename);
|
|
1023
|
-
fs.writeFileSync(savedPath, fileBuffer);
|
|
1024
|
-
// 上传到禅道
|
|
1025
|
-
const result = await zentaoApi.uploadFile({
|
|
1026
|
-
file: fileBuffer,
|
|
1027
|
-
filename: finalFilename,
|
|
1028
|
-
uid
|
|
1029
|
-
});
|
|
1030
|
-
const response = {
|
|
1031
|
-
upload: result,
|
|
1032
|
-
savedPath: savedPath,
|
|
1033
|
-
message: `图片已保存到本地并上传到禅道`
|
|
1034
|
-
};
|
|
1035
|
-
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
1036
1070
|
});
|
|
1037
1071
|
server.tool("downloadFile", {
|
|
1038
1072
|
fileId: z.number(),
|
package/dist/types/zentao.d.ts
CHANGED
|
@@ -48,12 +48,6 @@ export interface TaskUpdate {
|
|
|
48
48
|
finishedDate?: string;
|
|
49
49
|
comment?: string;
|
|
50
50
|
}
|
|
51
|
-
export interface BugResolution {
|
|
52
|
-
resolution: 'fixed' | 'notrepro' | 'duplicate' | 'bydesign' | 'willnotfix' | 'tostory' | 'external';
|
|
53
|
-
resolvedBuild?: string;
|
|
54
|
-
duplicateBug?: number;
|
|
55
|
-
comment?: string;
|
|
56
|
-
}
|
|
57
51
|
export type TaskStatus = 'wait' | 'doing' | 'done' | 'all';
|
|
58
52
|
export type BugStatus = 'active' | 'resolved' | 'closed' | 'all';
|
|
59
53
|
export interface User {
|
package/package.json
CHANGED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 获取剪贴板图片的 base64 编码
|
|
5
|
+
* 可以在 Claude Code 中使用 Bash 工具调用此脚本
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import { dirname, join } from 'path';
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = dirname(__filename);
|
|
15
|
+
|
|
16
|
+
async function getClipboardBase64() {
|
|
17
|
+
try {
|
|
18
|
+
let base64Data;
|
|
19
|
+
|
|
20
|
+
// Windows: 使用 PowerShell
|
|
21
|
+
if (process.platform === 'win32') {
|
|
22
|
+
const psScript = `
|
|
23
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
24
|
+
$img = [System.Windows.Forms.Clipboard]::GetImage()
|
|
25
|
+
if ($img -ne $null) {
|
|
26
|
+
$ms = New-Object System.IO.MemoryStream
|
|
27
|
+
$img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png)
|
|
28
|
+
[Convert]::ToBase64String($ms.ToArray())
|
|
29
|
+
} else {
|
|
30
|
+
Write-Error "No image in clipboard"
|
|
31
|
+
exit 1
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
base64Data = execSync(`powershell -Command "${psScript.replace(/\n/g, ' ')}"`, {
|
|
37
|
+
encoding: 'utf8',
|
|
38
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
39
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
40
|
+
}).trim();
|
|
41
|
+
|
|
42
|
+
if (!base64Data || base64Data.includes('Error')) {
|
|
43
|
+
console.error('ERROR: No image in clipboard');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 输出完整的 data URL
|
|
48
|
+
console.log(`data:image/png;base64,${base64Data}`);
|
|
49
|
+
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('ERROR: Failed to read clipboard image');
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// macOS: 使用 pngpaste
|
|
56
|
+
else if (process.platform === 'darwin') {
|
|
57
|
+
try {
|
|
58
|
+
execSync('which pngpaste', { stdio: 'ignore' });
|
|
59
|
+
const tempFile = join(__dirname, '.temp_clipboard.png');
|
|
60
|
+
execSync(`pngpaste "${tempFile}"`);
|
|
61
|
+
const buffer = fs.readFileSync(tempFile);
|
|
62
|
+
base64Data = buffer.toString('base64');
|
|
63
|
+
fs.unlinkSync(tempFile);
|
|
64
|
+
console.log(`data:image/png;base64,${base64Data}`);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error('ERROR: No image in clipboard or pngpaste not installed');
|
|
67
|
+
console.error('Install: brew install pngpaste');
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Linux: 使用 xclip
|
|
72
|
+
else {
|
|
73
|
+
try {
|
|
74
|
+
execSync('which xclip', { stdio: 'ignore' });
|
|
75
|
+
const buffer = execSync('xclip -selection clipboard -t image/png -o', {
|
|
76
|
+
maxBuffer: 10 * 1024 * 1024
|
|
77
|
+
});
|
|
78
|
+
base64Data = buffer.toString('base64');
|
|
79
|
+
console.log(`data:image/png;base64,${base64Data}`);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error('ERROR: No image in clipboard or xclip not installed');
|
|
82
|
+
console.error('Install: sudo apt-get install xclip');
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error('ERROR:', error.message);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getClipboardBase64();
|