@becrafter/prompt-manager 0.0.16 → 0.0.19

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.
Files changed (60) hide show
  1. package/IFLOW.md +175 -0
  2. package/README.md +66 -80
  3. package/app/desktop/assets/icons/icon.icns +0 -0
  4. package/app/desktop/assets/icons/icon.ico +0 -0
  5. package/app/desktop/assets/icons/icon_1024x1024.png +0 -0
  6. package/app/desktop/assets/icons/icon_128x128.png +0 -0
  7. package/app/desktop/assets/icons/icon_16x16.png +0 -0
  8. package/app/desktop/assets/icons/icon_24x24.png +0 -0
  9. package/app/desktop/assets/icons/icon_256x256.png +0 -0
  10. package/app/desktop/assets/icons/icon_32x32.png +0 -0
  11. package/app/desktop/assets/icons/icon_48x48.png +0 -0
  12. package/app/desktop/assets/icons/icon_512x512.png +0 -0
  13. package/app/desktop/assets/icons/icon_64x64.png +0 -0
  14. package/app/desktop/assets/icons/icon_96x96.png +0 -0
  15. package/app/desktop/assets/templates/about.html +147 -0
  16. package/app/desktop/main.js +178 -460
  17. package/app/desktop/package-lock.json +1150 -412
  18. package/app/desktop/package.json +54 -11
  19. package/app/desktop/preload.js +7 -0
  20. package/app/desktop/src/core/error-handler.js +108 -0
  21. package/app/desktop/src/core/event-emitter.js +84 -0
  22. package/app/desktop/src/core/logger.js +108 -0
  23. package/app/desktop/src/core/state-manager.js +125 -0
  24. package/app/desktop/src/services/module-loader.js +193 -0
  25. package/app/desktop/src/services/runtime-manager.js +152 -0
  26. package/app/desktop/src/services/service-manager.js +169 -0
  27. package/app/desktop/src/services/update-manager.js +268 -0
  28. package/app/desktop/src/ui/about-dialog-manager.js +208 -0
  29. package/app/desktop/src/ui/tray-manager.js +202 -0
  30. package/app/desktop/src/utils/icon-manager.js +141 -0
  31. package/app/desktop/src/utils/path-utils.js +58 -0
  32. package/app/desktop/src/utils/resource-paths.js +72 -0
  33. package/app/desktop/src/utils/template-renderer.js +284 -0
  34. package/app/desktop/src/utils/version-utils.js +59 -0
  35. package/examples/prompts/engineer/engineer-professional.yaml +92 -0
  36. package/examples/prompts/engineer/laowang-engineer.yaml +132 -0
  37. package/examples/prompts/engineer/nekomata-engineer.yaml +123 -0
  38. package/examples/prompts/engineer/ojousama-engineer.yaml +124 -0
  39. package/examples/prompts/workflow/sixstep-workflow.yaml +192 -0
  40. package/package.json +10 -3
  41. package/packages/admin-ui/admin.html +2 -2
  42. package/packages/resources/tools/filesystem/filesystem.tool.js +184 -0
  43. package/packages/resources/tools/index.js +16 -0
  44. package/packages/server/api/admin.routes.js +450 -0
  45. package/packages/server/api/open.routes.js +89 -0
  46. package/packages/server/app.js +163 -0
  47. package/packages/server/mcp/mcp.handler.js +265 -0
  48. package/packages/server/mcp/mcp.server.js +181 -0
  49. package/packages/server/mcp/toolx.handler.js +131 -0
  50. package/packages/server/middlewares/auth.middleware.js +34 -0
  51. package/packages/server/server.js +42 -908
  52. package/packages/server/{manager.js → services/manager.js} +13 -5
  53. package/packages/server/{config.js → utils/config.js} +27 -27
  54. package/packages/server/utils/util.js +356 -0
  55. package/scripts/build-icons.js +105 -0
  56. package/scripts/icns-builder/package.json +12 -0
  57. package/packages/server/mcp.js +0 -234
  58. package/packages/server/mcpManager.js +0 -205
  59. /package/app/desktop/assets/{icon.png → icons/icon.png} +0 -0
  60. /package/packages/server/{logger.js → utils/logger.js} +0 -0
@@ -3,8 +3,9 @@ import path from 'path';
3
3
  import YAML from 'yaml';
4
4
  import { z } from 'zod';
5
5
  import crypto from 'crypto';
6
- import { logger } from './logger.js';
7
- import { config } from './config.js';
6
+ import { logger } from '../utils/logger.js';
7
+ import { config } from '../utils/config.js';
8
+ import { GROUP_META_FILENAME } from '../utils/util.js';
8
9
 
9
10
  /**
10
11
  * Prompt数据结构验证schema
@@ -34,7 +35,7 @@ const PromptSchema = z.object({
34
35
  /**
35
36
  * Prompt管理器类
36
37
  */
37
- export class PromptManager {
38
+ class PromptManager {
38
39
  constructor(promptsDir) {
39
40
  this.promptsDir = promptsDir;
40
41
  this.loadedPrompts = new Map();
@@ -92,7 +93,6 @@ export class PromptManager {
92
93
  * @returns {Object} 组元数据
93
94
  */
94
95
  readGroupMeta(dir) {
95
- const GROUP_META_FILENAME = '.groupmeta.json';
96
96
  try {
97
97
  const metaPath = path.join(dir, GROUP_META_FILENAME);
98
98
  if (!fs.existsSync(metaPath)) {
@@ -322,7 +322,7 @@ export class PromptManager {
322
322
  }
323
323
  });
324
324
 
325
- logger.info(`本地Prompt加载完成: 启用 ${loadedCount} 个, 禁用 ${disabledCount} 个, 失败 ${errorCount} 个`);
325
+ logger.info(`本地Prompt加载完成: 启用 ${loadedCount} 个, 禁用 ${disabledCount} 个, 失败 ${errorCount} 个\n`);
326
326
 
327
327
  return {
328
328
  success: loadedCount,
@@ -345,6 +345,11 @@ export class PromptManager {
345
345
  const filePath = typeof fileInfo === 'string' ? path.join(this.promptsDir, fileInfo) : fileInfo.filePath;
346
346
  const relativePath = typeof fileInfo === 'string' ? fileInfo : fileInfo.relativePath;
347
347
 
348
+ if (fileName === GROUP_META_FILENAME) {
349
+ // 跳过组元数据文件
350
+ return null;
351
+ }
352
+
348
353
  try {
349
354
  const content = await fs.readFile(filePath, 'utf8');
350
355
  const ext = path.extname(fileName).toLowerCase();
@@ -471,3 +476,6 @@ export class PromptManager {
471
476
  return await this.loadPrompts();
472
477
  }
473
478
  }
479
+
480
+ // 创建全局PromptManager实例
481
+ export const promptManager = new PromptManager(config.getPromptsDir());
@@ -15,10 +15,10 @@ const DEFAULT_PROMPTS_DIR = path.join(DEFAULT_HOME_DIR, 'prompts');
15
15
  function parseCommandLineArgs() {
16
16
  const args = process.argv.slice(2);
17
17
  const options = {};
18
-
18
+
19
19
  for (let i = 0; i < args.length; i++) {
20
20
  const arg = args[i];
21
-
21
+
22
22
  if (arg === '--prompts-dir' || arg === '-p') {
23
23
  options.promptsDir = args[i + 1];
24
24
  i++; // 跳过下一个参数
@@ -35,7 +35,7 @@ function parseCommandLineArgs() {
35
35
  options[key] = value;
36
36
  }
37
37
  }
38
-
38
+
39
39
  return options;
40
40
  }
41
41
 
@@ -61,7 +61,7 @@ MCP Prompt Server - 智能 Prompt 管理服务器
61
61
  PROMPTS_DIR Prompts目录路径
62
62
  MCP_SERVER_VERSION 服务器版本
63
63
  LOG_LEVEL 日志级别 (默认: info)
64
- MAX_PROMPTS 最大prompt数量限制 (默认: 100)
64
+ MAX_PROMPTS 最大prompt数量限制 (默认: 1000)
65
65
  RECURSIVE_SCAN 是否启用递归扫描子目录 (默认: true)
66
66
 
67
67
  示例:
@@ -77,34 +77,34 @@ MCP Prompt Server - 智能 Prompt 管理服务器
77
77
  export class Config {
78
78
  constructor() {
79
79
  const cliArgs = parseCommandLineArgs();
80
-
80
+
81
81
  // 处理帮助和版本信息
82
82
  if (cliArgs.help) {
83
83
  showHelp();
84
84
  process.exit(0);
85
85
  }
86
-
86
+
87
87
  // 确定prompts目录
88
- this.promptsDir = cliArgs.promptsDir ||
89
- process.env.PROMPTS_DIR ||
90
- DEFAULT_PROMPTS_DIR;
88
+ this.promptsDir = cliArgs.promptsDir ||
89
+ process.env.PROMPTS_DIR ||
90
+ DEFAULT_PROMPTS_DIR;
91
91
  this.configHome = path.dirname(this.promptsDir);
92
-
92
+
93
93
  // 服务器端口
94
94
  this.port = cliArgs.port || process.env.SERVER_PORT || 5621;
95
-
95
+
96
96
  // 其他配置
97
97
  this.serverName = process.env.MCP_SERVER_NAME || 'prompt-manager';
98
- this.serverVersion = process.env.MCP_SERVER_VERSION || '0.0.16';
98
+ this.serverVersion = process.env.MCP_SERVER_VERSION || '0.0.19';
99
99
  this.logLevel = process.env.LOG_LEVEL || 'info';
100
- this.maxPrompts = parseInt(process.env.MAX_PROMPTS) || 100;
100
+ this.maxPrompts = parseInt(process.env.MAX_PROMPTS) || 1000;
101
101
  this.recursiveScan = process.env.RECURSIVE_SCAN !== 'false'; // 默认启用递归扫描
102
-
102
+
103
103
  // 管理员配置
104
104
  this.adminEnable = process.env.ADMIN_ENABLE !== 'false'; // 默认启用管理员功能
105
105
  this.adminPath = process.env.ADMIN_PATH || '/admin';
106
106
  this.exportToken = process.env.EXPORT_TOKEN || crypto.randomBytes(32).toString('hex');
107
-
107
+
108
108
  // 管理员账户(从环境变量或默认值)
109
109
  const adminUsername = process.env.ADMIN_USERNAME || 'admin';
110
110
  const adminPassword = process.env.ADMIN_PASSWORD || 'admin';
@@ -116,7 +116,7 @@ export class Config {
116
116
  token: process.env.ADMIN_TOKEN || crypto.randomBytes(32).toString('hex')
117
117
  }
118
118
  ];
119
-
119
+
120
120
  if (cliArgs.version) {
121
121
  process.stderr.write(this.serverVersion + '\n');
122
122
  process.exit(0);
@@ -254,10 +254,10 @@ export class Config {
254
254
  if (!exists) {
255
255
  throw new Error(`Prompts目录不存在: ${this.promptsDir}`);
256
256
  }
257
-
257
+
258
258
  // 检查目录是否可读
259
259
  await fs.access(this.promptsDir, fs.constants.R_OK);
260
-
260
+
261
261
  return true;
262
262
  } catch (error) {
263
263
  throw new Error(`配置验证失败: ${error.message}`);
@@ -268,15 +268,15 @@ export class Config {
268
268
  * 显示当前配置(输出到 stderr,不干扰 MCP 通信)
269
269
  */
270
270
  showConfig() {
271
- process.stderr.write('===== 服务器配置 =====\n');
272
- process.stderr.write(`服务名称: ${this.serverName}\n`);
273
- process.stderr.write(`服务版本: ${this.serverVersion}\n`);
274
- process.stderr.write(`服务端口: ${this.port}\n`);
275
- process.stderr.write(`日志级别: ${this.logLevel}\n`);
276
- process.stderr.write(`递归扫描: ${this.recursiveScan ? '启用' : '禁用'}\n`);
277
- process.stderr.write(`Prompts目录: ${this.promptsDir}\n`);
278
- process.stderr.write(`最大Prompt数量: ${this.maxPrompts}\n`);
279
- process.stderr.write('=====================\n');
271
+ process.stderr.write('===================================== 服务器配置 =====================================\n');
272
+ process.stderr.write(` 服务名称: ${this.serverName}\n`);
273
+ process.stderr.write(` 服务版本: ${this.serverVersion}\n`);
274
+ process.stderr.write(` 服务端口: ${this.port}\n`);
275
+ process.stderr.write(` 日志级别: ${this.logLevel}\n`);
276
+ process.stderr.write(` 递归扫描: ${this.recursiveScan ? '启用' : '禁用'}\n`);
277
+ process.stderr.write(` Prompts目录: ${this.promptsDir}\n`);
278
+ process.stderr.write(` 最大Prompt数量: ${this.maxPrompts}\n`);
279
+ process.stderr.write('======================================================================================\n\n');
280
280
  }
281
281
  }
282
282
 
@@ -0,0 +1,356 @@
1
+ import crypto from 'crypto';
2
+ import fs from 'fs';
3
+ import os from 'os';
4
+ import path from 'path';
5
+ import fse from 'fs-extra';
6
+ import yaml from 'js-yaml';
7
+ import { fileURLToPath } from 'url';
8
+ import { logger } from './logger.js';
9
+
10
+ export const GROUP_META_FILENAME = '.groupmeta.json';
11
+ export const GROUP_NAME_REGEX = /^[a-zA-Z0-9-_\u4e00-\u9fa5]{1,64}$/;
12
+
13
+ // 获取当前文件的目录路径
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = path.dirname(__filename);
16
+ const projectRoot = path.resolve(__dirname, '../../..');
17
+ const examplesPromptsRoot = path.join(projectRoot, 'examples', 'prompts');
18
+ const promptsDir = path.join(os.homedir(), '.prompt-manager', 'prompts');
19
+
20
+ let _promptManager;
21
+
22
+ export class Util {
23
+ /**
24
+ * 检查并初始化prompts目录,如果目录为空则从示例目录复制内容
25
+ * @returns
26
+ */
27
+ async seedPromptsIfEmpty() {
28
+ try {
29
+ const entries = await fse.readdir(promptsDir);
30
+ if (entries.length > 0) {
31
+ return;
32
+ }
33
+ } catch (error) {
34
+ logger.warn('读取Prompts目录失败,尝试同步示例数据:', error.message);
35
+ }
36
+
37
+ try {
38
+ const exists = await fse.pathExists(examplesPromptsRoot);
39
+ if (!exists) {
40
+ return;
41
+ }
42
+ await fse.copy(examplesPromptsRoot, promptsDir, {
43
+ overwrite: false,
44
+ errorOnExist: false,
45
+ recursive: true
46
+ });
47
+ logger.info(`已将示例Prompts同步到 ${promptsDir}`);
48
+ } catch (error) {
49
+ logger.warn('同步示例Prompts失败:', error.message);
50
+ }
51
+ }
52
+
53
+ /**
54
+ * 生成文件唯一标识码
55
+ * @param {*} relativePath
56
+ * @returns
57
+ */
58
+ generateUniqueId(relativePath) {
59
+ const hash = crypto.createHash('sha256');
60
+ hash.update(relativePath);
61
+ const hashHex = hash.digest('hex');
62
+ return hashHex.substring(0, 8);
63
+ }
64
+
65
+ /**
66
+ * 从文件中读取提示词
67
+ * @returns
68
+ */
69
+ getPromptsFromFiles() {
70
+ const prompts = [];
71
+
72
+ const traverseDir = (currentPath, relativeDir = '', inheritedEnabled = true) => {
73
+ let currentEnabled = inheritedEnabled;
74
+ if (relativeDir) {
75
+ const meta = this.readGroupMeta(currentPath);
76
+ currentEnabled = currentEnabled && (meta.enabled !== false);
77
+ }
78
+
79
+ try {
80
+ const entries = fs.readdirSync(currentPath, { withFileTypes: true });
81
+ for (const entry of entries) {
82
+ const fullPath = path.join(currentPath, entry.name);
83
+ if (entry.isDirectory()) {
84
+ const childRelativePath = relativeDir ? `${relativeDir}/${entry.name}` : entry.name;
85
+ traverseDir(fullPath, childRelativePath, currentEnabled);
86
+ } else if (entry.isFile() && entry.name.endsWith('.yaml')) {
87
+ try {
88
+ const fileContent = fs.readFileSync(fullPath, 'utf8');
89
+ const prompt = yaml.load(fileContent);
90
+ if (prompt && prompt.name) {
91
+ const relativePath = path.relative(promptsDir, fullPath);
92
+ const normalizedRelativePath = relativePath.split(path.sep).join('/');
93
+ const relativeDirForFile = path.dirname(normalizedRelativePath);
94
+ const topLevelGroup = relativeDirForFile && relativeDirForFile !== '.' ? relativeDirForFile.split('/')[0] : (prompt.group || 'default');
95
+ const groupPath = relativeDirForFile && relativeDirForFile !== '.' ? relativeDirForFile : topLevelGroup;
96
+ prompts.push({
97
+ ...prompt,
98
+ uniqueId: this.generateUniqueId(prompt.name + '.yaml'),
99
+ fileName: entry.name,
100
+ relativePath: normalizedRelativePath,
101
+ group: topLevelGroup,
102
+ groupPath,
103
+ groupEnabled: currentEnabled
104
+ });
105
+ }
106
+ } catch (error) {
107
+ logger.error(`Error processing file ${fullPath}:`, error);
108
+ }
109
+ }
110
+ }
111
+ } catch (error) {
112
+ logger.error(`Error reading directory ${currentPath}:`, error);
113
+ }
114
+ }
115
+
116
+ traverseDir(promptsDir);
117
+ return prompts;
118
+ }
119
+
120
+ /**
121
+ * 计算搜索关键词与prompt的相似度得分
122
+ * @param {string} searchTerm - 搜索关键词
123
+ * @param {Object} prompt - prompt对象
124
+ * @returns {number} 相似度得分 (0-100)
125
+ */
126
+ calculateSimilarityScore(searchTerm, prompt) {
127
+ let totalScore = 0;
128
+ const searchLower = searchTerm ? searchTerm.toLowerCase() : '';
129
+
130
+ // 搜索字段权重配置(专注于内容搜索,不包含ID检索)
131
+ const fieldWeights = {
132
+ name: 60, // 名称权重高,是主要匹配字段
133
+ description: 40 // 描述权重适中,是辅助匹配字段
134
+ };
135
+
136
+ // 计算name匹配得分
137
+ if (prompt && prompt.name && typeof prompt.name === 'string') {
138
+ const nameScore = util.getStringMatchScore(searchLower, prompt.name.toLowerCase());
139
+ totalScore += nameScore * fieldWeights.name;
140
+ }
141
+
142
+ // 计算description匹配得分
143
+ if (prompt.description) {
144
+ const descScore = util.getStringMatchScore(searchLower, prompt.description.toLowerCase());
145
+ totalScore += descScore * fieldWeights.description;
146
+ }
147
+
148
+ // 标准化得分到0-100范围
149
+ const maxPossibleScore = Object.values(fieldWeights).reduce((sum, weight) => sum + weight, 0);
150
+ return Math.round((totalScore / maxPossibleScore) * 100);
151
+ }
152
+
153
+ /**
154
+ * 计算两个字符串的匹配得分
155
+ * @param {string} search - 搜索词 (已转小写)
156
+ * @param {string} target - 目标字符串 (已转小写)
157
+ * @returns {number} 匹配得分 (0-1)
158
+ */
159
+ getStringMatchScore(search, target) {
160
+ if (!search || !target) return 0;
161
+
162
+ // 完全匹配得分最高
163
+ if (target === search) return 1.0;
164
+
165
+ // 完全包含得分较高
166
+ if (target.includes(search)) return 0.8;
167
+
168
+ // 部分词匹配
169
+ const searchWords = search.split(/\s+/).filter(word => word.length > 0);
170
+ const targetWords = target.split(/\s+/).filter(word => word.length > 0);
171
+
172
+ let matchedWords = 0;
173
+ for (const searchWord of searchWords) {
174
+ for (const targetWord of targetWords) {
175
+ if (targetWord.includes(searchWord) || searchWord.includes(targetWord)) {
176
+ matchedWords++;
177
+ break;
178
+ }
179
+ }
180
+ }
181
+
182
+ if (searchWords.length > 0) {
183
+ const wordMatchRatio = matchedWords / searchWords.length;
184
+ return wordMatchRatio * 0.6; // 部分词匹配得分
185
+ }
186
+
187
+ return 0;
188
+ }
189
+
190
+ /**
191
+ * 更新目录元数据
192
+ * @param {*} dir
193
+ * @param {*} meta
194
+ */
195
+ writeGroupMeta(dir, meta = {}) {
196
+ const metaPath = this.getGroupMetaPath(dir);
197
+ const data = {
198
+ enabled: meta.enabled !== false
199
+ };
200
+ fs.writeFileSync(metaPath, JSON.stringify(data, null, 2), 'utf8');
201
+ }
202
+
203
+ /**
204
+ * 验证目录名称是否有效
205
+ * @param {*} name
206
+ * @returns
207
+ */
208
+ isValidGroupName(name) {
209
+ return GROUP_NAME_REGEX.test(name);
210
+ }
211
+
212
+ /**
213
+ * 验证目录名称
214
+ * @param {*} relativePath
215
+ * @returns
216
+ */
217
+ validateGroupPath(relativePath) {
218
+ if (!relativePath || typeof relativePath !== 'string') {
219
+ return null;
220
+ }
221
+ const segments = relativePath.split('/').filter(Boolean);
222
+ if (!segments.length) {
223
+ return null;
224
+ }
225
+ for (const segment of segments) {
226
+ if (!this.isValidGroupName(segment)) {
227
+ return null;
228
+ }
229
+ }
230
+ return segments;
231
+ }
232
+
233
+ /**
234
+ *
235
+ * @param {*} relativePath
236
+ * @returns
237
+ */
238
+ resolveGroupDir(relativePath) {
239
+ const segments = this.validateGroupPath(relativePath);
240
+ if (!segments) return null;
241
+ const targetPath = path.resolve(promptsDir, ...segments);
242
+ const normalizedPromptsDir = path.resolve(promptsDir);
243
+ if (!targetPath.startsWith(normalizedPromptsDir)) {
244
+ return null;
245
+ }
246
+ return { dir: targetPath, segments };
247
+ }
248
+
249
+ /**
250
+ * 获取目录元数据文件路径
251
+ * @param {*} dir
252
+ * @returns
253
+ */
254
+ getGroupMetaPath(dir) {
255
+ return path.join(dir, GROUP_META_FILENAME);
256
+ }
257
+
258
+ /**
259
+ * 读取目录元数据
260
+ * @param {*} dir
261
+ * @returns
262
+ */
263
+ readGroupMeta(dir) {
264
+ try {
265
+ const metaPath = this.getGroupMetaPath(dir);
266
+ if (!fs.existsSync(metaPath)) {
267
+ return { enabled: true };
268
+ }
269
+ const raw = fs.readFileSync(metaPath, 'utf8');
270
+ const data = JSON.parse(raw);
271
+ return {
272
+ enabled: data.enabled !== false
273
+ };
274
+ } catch (error) {
275
+ logger.warn('读取类目元数据失败:', error);
276
+ return { enabled: true };
277
+ }
278
+ }
279
+
280
+ /**
281
+ * 获取所有分组(直接从目录读取)
282
+ * @param {*} baseDir
283
+ * @param {*} relativePath
284
+ * @returns
285
+ */
286
+ buildGroupTree(baseDir, relativePath = '') {
287
+ const nodes = [];
288
+ try {
289
+ const entries = fs.readdirSync(baseDir, { withFileTypes: true });
290
+ for (const entry of entries) {
291
+ if (!entry.isDirectory()) continue;
292
+ if (entry.name.startsWith('.')) continue;
293
+ const childRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
294
+ const childDir = path.join(baseDir, entry.name);
295
+ const children = this.buildGroupTree(childDir, childRelativePath);
296
+ const meta = this.readGroupMeta(childDir);
297
+ nodes.push({
298
+ name: entry.name,
299
+ path: childRelativePath,
300
+ children,
301
+ enabled: meta.enabled !== false
302
+ });
303
+ }
304
+ } catch (error) {
305
+ logger.error('读取分组目录失败:', error);
306
+ }
307
+ nodes.sort((a, b) => a.name.localeCompare(b.name, 'zh-CN'));
308
+ return nodes;
309
+ }
310
+
311
+ async processPromptContent(prompt, args) {
312
+ let content = '';
313
+
314
+ if (prompt.messages && Array.isArray(prompt.messages)) {
315
+ const userMessages = prompt.messages.filter(msg => msg.role === 'user');
316
+
317
+ for (const message of userMessages) {
318
+ if (message.content && typeof message.content.text === 'string') {
319
+ let text = message.content.text;
320
+
321
+ // Replace arguments
322
+ if (args) {
323
+ for (const [key, value] of Object.entries(args)) {
324
+ const placeholder = new RegExp(`{{${key}}}`, 'g');
325
+ text = text.replace(placeholder, String(value));
326
+ }
327
+ }
328
+
329
+ content += text + '\n\n';
330
+ }
331
+ }
332
+ }
333
+
334
+ return content.trim();
335
+ }
336
+
337
+ /**
338
+ * 获取admin-ui目录的绝对路径
339
+ * @returns {string} admin-ui目录的绝对路径
340
+ */
341
+ getAdminUiRoot() {
342
+ const __filename = fileURLToPath(import.meta.url);
343
+ const __dirname = path.dirname(path.dirname(__filename));
344
+ return path.join(__dirname, '..', 'admin-ui');
345
+ };
346
+
347
+ async getPromptManager() {
348
+ if (!_promptManager) {
349
+ const serviceModule = await import('../services/manager.js');
350
+ _promptManager = serviceModule.promptManager;
351
+ }
352
+ return _promptManager;
353
+ }
354
+ }
355
+
356
+ export const util = new Util()
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { spawn } from 'child_process';
7
+ import sharp from 'sharp';
8
+ import toIco from 'to-ico';
9
+
10
+ // 获取当前文件的目录
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
13
+
14
+ // 图标文件路径
15
+ const sourceIcon = path.join(__dirname, '..', 'app', 'desktop', 'assets', 'icons', 'icon.png');
16
+ const assetsDir = path.join(__dirname, '..', 'app', 'desktop', 'assets', 'icons');
17
+
18
+ // 确保 assets 目录存在
19
+ if (!fs.existsSync(assetsDir)) {
20
+ fs.mkdirSync(assetsDir, { recursive: true });
21
+ }
22
+
23
+ // macOS 图标尺寸
24
+ const macSizes = [16, 32, 64, 128, 256, 512, 1024];
25
+
26
+ // Windows 图标尺寸
27
+ const winSizes = [16, 24, 32, 48, 64, 96, 128, 256];
28
+
29
+ // 为 macOS 创建不同尺寸的图标
30
+ console.log('Creating macOS icons...');
31
+ const macIconDir = path.join(assetsDir, 'icon.iconset');
32
+ if (!fs.existsSync(macIconDir)) {
33
+ fs.mkdirSync(macIconDir, { recursive: true });
34
+ }
35
+
36
+ for (const size of macSizes) {
37
+ const filename = `icon_${size}x${size}.png`;
38
+ const filepath = path.join(macIconDir, size === 512 ? 'icon_512x512@2x.png' : `icon_${size}x${size}.png`);
39
+ console.log(` - ${filename}`);
40
+ // 对于 512x512@2x 实际上是 1024x1024
41
+ const actualSize = size === 512 ? 1024 : size;
42
+ await sharp(sourceIcon).resize(actualSize, actualSize).toFile(filepath);
43
+ }
44
+
45
+ // 为 Windows 创建不同尺寸的图标
46
+ console.log('Creating Windows icons...');
47
+ const winIconBuffers = [];
48
+ for (const size of winSizes) {
49
+ const filename = `icon_${size}x${size}.png`;
50
+ const filepath = path.join(assetsDir, filename);
51
+ console.log(` - ${filename}`);
52
+ const buffer = await sharp(sourceIcon).resize(size, size).png({ compressionLevel: 9 }).toBuffer();
53
+ fs.writeFileSync(filepath, buffer);
54
+ // 保存前几个尺寸用于创建 ICO 文件,现在包括 256x256
55
+ if (size == 256) {
56
+ // 确保 buffer 是有效的 Buffer 对象
57
+ if (Buffer.isBuffer(buffer) && buffer.length > 0) {
58
+ winIconBuffers.push(buffer);
59
+ } else {
60
+ console.log(` - Skipping invalid buffer for size ${size}`);
61
+ }
62
+ }
63
+ }
64
+
65
+ // 创建 ICNS 文件 (macOS) - 使用 iconutil 工具
66
+ console.log('Creating ICNS file for macOS...');
67
+ try {
68
+ const icnsPath = path.join(assetsDir, 'icon.icns');
69
+
70
+ // 使用 iconutil 创建 ICNS 文件
71
+ const iconutil = spawn('iconutil', ['-c', 'icns', '-o', icnsPath, macIconDir]);
72
+
73
+ iconutil.on('close', (code) => {
74
+ if (code === 0) {
75
+ console.log(' - icon.icns');
76
+ // 清理临时文件
77
+ fs.rmSync(macIconDir, { recursive: true, force: true });
78
+ } else {
79
+ console.log(' - Failed to create ICNS file with iconutil');
80
+ }
81
+ });
82
+ } catch (error) {
83
+ console.log(' - Failed to create ICNS file:', error.message, error.stack);
84
+ }
85
+
86
+ // 创建 ICO 文件 (Windows)
87
+ console.log('Creating ICO file for Windows...');
88
+ try {
89
+ // 确保 winIconBuffers 不为空并且包含有效的缓冲区
90
+ if (winIconBuffers.length > 0) {
91
+ // 验证所有缓冲区都是有效的
92
+ const validBuffers = winIconBuffers.filter(buffer => Buffer.isBuffer(buffer) && buffer.length > 0);
93
+ if (validBuffers.length > 0) {
94
+ const icoBuffer = await toIco(validBuffers);
95
+ const icoPath = path.join(assetsDir, 'icon.ico');
96
+ fs.writeFileSync(icoPath, icoBuffer);
97
+ } else {
98
+ console.log(' - No valid buffers for ICO creation');
99
+ }
100
+ }
101
+ } catch (error) {
102
+ console.log(' - Failed to create ICO file:', error.message, error.stack);
103
+ }
104
+
105
+ console.log('Icon preparation completed.');
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "icns-builder",
3
+ "version": "1.0.0",
4
+ "description": "Simple ICNS file builder for macOS",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "build": "node index.js"
8
+ },
9
+ "dependencies": {
10
+ "png-to-icns": "^0.0.4"
11
+ }
12
+ }