@becrafter/prompt-manager 0.0.8

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 (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +326 -0
  3. package/app/cli/cli.js +17 -0
  4. package/app/cli/commands/start.js +20 -0
  5. package/app/cli/index.js +37 -0
  6. package/app/cli/support/argv.js +7 -0
  7. package/app/cli/support/signals.js +34 -0
  8. package/app/desktop/assets/icon.png +0 -0
  9. package/app/desktop/main.js +496 -0
  10. package/app/desktop/package-lock.json +4091 -0
  11. package/app/desktop/package.json +63 -0
  12. package/examples/prompts/developer/code-review.yaml +32 -0
  13. package/examples/prompts/developer/code_refactoring.yaml +31 -0
  14. package/examples/prompts/developer/doc-generator.yaml +36 -0
  15. package/examples/prompts/developer/error-code-fixer.yaml +35 -0
  16. package/examples/prompts/generator/gen_3d_edu_webpage_html.yaml +117 -0
  17. package/examples/prompts/generator/gen_3d_webpage_html.yaml +75 -0
  18. package/examples/prompts/generator/gen_bento_grid_html.yaml +112 -0
  19. package/examples/prompts/generator/gen_html_web_page.yaml +88 -0
  20. package/examples/prompts/generator/gen_knowledge_card_html.yaml +83 -0
  21. package/examples/prompts/generator/gen_magazine_card_html.yaml +82 -0
  22. package/examples/prompts/generator/gen_mimeng_headline_title.yaml +71 -0
  23. package/examples/prompts/generator/gen_podcast_script.yaml +69 -0
  24. package/examples/prompts/generator/gen_prd_prototype_html.yaml +175 -0
  25. package/examples/prompts/generator/gen_summarize.yaml +157 -0
  26. package/examples/prompts/generator/gen_title.yaml +119 -0
  27. package/examples/prompts/generator/others/api_documentation.yaml +32 -0
  28. package/examples/prompts/generator/others/build_mcp_server.yaml +26 -0
  29. package/examples/prompts/generator/others/project_architecture.yaml +31 -0
  30. package/examples/prompts/generator/others/test_case_generator.yaml +30 -0
  31. package/examples/prompts/generator/others/writing_assistant.yaml +72 -0
  32. package/package.json +54 -0
  33. package/packages/admin-ui/admin.html +4959 -0
  34. package/packages/admin-ui/css/codemirror-theme_xq-light.css +43 -0
  35. package/packages/admin-ui/css/codemirror.css +344 -0
  36. package/packages/admin-ui/js/closebrackets.min.js +8 -0
  37. package/packages/admin-ui/js/codemirror.min.js +8 -0
  38. package/packages/admin-ui/js/js-yaml.min.js +2 -0
  39. package/packages/admin-ui/js/markdown.min.js +8 -0
  40. package/packages/server/config.js +283 -0
  41. package/packages/server/logger.js +55 -0
  42. package/packages/server/manager.js +473 -0
  43. package/packages/server/mcp.js +234 -0
  44. package/packages/server/mcpManager.js +205 -0
  45. package/packages/server/server.js +1001 -0
  46. package/scripts/postinstall.js +34 -0
@@ -0,0 +1,496 @@
1
+ const electron = require('electron');
2
+ const { app, BrowserWindow, Tray, Menu, clipboard, dialog, nativeImage, shell } = electron;
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+ const os = require('os');
6
+ const { pathToFileURL } = require('url');
7
+ const tar = require('tar');
8
+ const { spawn } = require('child_process');
9
+
10
+ let tray = null;
11
+ let adminWindow = null;
12
+ let serviceState = 'stopped';
13
+ let currentServerState = null;
14
+ let serverModule = null;
15
+ let serverModuleVersion = 0;
16
+ let serverModuleLoading = null;
17
+ let isQuitting = false;
18
+ let runtimeServerRoot = null;
19
+
20
+ const desktopPackageJson = JSON.parse(
21
+ fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8')
22
+ );
23
+
24
+ function resolveBaseServerRoot() {
25
+ return path.resolve(__dirname, '..', '..');
26
+ }
27
+
28
+ async function ensureRuntimeServerRoot() {
29
+ if (runtimeServerRoot) {
30
+ return runtimeServerRoot;
31
+ }
32
+
33
+ if (!app.isPackaged) {
34
+ runtimeServerRoot = resolveBaseServerRoot();
35
+ return runtimeServerRoot;
36
+ }
37
+
38
+ const packagedRoot = path.join(process.resourcesPath, 'prompt-manager');
39
+ const runtimeRoot = path.join(app.getPath('userData'), 'prompt-manager');
40
+
41
+ try {
42
+ await fs.promises.access(runtimeRoot, fs.constants.F_OK);
43
+ } catch (error) {
44
+ await fs.promises.mkdir(runtimeRoot, { recursive: true });
45
+ await fs.promises.cp(packagedRoot, runtimeRoot, { recursive: true });
46
+ }
47
+
48
+ runtimeServerRoot = runtimeRoot;
49
+ return runtimeServerRoot;
50
+ }
51
+
52
+ async function loadServerModule(options = {}) {
53
+ if (options.forceReload) {
54
+ serverModule = null;
55
+ serverModuleVersion += 1;
56
+ }
57
+
58
+ if (serverModule) {
59
+ return serverModule;
60
+ }
61
+
62
+ if (serverModuleLoading) {
63
+ return serverModuleLoading;
64
+ }
65
+
66
+ const serverRoot = await ensureRuntimeServerRoot();
67
+ const serverEntry = path.join(serverRoot, 'packages', 'server', 'server.js');
68
+ const entryUrl = pathToFileURL(serverEntry);
69
+ entryUrl.searchParams.set('v', String(serverModuleVersion));
70
+ serverModuleLoading = import(entryUrl.href)
71
+ .then((mod) => {
72
+ serverModule = mod;
73
+ return mod;
74
+ })
75
+ .finally(() => {
76
+ serverModuleLoading = null;
77
+ });
78
+ return serverModuleLoading;
79
+ }
80
+
81
+ function getServerStatusLabel() {
82
+ switch (serviceState) {
83
+ case 'running':
84
+ return '运行中';
85
+ case 'starting':
86
+ return '启动中';
87
+ case 'stopping':
88
+ return '停止中';
89
+ case 'error':
90
+ return '启动失败';
91
+ default:
92
+ return '已停止';
93
+ }
94
+ }
95
+
96
+ async function startService() {
97
+ if (serviceState === 'running' || serviceState === 'starting') {
98
+ return;
99
+ }
100
+
101
+ serviceState = 'starting';
102
+ refreshTrayMenu();
103
+
104
+ try {
105
+ const module = await loadServerModule();
106
+ await module.startServer();
107
+ currentServerState = module.getServerState();
108
+ serviceState = 'running';
109
+ } catch (error) {
110
+ serviceState = 'error';
111
+ currentServerState = null;
112
+ dialog.showErrorBox('服务启动失败', error?.message || String(error));
113
+ } finally {
114
+ refreshTrayMenu();
115
+ }
116
+ }
117
+
118
+ async function stopService() {
119
+ if (serviceState !== 'running') {
120
+ return;
121
+ }
122
+
123
+ serviceState = 'stopping';
124
+ refreshTrayMenu();
125
+
126
+ try {
127
+ const module = await loadServerModule();
128
+ await module.stopServer();
129
+ currentServerState = null;
130
+ serviceState = 'stopped';
131
+ } catch (error) {
132
+ dialog.showErrorBox('停止服务失败', error?.message || String(error));
133
+ serviceState = 'error';
134
+ } finally {
135
+ refreshTrayMenu();
136
+ }
137
+ }
138
+
139
+ function ensureTray() {
140
+ if (tray) {
141
+ return tray;
142
+ }
143
+
144
+ // 尝试多种可能的图标路径
145
+ const possiblePaths = [
146
+ path.join(__dirname, 'assets', 'icon.png'),
147
+ path.join(process.resourcesPath, 'assets', 'icon.png'),
148
+ path.join(__dirname, '..', 'assets', 'icon.png')
149
+ ];
150
+
151
+ let iconPath = null;
152
+ let icon = null;
153
+
154
+ console.log('App is packaged:', app.isPackaged);
155
+ console.log('__dirname:', __dirname);
156
+ console.log('process.resourcesPath:', process.resourcesPath);
157
+
158
+ for (const possiblePath of possiblePaths) {
159
+ console.log('Checking icon path:', possiblePath);
160
+ console.log('File exists:', fs.existsSync(possiblePath));
161
+ if (fs.existsSync(possiblePath)) {
162
+ iconPath = possiblePath;
163
+ break;
164
+ }
165
+ }
166
+
167
+ if (!iconPath) {
168
+ console.error('Icon file not found in any of the expected locations');
169
+ // 创建一个简单的文本托盘作为后备
170
+ tray = new Tray(nativeImage.createEmpty());
171
+ tray.setToolTip('Prompt Server - Icon Missing');
172
+ refreshTrayMenu();
173
+ return tray;
174
+ }
175
+
176
+ console.log('Attempting to load icon from:', iconPath);
177
+
178
+ try {
179
+ icon = nativeImage.createFromPath(iconPath);
180
+ console.log('Icon loaded successfully');
181
+ console.log('Icon is empty:', icon.isEmpty());
182
+ console.log('Icon size:', icon.getSize());
183
+
184
+ if (process.platform === 'darwin') {
185
+ icon = icon.resize({ width: 18, height: 18 });
186
+ icon.setTemplateImage(true);
187
+ console.log('Icon resized and set as template image');
188
+ }
189
+
190
+ tray = new Tray(icon);
191
+ tray.setToolTip('Prompt Server');
192
+ console.log('Tray created successfully');
193
+ refreshTrayMenu();
194
+ return tray;
195
+ } catch (error) {
196
+ console.error('Failed to create tray:', error);
197
+ // 创建一个简单的文本托盘作为后备
198
+ tray = new Tray(nativeImage.createEmpty());
199
+ tray.setToolTip('Prompt Server - Error');
200
+ refreshTrayMenu();
201
+ return tray;
202
+ }
203
+ }
204
+
205
+ function refreshTrayMenu() {
206
+ if (!tray) {
207
+ return;
208
+ }
209
+
210
+ const address = currentServerState?.address ?? 'http://127.0.0.1:5621';
211
+ const adminUrl = currentServerState?.adminPath ? `${address}${currentServerState.adminPath}` : `${address}/admin`;
212
+
213
+ const template = [
214
+ { label: `状态:${getServerStatusLabel()}`, enabled: false },
215
+ { type: 'separator' },
216
+ {
217
+ label: serviceState === 'running' ? '停止服务' : '启动服务',
218
+ click: () => (serviceState === 'running' ? stopService() : startService())
219
+ },
220
+ {
221
+ label: '复制服务地址',
222
+ enabled: serviceState === 'running',
223
+ click: () => clipboard.writeText(`${address}/mcp`)
224
+ },
225
+ {
226
+ label: '打开管理后台',
227
+ enabled: serviceState === 'running',
228
+ click: () => openAdminWindow(adminUrl)
229
+ },
230
+ { type: 'separator' },
231
+ {
232
+ label: '检查更新',
233
+ click: () => checkForUpdates()
234
+ },
235
+ {
236
+ label: '关于服务',
237
+ click: () => showAboutDialog()
238
+ },
239
+ { type: 'separator' },
240
+ {
241
+ label: '退出服务',
242
+ click: async () => {
243
+ isQuitting = true;
244
+ await stopService();
245
+ app.quit();
246
+ }
247
+ }
248
+ ];
249
+
250
+ const menu = Menu.buildFromTemplate(template);
251
+ tray.setContextMenu(menu);
252
+ }
253
+
254
+ function openAdminWindow(url) {
255
+ if (adminWindow) {
256
+ adminWindow.focus();
257
+ return;
258
+ }
259
+
260
+ adminWindow = new BrowserWindow({
261
+ width: 1200,
262
+ height: 800,
263
+ title: 'Prompt Server 管理后台',
264
+ webPreferences: {
265
+ contextIsolation: true,
266
+ preload: undefined
267
+ }
268
+ });
269
+
270
+ adminWindow.loadURL(url);
271
+ adminWindow.on('closed', () => {
272
+ adminWindow = null;
273
+ });
274
+ }
275
+
276
+ async function getCurrentServiceVersion() {
277
+ try {
278
+ const serverRoot = await ensureRuntimeServerRoot();
279
+ const pkgRaw = await fs.promises.readFile(path.join(serverRoot, 'package.json'), 'utf8');
280
+ const pkg = JSON.parse(pkgRaw);
281
+ return pkg.version;
282
+ } catch (error) {
283
+ return 'unknown';
284
+ }
285
+ }
286
+
287
+ function compareVersions(a, b) {
288
+ const toNumbers = (value = '') => value.split('.').map((part) => parseInt(part, 10) || 0);
289
+ const [a1, a2, a3] = toNumbers(a);
290
+ const [b1, b2, b3] = toNumbers(b);
291
+ if (a1 !== b1) return a1 - b1;
292
+ if (a2 !== b2) return a2 - b2;
293
+ return a3 - b3;
294
+ }
295
+
296
+ async function checkForUpdates() {
297
+ try {
298
+ const currentVersion = await getCurrentServiceVersion();
299
+
300
+ const response = await fetch('https://registry.npmjs.org/@becrafter/prompt-manager');
301
+ if (!response.ok) {
302
+ throw new Error('无法获取最新版本信息');
303
+ }
304
+
305
+ const metadata = await response.json();
306
+ const latestVersion = metadata?.['dist-tags']?.latest;
307
+
308
+ if (!latestVersion) {
309
+ throw new Error('未能解析最新版本号');
310
+ }
311
+
312
+ if (compareVersions(latestVersion, currentVersion) <= 0) {
313
+ await dialog.showMessageBox({
314
+ type: 'info',
315
+ message: '已是最新版本',
316
+ detail: `服务当前版本:${currentVersion}`
317
+ });
318
+ return;
319
+ }
320
+
321
+ const { response: action } = await dialog.showMessageBox({
322
+ type: 'question',
323
+ buttons: ['立即升级', '打开发布页', '取消'],
324
+ defaultId: 0,
325
+ cancelId: 2,
326
+ message: `发现新版本 ${latestVersion}`,
327
+ detail: '升级期间服务会短暂停止,是否继续?'
328
+ });
329
+
330
+ if (action === 1) {
331
+ shell.openExternal('https://github.com/BeCrafter/prompt-manager/releases/latest');
332
+ return;
333
+ }
334
+
335
+ if (action !== 0) {
336
+ return;
337
+ }
338
+
339
+ const wasRunning = serviceState === 'running';
340
+ if (wasRunning) {
341
+ await stopService();
342
+ }
343
+
344
+ await performInPlaceUpgrade(latestVersion);
345
+
346
+ await dialog.showMessageBox({
347
+ type: 'info',
348
+ message: '升级成功',
349
+ detail: `服务已升级到 ${latestVersion}`
350
+ });
351
+
352
+ if (wasRunning) {
353
+ await startService();
354
+ }
355
+ } catch (error) {
356
+ dialog.showErrorBox('检查更新失败', error?.message || String(error));
357
+ }
358
+ }
359
+
360
+ async function performInPlaceUpgrade(version) {
361
+ const tarballUrl = `https://registry.npmjs.org/@becrafter/prompt-manager/-/@becrafter/prompt-manager-${version}.tgz`;
362
+ const tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'prompt-manager-upgrade-'));
363
+ const tarballPath = path.join(tmpDir, `${version}.tgz`);
364
+
365
+ try {
366
+ const response = await fetch(tarballUrl);
367
+ if (!response.ok) {
368
+ throw new Error('无法下载升级包');
369
+ }
370
+
371
+ const buffer = Buffer.from(await response.arrayBuffer());
372
+ await fs.promises.writeFile(tarballPath, buffer);
373
+
374
+ await tar.x({ file: tarballPath, cwd: tmpDir });
375
+ const extractedPath = path.join(tmpDir, 'package');
376
+
377
+ const serverRoot = await ensureRuntimeServerRoot();
378
+ const examplesDir = path.join(serverRoot, 'examples', 'prompts');
379
+ const examplesBackup = path.join(tmpDir, 'examples-prompts');
380
+
381
+ if (await pathExists(examplesDir)) {
382
+ await fs.promises.cp(examplesDir, examplesBackup, { recursive: true });
383
+ }
384
+
385
+ await fs.promises.rm(serverRoot, { recursive: true, force: true });
386
+ await fs.promises.mkdir(serverRoot, { recursive: true });
387
+ await fs.promises.cp(extractedPath, serverRoot, { recursive: true });
388
+
389
+ if (await pathExists(examplesBackup)) {
390
+ const targetExamples = path.join(serverRoot, 'examples', 'prompts');
391
+ await fs.promises.mkdir(path.dirname(targetExamples), { recursive: true });
392
+ await fs.promises.cp(examplesBackup, targetExamples, { recursive: true });
393
+ }
394
+
395
+ await installServerDependencies(serverRoot);
396
+ await loadServerModule({ forceReload: true });
397
+ } finally {
398
+ await fs.promises.rm(tmpDir, { recursive: true, force: true });
399
+ }
400
+ }
401
+
402
+ async function pathExists(targetPath) {
403
+ try {
404
+ await fs.promises.access(targetPath, fs.constants.F_OK);
405
+ return true;
406
+ } catch (error) {
407
+ return false;
408
+ }
409
+ }
410
+
411
+ async function installServerDependencies(targetDir) {
412
+ const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
413
+ const args = ['install', '--omit=dev'];
414
+
415
+ await new Promise((resolve, reject) => {
416
+ const child = spawn(npmCommand, args, {
417
+ cwd: targetDir,
418
+ stdio: ['ignore', 'pipe', 'pipe']
419
+ });
420
+
421
+ let stderr = '';
422
+ child.stdout.on('data', (data) => {
423
+ console.log(`[npm] ${data.toString().trim()}`);
424
+ });
425
+ child.stderr.on('data', (data) => {
426
+ stderr += data.toString();
427
+ console.error(`[npm] ${data.toString().trim()}`);
428
+ });
429
+
430
+ child.on('error', (error) => {
431
+ reject(error);
432
+ });
433
+
434
+ child.on('close', (code) => {
435
+ if (code === 0) {
436
+ resolve();
437
+ } else {
438
+ reject(new Error(`npm install failed with exit code ${code}: ${stderr}`));
439
+ }
440
+ });
441
+ });
442
+ }
443
+
444
+ async function showAboutDialog() {
445
+ const serviceVersion = await getCurrentServiceVersion();
446
+ const lines = [
447
+ // `桌面应用版本:${desktopPackageJson.version}`,
448
+ `服务版本:${serviceVersion}`,
449
+ `Electron:${process.versions.electron}`,
450
+ // `Chromium:${process.versions.chrome}`,
451
+ `Node.js:${process.versions.node}`
452
+ ];
453
+
454
+ await dialog.showMessageBox({
455
+ type: 'info',
456
+ title: '关于 Prompt Server',
457
+ message: '组件版本信息',
458
+ detail: lines.join('\n')
459
+ });
460
+ }
461
+
462
+ app.whenReady().then(async () => {
463
+ console.log('App is packaged:', app.isPackaged);
464
+ console.log('App data path:', app.getPath('userData'));
465
+ console.log('Resources path:', process.resourcesPath);
466
+ console.log('__dirname:', __dirname);
467
+
468
+ Menu.setApplicationMenu(null);
469
+ if (process.platform === 'darwin') {
470
+ app.dock.hide();
471
+ }
472
+
473
+ await ensureRuntimeServerRoot();
474
+ ensureTray();
475
+ await startService();
476
+ });
477
+
478
+ app.on('window-all-closed', (event) => {
479
+ event.preventDefault();
480
+ });
481
+
482
+ app.on('before-quit', async (event) => {
483
+ if (isQuitting) {
484
+ return;
485
+ }
486
+ event.preventDefault();
487
+ isQuitting = true;
488
+ await stopService();
489
+ app.quit();
490
+ });
491
+
492
+ app.on('activate', () => {
493
+ if (!tray) {
494
+ ensureTray();
495
+ }
496
+ });