@becrafter/prompt-manager 0.0.18 → 0.1.1

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 (108) hide show
  1. package/IFLOW.md +175 -0
  2. package/README.md +145 -234
  3. package/app/desktop/assets/app.1.png +0 -0
  4. package/app/desktop/assets/app.png +0 -0
  5. package/app/desktop/assets/icons/icon.icns +0 -0
  6. package/app/desktop/assets/icons/icon.ico +0 -0
  7. package/app/desktop/assets/icons/icon.png +0 -0
  8. package/app/desktop/assets/icons/tray.png +0 -0
  9. package/app/desktop/assets/templates/about.html +147 -0
  10. package/app/desktop/assets/tray.png +0 -0
  11. package/app/desktop/main.js +187 -732
  12. package/app/desktop/package-lock.json +723 -522
  13. package/app/desktop/package.json +54 -25
  14. package/app/desktop/preload.js +7 -0
  15. package/app/desktop/src/core/error-handler.js +108 -0
  16. package/app/desktop/src/core/event-emitter.js +84 -0
  17. package/app/desktop/src/core/logger.js +108 -0
  18. package/app/desktop/src/core/state-manager.js +125 -0
  19. package/app/desktop/src/services/module-loader.js +214 -0
  20. package/app/desktop/src/services/runtime-manager.js +301 -0
  21. package/app/desktop/src/services/service-manager.js +169 -0
  22. package/app/desktop/src/services/update-manager.js +268 -0
  23. package/app/desktop/src/ui/about-dialog-manager.js +208 -0
  24. package/app/desktop/src/ui/admin-window-manager.js +757 -0
  25. package/app/desktop/src/ui/splash-manager.js +253 -0
  26. package/app/desktop/src/ui/tray-manager.js +186 -0
  27. package/app/desktop/src/utils/icon-manager.js +133 -0
  28. package/app/desktop/src/utils/path-utils.js +58 -0
  29. package/app/desktop/src/utils/resource-paths.js +49 -0
  30. package/app/desktop/src/utils/resource-sync.js +260 -0
  31. package/app/desktop/src/utils/runtime-sync.js +241 -0
  32. package/app/desktop/src/utils/template-renderer.js +284 -0
  33. package/app/desktop/src/utils/version-utils.js +59 -0
  34. package/examples/prompts/engineer/engineer-professional.yaml +92 -0
  35. package/examples/prompts/engineer/laowang-engineer.yaml +132 -0
  36. package/examples/prompts/engineer/nekomata-engineer.yaml +123 -0
  37. package/examples/prompts/engineer/ojousama-engineer.yaml +124 -0
  38. package/examples/prompts/recommend/human_3-0_growth_diagnostic_coach_prompt.yaml +105 -0
  39. package/examples/prompts/workflow/sixstep-workflow.yaml +192 -0
  40. package/package.json +18 -9
  41. package/packages/admin-ui/.babelrc +3 -0
  42. package/packages/admin-ui/admin.html +237 -4784
  43. package/packages/admin-ui/css/main.css +2592 -0
  44. package/packages/admin-ui/css/recommended-prompts.css +610 -0
  45. package/packages/admin-ui/package-lock.json +6981 -0
  46. package/packages/admin-ui/package.json +36 -0
  47. package/packages/admin-ui/src/codemirror.js +53 -0
  48. package/packages/admin-ui/src/index.js +3188 -0
  49. package/packages/admin-ui/webpack.config.js +76 -0
  50. package/packages/resources/tools/chrome-devtools/README.md +310 -0
  51. package/packages/resources/tools/chrome-devtools/chrome-devtools.tool.js +1703 -0
  52. package/packages/resources/tools/file-reader/README.md +289 -0
  53. package/packages/resources/tools/file-reader/file-reader.tool.js +1545 -0
  54. package/packages/resources/tools/filesystem/README.md +359 -0
  55. package/packages/resources/tools/filesystem/filesystem.tool.js +538 -0
  56. package/packages/resources/tools/ollama-remote/README.md +192 -0
  57. package/packages/resources/tools/ollama-remote/ollama-remote.tool.js +421 -0
  58. package/packages/resources/tools/pdf-reader/README.md +236 -0
  59. package/packages/resources/tools/pdf-reader/pdf-reader.tool.js +565 -0
  60. package/packages/resources/tools/playwright/README.md +306 -0
  61. package/packages/resources/tools/playwright/playwright.tool.js +1186 -0
  62. package/packages/resources/tools/todolist/README.md +394 -0
  63. package/packages/resources/tools/todolist/todolist.tool.js +1312 -0
  64. package/packages/server/README.md +142 -0
  65. package/packages/server/api/admin.routes.js +42 -11
  66. package/packages/server/api/surge.routes.js +43 -0
  67. package/packages/server/app.js +119 -14
  68. package/packages/server/index.js +39 -0
  69. package/packages/server/mcp/mcp.server.js +346 -28
  70. package/packages/server/mcp/{mcp.handler.js → prompt.handler.js} +108 -9
  71. package/packages/server/mcp/sequential-thinking.handler.js +318 -0
  72. package/packages/server/mcp/think-plan.handler.js +274 -0
  73. package/packages/server/middlewares/auth.middleware.js +6 -0
  74. package/packages/server/package.json +51 -0
  75. package/packages/server/server.js +37 -1
  76. package/packages/server/toolm/index.js +9 -0
  77. package/packages/server/toolm/package-installer.service.js +267 -0
  78. package/packages/server/toolm/test-tools.js +264 -0
  79. package/packages/server/toolm/tool-context.service.js +334 -0
  80. package/packages/server/toolm/tool-dependency.service.js +168 -0
  81. package/packages/server/toolm/tool-description-generator-optimized.service.js +375 -0
  82. package/packages/server/toolm/tool-description-generator.service.js +312 -0
  83. package/packages/server/toolm/tool-environment.service.js +200 -0
  84. package/packages/server/toolm/tool-execution.service.js +277 -0
  85. package/packages/server/toolm/tool-loader.service.js +219 -0
  86. package/packages/server/toolm/tool-logger.service.js +223 -0
  87. package/packages/server/toolm/tool-manager.handler.js +65 -0
  88. package/packages/server/toolm/tool-manual-generator.service.js +389 -0
  89. package/packages/server/toolm/tool-mode-handlers.service.js +224 -0
  90. package/packages/server/toolm/tool-storage.service.js +111 -0
  91. package/packages/server/toolm/tool-sync.service.js +138 -0
  92. package/packages/server/toolm/tool-utils.js +20 -0
  93. package/packages/server/toolm/tool-yaml-parser.service.js +81 -0
  94. package/packages/server/toolm/validate-system.js +421 -0
  95. package/packages/server/utils/config.js +49 -5
  96. package/packages/server/utils/util.js +65 -10
  97. package/scripts/build-icons.js +135 -0
  98. package/scripts/build.sh +57 -0
  99. package/scripts/surge/CNAME +1 -0
  100. package/scripts/surge/README.md +47 -0
  101. package/scripts/surge/package-lock.json +34 -0
  102. package/scripts/surge/package.json +20 -0
  103. package/scripts/surge/sync-to-surge.js +151 -0
  104. package/packages/admin-ui/js/closebrackets.min.js +0 -8
  105. package/packages/admin-ui/js/codemirror.min.js +0 -8
  106. package/packages/admin-ui/js/js-yaml.min.js +0 -2
  107. package/packages/admin-ui/js/markdown.min.js +0 -8
  108. /package/app/desktop/assets/{icon.png → tray.1.png} +0 -0
@@ -1,28 +1,46 @@
1
1
  {
2
2
  "name": "@becrafter/prompt-desktop",
3
- "version": "0.0.18",
3
+ "version": "0.1.1",
4
4
  "description": "Menu bar desktop wrapper for @becrafter/prompt-manager",
5
+ "author": "BeCrafter",
6
+ "homepage": "https://github.com/BeCrafter/prompt-manager",
5
7
  "private": true,
6
8
  "main": "main.js",
7
9
  "scripts": {
8
10
  "dev": "electron .",
9
11
  "start": "npm run dev",
12
+ "prebuild": "cd ../.. && npm run build:icons",
10
13
  "build": "electron-builder"
11
14
  },
12
15
  "devDependencies": {
13
- "electron": "^31.0.2",
16
+ "electron": "^39.0.0",
14
17
  "electron-builder": "^24.13.3"
15
18
  },
16
19
  "dependencies": {
20
+ "@becrafter/prompt-manager-core": "file:../../packages/server",
17
21
  "@modelcontextprotocol/sdk": "^1.20.2",
18
- "tar": "^6.2.1"
22
+ "http-proxy-middleware": "^3.0.5",
23
+ "tar": "^7.5.2"
19
24
  },
20
25
  "build": {
21
26
  "appId": "com.becrafter.promptserver",
22
27
  "productName": "Prompt Manager",
28
+ "icon": "assets/icons/icon.png",
23
29
  "files": [
24
30
  "**/*",
25
- "assets/icon.png"
31
+ "assets/**/*",
32
+ "!docs",
33
+ "!.npmignore",
34
+ "!assets/**/*.1.png",
35
+ "!node_modules/**/test/**",
36
+ "!node_modules/**/example/**",
37
+ "!node_modules/**/*.md",
38
+ "!node_modules/**/LICENSE",
39
+ {
40
+ "from": "../../packages/web/",
41
+ "to": "web/",
42
+ "filter": ["**/*"]
43
+ }
26
44
  ],
27
45
  "directories": {
28
46
  "output": "../../dist",
@@ -30,42 +48,53 @@
30
48
  },
31
49
  "extraResources": [
32
50
  {
33
- "from": "../../",
34
- "to": "prompt-manager",
35
- "filter": [
36
- "package.json",
37
- "packages/**/*",
38
- "examples/**/*",
39
- "!node_modules",
40
- "!dist",
41
- "!app/desktop",
42
- "!scripts",
43
- "!*.md",
44
- "!*.lock",
45
- "!*.log",
46
- "!*.gitignore",
47
- "!*.npmignore"
48
- ]
51
+ "from": "../../packages/resources/tools/",
52
+ "to": "runtime/toolbox/",
53
+ "filter": ["**/*"]
54
+ },
55
+ {
56
+ "from": "../../env.example",
57
+ "to": "runtime/.env"
49
58
  }
50
59
  ],
51
60
  "mac": {
52
- "category": "public.app-category.productivity"
61
+ "category": "public.app-category.productivity",
62
+ "icon": "assets/icons/icon.icns",
63
+ "target": [
64
+ {
65
+ "target": "dmg",
66
+ "arch": [
67
+ "x64",
68
+ "arm64"
69
+ ]
70
+ }
71
+ ]
53
72
  },
54
73
  "win": {
55
74
  "target": [
56
75
  {
57
76
  "target": "nsis",
58
77
  "arch": [
59
- "x64"
78
+ "x64",
79
+ "arm64"
60
80
  ]
61
81
  }
62
- ]
82
+ ],
83
+ "icon": "assets/icons/icon.ico"
63
84
  },
64
85
  "linux": {
65
86
  "target": [
66
- "AppImage"
87
+ "AppImage",
88
+ "tar.xz"
67
89
  ],
68
- "category": "Utility"
90
+ "category": "Utility",
91
+ "maintainer": "support@becrafter.com",
92
+ "synopsis": "Menu bar desktop wrapper for Prompt Manager",
93
+ "description": "Prompt Manager is a desktop application that provides a menu bar interface for managing prompts via MCP (Model Context Protocol).",
94
+ "desktop": {
95
+ "StartupNotify": "true"
96
+ },
97
+ "artifactName": "${productName}-${version}.${ext}"
69
98
  }
70
99
  }
71
100
  }
@@ -0,0 +1,7 @@
1
+ const { contextBridge, ipcRenderer } = require('electron');
2
+
3
+ // 安全地暴露API到渲染进程
4
+ contextBridge.exposeInMainWorld('electronAPI', {
5
+ sendAboutWindowClick: (data) => ipcRenderer.send('about-window-click', data),
6
+ onAboutWindowClick: (callback) => ipcRenderer.on('about-window-click-response', callback)
7
+ });
@@ -0,0 +1,108 @@
1
+ const { dialog } = require('electron');
2
+
3
+ class ErrorHandler {
4
+ constructor(logger) {
5
+ this.logger = logger;
6
+ this.errorMessages = {
7
+ MODULE_LOAD_FAILED: '模块加载失败',
8
+ SERVER_INIT_FAILED: '服务器初始化失败',
9
+ DEPENDENCY_INSTALL_FAILED: '依赖安装失败',
10
+ SERVICE_START_FAILED: '服务启动失败',
11
+ SERVICE_STOP_FAILED: '服务停止失败',
12
+ UPGRADE_FAILED: '升级失败',
13
+ ICON_LOAD_FAILED: '图标加载失败'
14
+ };
15
+ }
16
+
17
+ handleError(errorType, error, context = {}) {
18
+ const errorMessage = this.formatErrorMessage(errorType, error, context);
19
+
20
+ // 记录错误日志
21
+ this.logger.error(errorMessage, error, {
22
+ errorType,
23
+ context,
24
+ timestamp: new Date().toISOString()
25
+ });
26
+
27
+ // 显示用户友好的错误提示
28
+ this.showErrorDialog(errorType, errorMessage, context);
29
+
30
+ return errorMessage;
31
+ }
32
+
33
+ formatErrorMessage(errorType, error, context) {
34
+ const baseMessage = this.errorMessages[errorType] || '发生未知错误';
35
+ const detailMessage = error?.message || String(error);
36
+
37
+ let fullMessage = `${baseMessage}: ${detailMessage}`;
38
+
39
+ if (context.logFilePath) {
40
+ fullMessage += `\n\n请查看日志文件: ${context.logFilePath}`;
41
+ }
42
+
43
+ return fullMessage;
44
+ }
45
+
46
+ showErrorDialog(errorType, message, context = {}) {
47
+ const options = {
48
+ type: 'error',
49
+ title: '错误',
50
+ message: message.split('\n')[0], // 第一行作为标题
51
+ detail: message.split('\n').slice(1).join('\n') // 其余作为详情
52
+ };
53
+
54
+ // 特殊错误类型的自定义处理
55
+ switch (errorType) {
56
+ case 'SERVICE_START_FAILED':
57
+ if (context.failureCount >= 3) {
58
+ options.buttons = ['重启应用', '取消'];
59
+ options.defaultId = 0;
60
+ options.cancelId = 1;
61
+ options.message = '服务启动失败';
62
+ options.detail = '多次尝试启动服务均失败,建议重启应用以恢复正常状态。';
63
+ }
64
+ break;
65
+
66
+ case 'DEPENDENCY_INSTALL_FAILED':
67
+ options.message = '依赖安装失败';
68
+ options.detail = `无法安装服务器依赖: ${context.detail || '未知错误'}\n\n请查看日志文件获取更多信息。`;
69
+ break;
70
+ }
71
+
72
+ return dialog.showMessageBox(options);
73
+ }
74
+
75
+ async handleServiceStartError(error, failureCount, logFilePath) {
76
+ const context = { failureCount, logFilePath };
77
+
78
+ if (failureCount >= 3) {
79
+ const { response } = await this.showErrorDialog('SERVICE_START_FAILED', '', context);
80
+ return response === 0; // 返回是否需要重启
81
+ } else {
82
+ this.handleError('SERVICE_START_FAILED', error, context);
83
+ return false;
84
+ }
85
+ }
86
+
87
+ handleModuleLoadError(error, context = {}) {
88
+ return this.handleError('MODULE_LOAD_FAILED', error, context);
89
+ }
90
+
91
+ handleServerInitError(error, context = {}) {
92
+ return this.handleError('SERVER_INIT_FAILED', error, context);
93
+ }
94
+
95
+ handleDependencyInstallError(error, context = {}) {
96
+ return this.handleError('DEPENDENCY_INSTALL_FAILED', error, context);
97
+ }
98
+
99
+ handleUpgradeError(error, context = {}) {
100
+ return this.handleError('UPGRADE_FAILED', error, context);
101
+ }
102
+
103
+ handleIconLoadError(error, context = {}) {
104
+ return this.handleError('ICON_LOAD_FAILED', error, context);
105
+ }
106
+ }
107
+
108
+ module.exports = ErrorHandler;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * 事件发射器基类
3
+ * 提供标准化的事件处理功能
4
+ */
5
+
6
+ class EventEmitter {
7
+ constructor() {
8
+ this.listeners = {};
9
+ }
10
+
11
+ /**
12
+ * 添加事件监听器
13
+ * @param {string} event - 事件名称
14
+ * @param {function} listener - 监听器函数
15
+ */
16
+ on(event, listener) {
17
+ if (!this.listeners[event]) {
18
+ this.listeners[event] = [];
19
+ }
20
+ this.listeners[event].push(listener);
21
+ }
22
+
23
+ /**
24
+ * 移除事件监听器
25
+ * @param {string} event - 事件名称
26
+ * @param {function} listener - 监听器函数
27
+ */
28
+ off(event, listener) {
29
+ if (!this.listeners[event]) return;
30
+
31
+ const index = this.listeners[event].indexOf(listener);
32
+ if (index > -1) {
33
+ this.listeners[event].splice(index, 1);
34
+ }
35
+ }
36
+
37
+ /**
38
+ * 发射事件
39
+ * @param {string} event - 事件名称
40
+ * @param {...any} args - 传递给监听器的参数
41
+ */
42
+ emit(event, ...args) {
43
+ if (!this.listeners[event]) return;
44
+
45
+ this.listeners[event].forEach(listener => {
46
+ try {
47
+ listener(...args);
48
+ } catch (error) {
49
+ console.error(`Error in event listener for ${event}:`, error);
50
+ }
51
+ });
52
+ }
53
+
54
+ /**
55
+ * 移除指定事件的所有监听器
56
+ * @param {string} event - 事件名称
57
+ */
58
+ removeAllListeners(event) {
59
+ if (event) {
60
+ delete this.listeners[event];
61
+ } else {
62
+ this.listeners = {};
63
+ }
64
+ }
65
+
66
+ /**
67
+ * 获取指定事件的监听器数量
68
+ * @param {string} event - 事件名称
69
+ * @returns {number} - 监听器数量
70
+ */
71
+ listenerCount(event) {
72
+ return this.listeners[event] ? this.listeners[event].length : 0;
73
+ }
74
+
75
+ /**
76
+ * 获取所有事件名称
77
+ * @returns {string[]} - 事件名称数组
78
+ */
79
+ eventNames() {
80
+ return Object.keys(this.listeners);
81
+ }
82
+ }
83
+
84
+ module.exports = EventEmitter;
@@ -0,0 +1,108 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { app } = require('electron');
4
+
5
+ class Logger {
6
+ constructor(options = {}) {
7
+ this.logFilePath = options.logFilePath || path.join(app.getPath('userData'), 'prompt-manager-desktop.log');
8
+ this.debugEnabled = options.debugEnabled || false;
9
+ this.logStream = null;
10
+ this.initialized = false;
11
+ }
12
+
13
+ async initialize() {
14
+ if (this.initialized) return;
15
+
16
+ try {
17
+ const logDir = path.dirname(this.logFilePath);
18
+ if (!fs.existsSync(logDir)) {
19
+ fs.mkdirSync(logDir, { recursive: true });
20
+ }
21
+
22
+ this.logStream = fs.createWriteStream(this.logFilePath, { flags: 'a' });
23
+ this.initialized = true;
24
+
25
+ this.info('Logging initialized', { logFile: this.logFilePath });
26
+ this.info(`Debug logging is ${this.debugEnabled ? 'enabled' : 'disabled'}`);
27
+ } catch (error) {
28
+ console.error('Failed to initialize logging:', error);
29
+ throw error;
30
+ }
31
+ }
32
+
33
+ formatMessage(level, message, meta = {}) {
34
+ const timestamp = new Date().toISOString();
35
+ const metaStr = Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : '';
36
+ return `[${timestamp}] [${level.toUpperCase()}] ${message}${metaStr}\n`;
37
+ }
38
+
39
+ write(level, message, meta = {}) {
40
+ if (!this.initialized && level !== 'error') return;
41
+
42
+ const formattedMessage = this.formatMessage(level, message, meta);
43
+
44
+ // 始终输出到控制台
45
+ if (level === 'error') {
46
+ process.stderr.write(formattedMessage);
47
+ } else {
48
+ process.stdout.write(formattedMessage);
49
+ }
50
+
51
+ // 写入日志文件
52
+ if (this.logStream) {
53
+ const shouldWrite = level === 'error' ||
54
+ (this.debugEnabled && ['debug', 'warn'].includes(level)) ||
55
+ (!this.debugEnabled && ['info'].includes(level));
56
+
57
+ if (shouldWrite) {
58
+ this.logStream.write(formattedMessage);
59
+ }
60
+ }
61
+ }
62
+
63
+ info(message, meta = {}) {
64
+ this.write('info', message, meta);
65
+ }
66
+
67
+ error(message, error = null, meta = {}) {
68
+ const errorMeta = { ...meta };
69
+ if (error) {
70
+ errorMeta.error = error.message || String(error);
71
+ errorMeta.stack = error.stack;
72
+ }
73
+ this.write('error', message, errorMeta);
74
+ }
75
+
76
+ warn(message, meta = {}) {
77
+ this.write('warn', message, meta);
78
+ }
79
+
80
+ debug(message, meta = {}) {
81
+ if (this.debugEnabled) {
82
+ this.write('debug', message, meta);
83
+ }
84
+ }
85
+
86
+ setDebugEnabled(enabled) {
87
+ this.debugEnabled = enabled;
88
+ this.info(`Debug logging ${enabled ? 'enabled' : 'disabled'}`);
89
+ }
90
+
91
+ getLogFilePath() {
92
+ return this.logFilePath;
93
+ }
94
+
95
+ async close() {
96
+ if (this.logStream) {
97
+ return new Promise((resolve) => {
98
+ this.logStream.end(() => {
99
+ this.logStream = null;
100
+ this.initialized = false;
101
+ resolve();
102
+ });
103
+ });
104
+ }
105
+ }
106
+ }
107
+
108
+ module.exports = Logger;
@@ -0,0 +1,125 @@
1
+ const { app } = require('electron');
2
+
3
+ /**
4
+ * 应用状态管理器
5
+ * 遵循单一职责原则,集中管理应用状态
6
+ */
7
+
8
+ class AppState {
9
+ constructor() {
10
+ this.state = {
11
+ service: 'stopped', // stopped | starting | running | stopping | error
12
+ server: null,
13
+ module: null,
14
+ moduleVersion: 0,
15
+ moduleLoading: null,
16
+ isQuitting: false,
17
+ runtimeRoot: null,
18
+ failureCount: 0,
19
+ debugEnabled: false
20
+ };
21
+
22
+ this.clickCounters = {
23
+ aboutButton: { count: 0, lastTime: 0 },
24
+ aboutWindow: { count: 0, lastTime: 0 }
25
+ };
26
+
27
+ this.ui = {
28
+ tray: null,
29
+ adminWindow: null,
30
+ aboutWindow: null
31
+ };
32
+ }
33
+
34
+ get(key) {
35
+ return key ? this.state[key] : { ...this.state };
36
+ }
37
+
38
+ set(key, value) {
39
+ if (typeof key === 'object') {
40
+ Object.assign(this.state, key);
41
+ } else {
42
+ this.state[key] = value;
43
+ }
44
+ return this;
45
+ }
46
+
47
+ getServiceStatus() {
48
+ const statusMap = {
49
+ running: '运行中',
50
+ starting: '启动中',
51
+ stopping: '停止中',
52
+ error: '启动失败',
53
+ stopped: '已停止'
54
+ };
55
+ return statusMap[this.state.service] || '未知状态';
56
+ }
57
+
58
+ getClickCounter(type) {
59
+ return this.clickCounters[type] || { count: 0, lastTime: 0 };
60
+ }
61
+
62
+ updateClickCounter(type, increment = 1) {
63
+ const now = Date.now();
64
+ const counter = this.clickCounters[type];
65
+
66
+ if (now - counter.lastTime > 3000) {
67
+ counter.count = 0;
68
+ }
69
+
70
+ counter.count += increment;
71
+ counter.lastTime = now;
72
+
73
+ return counter.count;
74
+ }
75
+
76
+ resetClickCounter(type) {
77
+ this.clickCounters[type] = { count: 0, lastTime: 0 };
78
+ return this;
79
+ }
80
+
81
+ getUI(component) {
82
+ return component ? this.ui[component] : { ...this.ui };
83
+ }
84
+
85
+ setUI(component, instance) {
86
+ this.ui[component] = instance;
87
+ return this;
88
+ }
89
+
90
+ isServiceRunning() {
91
+ return this.state.service === 'running';
92
+ }
93
+
94
+ isServiceStarting() {
95
+ return this.state.service === 'starting';
96
+ }
97
+
98
+ isServiceStopping() {
99
+ return this.state.service === 'stopping';
100
+ }
101
+
102
+ canStartService() {
103
+ return ['stopped', 'error'].includes(this.state.service);
104
+ }
105
+
106
+ canStopService() {
107
+ return ['running', 'error'].includes(this.state.service);
108
+ }
109
+
110
+ incrementFailureCount() {
111
+ this.state.failureCount++;
112
+ return this.state.failureCount;
113
+ }
114
+
115
+ resetFailureCount() {
116
+ this.state.failureCount = 0;
117
+ return this;
118
+ }
119
+
120
+ shouldPromptForRestart() {
121
+ return this.state.failureCount >= 3;
122
+ }
123
+ }
124
+
125
+ module.exports = AppState;