@zzp123/mcp-zentao 1.18.9 → 1.18.10

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.
@@ -1,209 +1,212 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
-
4
- // 角色工具配置
5
- const TOOL_CATEGORIES = {
6
- init: ['initZentao'],
7
- story: ['createStory', 'getStoryDetail', 'changeStory', 'deleteStory', 'getProductStories', 'addStoryComment'],
8
- storyReadonly: ['getStoryDetail', 'getProductStories'],
9
- product: ['getProducts', 'getProductDetail', 'createProduct', 'updateProduct', 'deleteProduct'],
10
- plan: ['getProductPlans', 'getPlanDetail', 'createPlan', 'updatePlan', 'deletePlan', 'linkStoriesToPlan', 'unlinkStoriesFromPlan', 'linkBugsToPlan', 'unlinkBugsFromPlan'],
11
- program: ['getPrograms', 'getProgramDetail', 'createProgram', 'updateProgram', 'deleteProgram'],
12
- feedback: ['getFeedbacks', 'getFeedbackDetail', 'createFeedback', 'updateFeedback', 'deleteFeedback', 'assignFeedback', 'closeFeedback'],
13
- bug: ['getMyBugs', 'getProductBugs', 'getBugDetail', 'createBug', 'updateBug', 'deleteBug', 'resolveBug', 'addBugComment'],
14
- bugResolve: ['getMyBugs', 'getBugDetail', 'resolveBug', 'addBugComment'],
15
- bugReadonly: ['getMyBugs', 'getProductBugs', 'getBugDetail'],
16
- testcase: ['getProductTestCases', 'getTestCaseDetail', 'createTestCase', 'updateTestCase', 'deleteTestCase'],
17
- task: ['getMyTasks', 'getTaskDetail', 'createTask', 'updateTask', 'finishTask', 'deleteTask', 'addTaskComment'],
18
- taskReadonly: ['getMyTasks', 'getTaskDetail'],
19
- project: ['getProjects', 'getProjectDetail', 'createProject', 'updateProject', 'deleteProject', 'getProjectReleases', 'getProjectExecutions', 'getProjectBuilds'],
20
- execution: ['getExecutionDetail', 'createExecution', 'updateExecution', 'deleteExecution', 'getExecutionBuilds'],
21
- build: ['getBuildDetail', 'createBuild', 'updateBuild', 'deleteBuild'],
22
- ticket: ['getTickets', 'getTicketDetail', 'createTicket', 'updateTicket', 'deleteTicket'],
23
- comment: ['getComments', 'getCommentDetail', 'addComment', 'updateComment', 'deleteComment'],
24
- utility: ['getModules', 'uploadFile', 'uploadImageFromClipboard', 'downloadFile'],
25
- user: ['getUsers', 'getUserDetail', 'getMyProfile', 'createUser', 'updateUser', 'deleteUser']
26
- };
27
-
28
- const ROLE_PERMISSIONS = {
29
- pm: [
30
- ...TOOL_CATEGORIES.init,
31
- ...TOOL_CATEGORIES.story,
32
- ...TOOL_CATEGORIES.product,
33
- ...TOOL_CATEGORIES.plan,
34
- ...TOOL_CATEGORIES.program,
35
- ...TOOL_CATEGORIES.bugReadonly,
36
- ...TOOL_CATEGORIES.taskReadonly,
37
- ...TOOL_CATEGORIES.comment,
38
- ...TOOL_CATEGORIES.utility
39
- ],
40
- qa: [
41
- ...TOOL_CATEGORIES.init,
42
- ...TOOL_CATEGORIES.bug,
43
- ...TOOL_CATEGORIES.testcase,
44
- ...TOOL_CATEGORIES.storyReadonly,
45
- ...TOOL_CATEGORIES.taskReadonly,
46
- ...TOOL_CATEGORIES.comment,
47
- ...TOOL_CATEGORIES.utility
48
- ],
49
- dev: [
50
- ...TOOL_CATEGORIES.init,
51
- ...TOOL_CATEGORIES.task,
52
- ...TOOL_CATEGORIES.bugResolve,
53
- ...TOOL_CATEGORIES.project,
54
- ...TOOL_CATEGORIES.execution,
55
- ...TOOL_CATEGORIES.build,
56
- ...TOOL_CATEGORIES.storyReadonly,
57
- ...TOOL_CATEGORIES.comment,
58
- ...TOOL_CATEGORIES.utility
59
- ]
60
- };
61
-
62
- const ROLE_NAMES = {
63
- pm: '产品经理',
64
- qa: '测试工程师',
65
- dev: '开发工程师'
66
- };
67
-
68
- // 读取完整的 index.ts
69
- const indexPath = path.join(__dirname, '..', 'src', 'index.ts');
70
- const indexContent = fs.readFileSync(indexPath, 'utf8');
71
-
72
- // 提取工具注册块的函数
73
- function extractToolBlocks(content) {
74
- const lines = content.split('\n');
75
- const blocks = [];
76
- let currentBlock = null;
77
- let braceDepth = 0;
78
- let parenDepth = 0;
79
-
80
- for (let i = 0; i < lines.length; i++) {
81
- const line = lines[i];
82
- const trimmed = line.trim();
83
-
84
- // 检测 server.tool 的开始
85
- if (trimmed.startsWith('server.tool(')) {
86
- // 提取工具名
87
- const match = trimmed.match(/server\.tool\("([^"]+)"/);
88
- if (match) {
89
- currentBlock = {
90
- toolName: match[1],
91
- startLine: i,
92
- lines: [line],
93
- braceDepth: 0,
94
- parenDepth: 0
95
- };
96
-
97
- // 计算当前行的括号和大括号
98
- currentBlock.parenDepth += (line.match(/\(/g) || []).length - (line.match(/\)/g) || []).length;
99
- currentBlock.braceDepth += (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length;
100
- }
101
- } else if (currentBlock) {
102
- // 在工具块内
103
- currentBlock.lines.push(line);
104
- currentBlock.parenDepth += (line.match(/\(/g) || []).length - (line.match(/\)/g) || []).length;
105
- currentBlock.braceDepth += (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length;
106
-
107
- // 检查是否到达块的结束(圆括号和大括号都平衡,且以 ); 结尾)
108
- if (currentBlock.parenDepth === 0 && currentBlock.braceDepth === 0 && trimmed.endsWith(');')) {
109
- currentBlock.endLine = i;
110
- blocks.push(currentBlock);
111
- currentBlock = null;
112
- }
113
- }
114
- }
115
-
116
- return blocks;
117
- }
118
-
119
- // 为每个角色生成文件
120
- ['pm', 'qa', 'dev'].forEach(role => {
121
- console.log(`\n正在生成 ${ROLE_NAMES[role]} (${role}) 版本...`);
122
-
123
- const allowedTools = new Set(ROLE_PERMISSIONS[role]);
124
- console.log(` 允许的工具: ${Array.from(allowedTools).join(', ')}`);
125
-
126
- // 提取所有工具块
127
- const toolBlocks = extractToolBlocks(indexContent);
128
- console.log(` 总工具数: ${toolBlocks.length}`);
129
-
130
- // 过滤出允许的工具
131
- const allowedBlocks = toolBlocks.filter(block => allowedTools.has(block.toolName));
132
- console.log(` ${ROLE_NAMES[role]}可用工具数: ${allowedBlocks.length}`);
133
-
134
- // 构建新文件内容
135
- const lines = indexContent.split('\n');
136
- const outputLines = [];
137
- const includedLines = new Set();
138
-
139
- // 标记所有要包含的行
140
- allowedBlocks.forEach(block => {
141
- for (let i = block.startLine; i <= block.endLine; i++) {
142
- includedLines.add(i);
143
- }
144
- // 如果前一行是注释,也包含进来
145
- if (block.startLine > 0 && lines[block.startLine - 1].trim().startsWith('//')) {
146
- includedLines.add(block.startLine - 1);
147
- }
148
- });
149
-
150
- // 找到第一个 server.tool 的位置
151
- let firstToolLine = toolBlocks.length > 0 ? toolBlocks[0].startLine : lines.length;
152
-
153
- // 输出文件头部(到第一个工具之前)
154
- for (let i = 0; i < firstToolLine; i++) {
155
- outputLines.push(lines[i]);
156
- }
157
-
158
- // 更新服务器名称和版本
159
- const headerContent = outputLines.join('\n');
160
- let updatedHeader = headerContent
161
- .replace(/name: "Zentao API"/, `name: "Zentao API (${ROLE_NAMES[role]})"`)
162
- .replace(/version: "1\.16\.0"/, `version: "1.17.0"`);
163
-
164
- // 输出更新后的头部
165
- const finalLines = updatedHeader.split('\n');
166
-
167
- // 添加所有允许的工具块
168
- let lastEndLine = firstToolLine - 1;
169
- allowedBlocks.forEach((block, index) => {
170
- // 添加空行分隔(如果需要)
171
- if (block.startLine > lastEndLine + 1) {
172
- finalLines.push('');
173
- }
174
-
175
- // 添加工具块
176
- for (let i = block.startLine; i <= block.endLine; i++) {
177
- finalLines.push(lines[i]);
178
- }
179
-
180
- lastEndLine = block.endLine;
181
- });
182
-
183
- // 添加文件尾部(服务器启动代码)
184
- const lastToolBlock = toolBlocks[toolBlocks.length - 1];
185
- if (lastToolBlock) {
186
- for (let i = lastToolBlock.endLine + 1; i < lines.length; i++) {
187
- finalLines.push(lines[i]);
188
- }
189
- }
190
-
191
- // 写入文件
192
- const output = finalLines.join('\n');
193
- const outputPath = path.join(__dirname, '..', 'src', `index-${role}.ts`);
194
- fs.writeFileSync(outputPath, output, 'utf8');
195
-
196
- console.log(` ✓ 已生成: ${outputPath}`);
197
-
198
- // 统计文件大小
199
- const stats = fs.statSync(outputPath);
200
- const originalStats = fs.statSync(indexPath);
201
- const reduction = ((1 - stats.size / originalStats.size) * 100).toFixed(1);
202
- console.log(` 文件大小: ${(stats.size / 1024).toFixed(1)} KB (原始: ${(originalStats.size / 1024).toFixed(1)} KB, 减少 ${reduction}%)`);
203
- });
204
-
205
- console.log('\n✓ 所有角色版本生成完成!');
206
- console.log('\n下一步:');
207
- console.log('1. 运行 npx tsc 编译所有版本');
208
- console.log('2. 配置 package.json 的构建脚本');
209
- console.log('3. 测试各个版本');
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ // 角色工具配置
5
+ const TOOL_CATEGORIES = {
6
+ init: ['initZentao'],
7
+ story: ['createStory', 'getStoryDetail', 'changeStory', 'deleteStory', 'getProductStories', 'addStoryComment'],
8
+ storyReadonly: ['getStoryDetail', 'getProductStories'],
9
+ product: ['getProducts', 'getProductDetail', 'createProduct', 'updateProduct', 'deleteProduct'],
10
+ plan: ['getProductPlans', 'getPlanDetail', 'createPlan', 'updatePlan', 'deletePlan', 'linkStoriesToPlan', 'unlinkStoriesFromPlan', 'linkBugsToPlan', 'unlinkBugsFromPlan'],
11
+ program: ['getPrograms', 'getProgramDetail', 'createProgram', 'updateProgram', 'deleteProgram'],
12
+ feedback: ['getFeedbacks', 'getFeedbackDetail', 'createFeedback', 'updateFeedback', 'deleteFeedback', 'assignFeedback', 'closeFeedback'],
13
+ bug: ['getMyBugs', 'getProductBugs', 'getBugDetail', 'createBug', 'updateBug', 'deleteBug', 'resolveBug', 'addBugComment'],
14
+ bugResolve: ['getMyBugs', 'getBugDetail', 'resolveBug', 'addBugComment'],
15
+ bugReadonly: ['getMyBugs', 'getProductBugs', 'getBugDetail'],
16
+ testcase: ['getProductTestCases', 'getTestCaseDetail', 'createTestCase', 'updateTestCase', 'deleteTestCase'],
17
+ task: ['getMyTasks', 'getTaskDetail', 'createTask', 'updateTask', 'finishTask', 'deleteTask', 'addTaskComment'],
18
+ taskReadonly: ['getMyTasks', 'getTaskDetail'],
19
+ project: ['getProjects', 'getProjectDetail', 'createProject', 'updateProject', 'deleteProject', 'getProjectReleases', 'getProjectExecutions', 'getProjectBuilds'],
20
+ execution: ['getExecutionDetail', 'createExecution', 'updateExecution', 'deleteExecution', 'getExecutionBuilds'],
21
+ build: ['getBuildDetail', 'createBuild', 'updateBuild', 'deleteBuild'],
22
+ ticket: ['getTickets', 'getTicketDetail', 'createTicket', 'updateTicket', 'deleteTicket'],
23
+ comment: ['getComments', 'getCommentDetail', 'addComment', 'updateComment', 'deleteComment'],
24
+ utility: ['getModules', 'uploadFile', 'uploadImageFromClipboard', 'downloadFile'],
25
+ user: ['getUsers', 'getUserDetail', 'getMyProfile', 'createUser', 'updateUser', 'deleteUser']
26
+ };
27
+
28
+ const ROLE_PERMISSIONS = {
29
+ pm: [
30
+ ...TOOL_CATEGORIES.init,
31
+ 'getProjects', // 获取项目列表
32
+ 'createStory', // 添加需求功能
33
+ 'getStoryDetail', // 获取需求详情
34
+ 'changeStory', // 变更需求
35
+ 'deleteStory', // 删除需求
36
+ 'getProjectExecutions', // 获取执行列表
37
+ 'getProductPlans', // 获取产品计划列表
38
+ 'linkStoriesToPlan', // 产品计划关联需求
39
+ 'createExecution', // 创建执行
40
+ 'deleteExecution', // 删除执行
41
+ 'deletePlan' // 删除产品计划
42
+ ],
43
+ qa: [
44
+ ...TOOL_CATEGORIES.init,
45
+ ...TOOL_CATEGORIES.bug,
46
+ ...TOOL_CATEGORIES.testcase,
47
+ ...TOOL_CATEGORIES.storyReadonly,
48
+ ...TOOL_CATEGORIES.taskReadonly,
49
+ ...TOOL_CATEGORIES.comment,
50
+ ...TOOL_CATEGORIES.utility
51
+ ],
52
+ dev: [
53
+ ...TOOL_CATEGORIES.init,
54
+ ...TOOL_CATEGORIES.task,
55
+ ...TOOL_CATEGORIES.bugResolve,
56
+ ...TOOL_CATEGORIES.project,
57
+ ...TOOL_CATEGORIES.execution,
58
+ ...TOOL_CATEGORIES.build,
59
+ ...TOOL_CATEGORIES.storyReadonly,
60
+ ...TOOL_CATEGORIES.comment,
61
+ ...TOOL_CATEGORIES.utility
62
+ ]
63
+ };
64
+
65
+ const ROLE_NAMES = {
66
+ pm: '产品经理',
67
+ qa: '测试工程师',
68
+ dev: '开发工程师'
69
+ };
70
+
71
+ // 读取完整的 index.ts
72
+ const indexPath = path.join(__dirname, '..', 'src', 'index.ts');
73
+ const indexContent = fs.readFileSync(indexPath, 'utf8');
74
+
75
+ // 提取工具注册块的函数
76
+ function extractToolBlocks(content) {
77
+ const lines = content.split('\n');
78
+ const blocks = [];
79
+ let currentBlock = null;
80
+ let braceDepth = 0;
81
+ let parenDepth = 0;
82
+
83
+ for (let i = 0; i < lines.length; i++) {
84
+ const line = lines[i];
85
+ const trimmed = line.trim();
86
+
87
+ // 检测 server.tool 的开始
88
+ if (trimmed.startsWith('server.tool(')) {
89
+ // 提取工具名
90
+ const match = trimmed.match(/server\.tool\("([^"]+)"/);
91
+ if (match) {
92
+ currentBlock = {
93
+ toolName: match[1],
94
+ startLine: i,
95
+ lines: [line],
96
+ braceDepth: 0,
97
+ parenDepth: 0
98
+ };
99
+
100
+ // 计算当前行的括号和大括号
101
+ currentBlock.parenDepth += (line.match(/\(/g) || []).length - (line.match(/\)/g) || []).length;
102
+ currentBlock.braceDepth += (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length;
103
+ }
104
+ } else if (currentBlock) {
105
+ // 在工具块内
106
+ currentBlock.lines.push(line);
107
+ currentBlock.parenDepth += (line.match(/\(/g) || []).length - (line.match(/\)/g) || []).length;
108
+ currentBlock.braceDepth += (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length;
109
+
110
+ // 检查是否到达块的结束(圆括号和大括号都平衡,且以 ); 结尾)
111
+ if (currentBlock.parenDepth === 0 && currentBlock.braceDepth === 0 && trimmed.endsWith(');')) {
112
+ currentBlock.endLine = i;
113
+ blocks.push(currentBlock);
114
+ currentBlock = null;
115
+ }
116
+ }
117
+ }
118
+
119
+ return blocks;
120
+ }
121
+
122
+ // 为每个角色生成文件
123
+ ['pm', 'qa', 'dev'].forEach(role => {
124
+ console.log(`\n正在生成 ${ROLE_NAMES[role]} (${role}) 版本...`);
125
+
126
+ const allowedTools = new Set(ROLE_PERMISSIONS[role]);
127
+ console.log(` 允许的工具: ${Array.from(allowedTools).join(', ')}`);
128
+
129
+ // 提取所有工具块
130
+ const toolBlocks = extractToolBlocks(indexContent);
131
+ console.log(` 总工具数: ${toolBlocks.length}`);
132
+
133
+ // 过滤出允许的工具
134
+ const allowedBlocks = toolBlocks.filter(block => allowedTools.has(block.toolName));
135
+ console.log(` ${ROLE_NAMES[role]}可用工具数: ${allowedBlocks.length}`);
136
+
137
+ // 构建新文件内容
138
+ const lines = indexContent.split('\n');
139
+ const outputLines = [];
140
+ const includedLines = new Set();
141
+
142
+ // 标记所有要包含的行
143
+ allowedBlocks.forEach(block => {
144
+ for (let i = block.startLine; i <= block.endLine; i++) {
145
+ includedLines.add(i);
146
+ }
147
+ // 如果前一行是注释,也包含进来
148
+ if (block.startLine > 0 && lines[block.startLine - 1].trim().startsWith('//')) {
149
+ includedLines.add(block.startLine - 1);
150
+ }
151
+ });
152
+
153
+ // 找到第一个 server.tool 的位置
154
+ let firstToolLine = toolBlocks.length > 0 ? toolBlocks[0].startLine : lines.length;
155
+
156
+ // 输出文件头部(到第一个工具之前)
157
+ for (let i = 0; i < firstToolLine; i++) {
158
+ outputLines.push(lines[i]);
159
+ }
160
+
161
+ // 更新服务器名称和版本
162
+ const headerContent = outputLines.join('\n');
163
+ let updatedHeader = headerContent
164
+ .replace(/name: "Zentao API"/, `name: "Zentao API (${ROLE_NAMES[role]})"`)
165
+ .replace(/version: "1\.16\.0"/, `version: "1.17.0"`);
166
+
167
+ // 输出更新后的头部
168
+ const finalLines = updatedHeader.split('\n');
169
+
170
+ // 添加所有允许的工具块
171
+ let lastEndLine = firstToolLine - 1;
172
+ allowedBlocks.forEach((block, index) => {
173
+ // 添加空行分隔(如果需要)
174
+ if (block.startLine > lastEndLine + 1) {
175
+ finalLines.push('');
176
+ }
177
+
178
+ // 添加工具块
179
+ for (let i = block.startLine; i <= block.endLine; i++) {
180
+ finalLines.push(lines[i]);
181
+ }
182
+
183
+ lastEndLine = block.endLine;
184
+ });
185
+
186
+ // 添加文件尾部(服务器启动代码)
187
+ const lastToolBlock = toolBlocks[toolBlocks.length - 1];
188
+ if (lastToolBlock) {
189
+ for (let i = lastToolBlock.endLine + 1; i < lines.length; i++) {
190
+ finalLines.push(lines[i]);
191
+ }
192
+ }
193
+
194
+ // 写入文件
195
+ const output = finalLines.join('\n');
196
+ const outputPath = path.join(__dirname, '..', 'src', `index-${role}.ts`);
197
+ fs.writeFileSync(outputPath, output, 'utf8');
198
+
199
+ console.log(` ✓ 已生成: ${outputPath}`);
200
+
201
+ // 统计文件大小
202
+ const stats = fs.statSync(outputPath);
203
+ const originalStats = fs.statSync(indexPath);
204
+ const reduction = ((1 - stats.size / originalStats.size) * 100).toFixed(1);
205
+ console.log(` 文件大小: ${(stats.size / 1024).toFixed(1)} KB (原始: ${(originalStats.size / 1024).toFixed(1)} KB, 减少 ${reduction}%)`);
206
+ });
207
+
208
+ console.log('\n✓ 所有角色版本生成完成!');
209
+ console.log('\n下一步:');
210
+ console.log('1. 运行 npx tsc 编译所有版本');
211
+ console.log('2. 配置 package.json 的构建脚本');
212
+ console.log('3. 测试各个版本');
@@ -1,93 +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();
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();
@@ -1,13 +1,13 @@
1
- Add-Type -AssemblyName System.Windows.Forms
2
- Add-Type -AssemblyName System.Drawing
3
- $img = [System.Windows.Forms.Clipboard]::GetImage()
4
- if ($img) {
5
- $ms = New-Object System.IO.MemoryStream
6
- $img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png)
7
- $bytes = $ms.ToArray()
8
- $ms.Close()
9
- $base64 = [Convert]::ToBase64String($bytes)
10
- Write-Output $base64
11
- } else {
12
- Write-Error "NoImage"
13
- }
1
+ Add-Type -AssemblyName System.Windows.Forms
2
+ Add-Type -AssemblyName System.Drawing
3
+ $img = [System.Windows.Forms.Clipboard]::GetImage()
4
+ if ($img) {
5
+ $ms = New-Object System.IO.MemoryStream
6
+ $img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png)
7
+ $bytes = $ms.ToArray()
8
+ $ms.Close()
9
+ $base64 = [Convert]::ToBase64String($bytes)
10
+ Write-Output $base64
11
+ } else {
12
+ Write-Error "NoImage"
13
+ }