@mcp-shark/mcp-shark 1.5.3 → 1.5.5

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 (188) hide show
  1. package/README.md +32 -96
  2. package/bin/mcp-shark.js +1 -1
  3. package/core/configs/codex.js +68 -0
  4. package/core/configs/environment.js +51 -0
  5. package/{lib/common → core}/configs/index.js +16 -1
  6. package/core/constants/Defaults.js +15 -0
  7. package/core/constants/HttpStatus.js +14 -0
  8. package/core/constants/Server.js +20 -0
  9. package/core/constants/StatusCodes.js +25 -0
  10. package/core/constants/index.js +7 -0
  11. package/core/container/DependencyContainer.js +179 -0
  12. package/core/db/init.js +33 -0
  13. package/core/index.js +10 -0
  14. package/{mcp-server/lib/common/error.js → core/libraries/ErrorLibrary.js} +4 -0
  15. package/core/libraries/LoggerLibrary.js +91 -0
  16. package/core/libraries/SerializationLibrary.js +32 -0
  17. package/core/libraries/bootstrap-logger.js +19 -0
  18. package/core/libraries/errors/ApplicationError.js +97 -0
  19. package/core/libraries/index.js +17 -0
  20. package/{mcp-server/lib → core/mcp-server}/auditor/audit.js +77 -53
  21. package/core/mcp-server/index.js +192 -0
  22. package/{mcp-server/lib → core/mcp-server}/server/external/all.js +1 -1
  23. package/core/mcp-server/server/external/config.js +75 -0
  24. package/{mcp-server/lib → core/mcp-server}/server/external/single/client.js +1 -1
  25. package/{mcp-server/lib → core/mcp-server}/server/external/single/request.js +1 -1
  26. package/{mcp-server/lib → core/mcp-server}/server/external/single/run.js +20 -11
  27. package/{mcp-server/lib → core/mcp-server}/server/external/single/transport.js +1 -1
  28. package/{mcp-server/lib → core/mcp-server}/server/internal/handlers/error.js +1 -1
  29. package/core/mcp-server/server/internal/handlers/prompts-get.js +28 -0
  30. package/core/mcp-server/server/internal/handlers/prompts-list.js +21 -0
  31. package/core/mcp-server/server/internal/handlers/resources-list.js +21 -0
  32. package/core/mcp-server/server/internal/handlers/resources-read.js +28 -0
  33. package/core/mcp-server/server/internal/handlers/tools-call.js +44 -0
  34. package/core/mcp-server/server/internal/handlers/tools-list.js +23 -0
  35. package/core/mcp-server/server/internal/run.js +53 -0
  36. package/{mcp-server/lib → core/mcp-server}/server/internal/server.js +11 -1
  37. package/core/models/ConversationFilters.js +31 -0
  38. package/core/models/ExportFormat.js +8 -0
  39. package/core/models/RequestFilters.js +43 -0
  40. package/core/models/SessionFilters.js +23 -0
  41. package/core/models/index.js +8 -0
  42. package/core/repositories/AuditRepository.js +233 -0
  43. package/core/repositories/ConversationRepository.js +182 -0
  44. package/core/repositories/PacketRepository.js +237 -0
  45. package/core/repositories/SchemaRepository.js +107 -0
  46. package/core/repositories/SessionRepository.js +59 -0
  47. package/core/repositories/StatisticsRepository.js +54 -0
  48. package/core/repositories/index.js +10 -0
  49. package/core/services/AuditService.js +144 -0
  50. package/core/services/BackupService.js +222 -0
  51. package/core/services/ConfigDetectionService.js +89 -0
  52. package/core/services/ConfigFileService.js +210 -0
  53. package/core/services/ConfigPatchingService.js +137 -0
  54. package/core/services/ConfigService.js +250 -0
  55. package/core/services/ConfigTransformService.js +178 -0
  56. package/core/services/ConversationService.js +19 -0
  57. package/core/services/ExportService.js +117 -0
  58. package/core/services/LogService.js +64 -0
  59. package/core/services/McpClientService.js +235 -0
  60. package/core/services/McpDiscoveryService.js +107 -0
  61. package/core/services/RequestService.js +56 -0
  62. package/core/services/ScanCacheService.js +242 -0
  63. package/core/services/ScanService.js +167 -0
  64. package/core/services/ServerManagementService.js +206 -0
  65. package/core/services/SessionService.js +34 -0
  66. package/core/services/SettingsService.js +163 -0
  67. package/core/services/StatisticsService.js +64 -0
  68. package/core/services/TokenService.js +94 -0
  69. package/core/services/index.js +25 -0
  70. package/core/services/parsers/ConfigParserFactory.js +113 -0
  71. package/core/services/parsers/JsonConfigParser.js +66 -0
  72. package/core/services/parsers/LegacyJsonConfigParser.js +71 -0
  73. package/core/services/parsers/TomlConfigParser.js +87 -0
  74. package/core/services/parsers/index.js +4 -0
  75. package/{ui/server → core}/utils/scan-cache/directory.js +1 -1
  76. package/core/utils/validation.js +77 -0
  77. package/package.json +14 -11
  78. package/ui/dist/assets/index-CArYxKxS.js +35 -0
  79. package/ui/dist/index.html +1 -1
  80. package/ui/server/controllers/BackupController.js +129 -0
  81. package/ui/server/controllers/ConfigController.js +92 -0
  82. package/ui/server/controllers/ConversationController.js +41 -0
  83. package/ui/server/controllers/LogController.js +44 -0
  84. package/ui/server/controllers/McpClientController.js +60 -0
  85. package/ui/server/controllers/McpDiscoveryController.js +44 -0
  86. package/ui/server/controllers/RequestController.js +129 -0
  87. package/ui/server/controllers/ScanController.js +122 -0
  88. package/ui/server/controllers/ServerManagementController.js +134 -0
  89. package/ui/server/controllers/SessionController.js +57 -0
  90. package/ui/server/controllers/SettingsController.js +24 -0
  91. package/ui/server/controllers/StatisticsController.js +54 -0
  92. package/ui/server/controllers/TokenController.js +58 -0
  93. package/ui/server/controllers/index.js +17 -0
  94. package/ui/server/routes/backups/index.js +15 -9
  95. package/ui/server/routes/composite/index.js +62 -32
  96. package/ui/server/routes/composite/servers.js +20 -15
  97. package/ui/server/routes/config.js +13 -172
  98. package/ui/server/routes/conversations.js +9 -19
  99. package/ui/server/routes/help.js +4 -3
  100. package/ui/server/routes/logs.js +14 -26
  101. package/ui/server/routes/playground.js +11 -174
  102. package/ui/server/routes/requests.js +12 -232
  103. package/ui/server/routes/sessions.js +10 -21
  104. package/ui/server/routes/settings.js +10 -192
  105. package/ui/server/routes/smartscan.js +26 -15
  106. package/ui/server/routes/statistics.js +8 -79
  107. package/ui/server/setup.js +162 -0
  108. package/ui/server/swagger/paths/backups.js +151 -0
  109. package/ui/server/swagger/paths/components.js +76 -0
  110. package/ui/server/swagger/paths/config.js +117 -0
  111. package/ui/server/swagger/paths/conversations.js +29 -0
  112. package/ui/server/swagger/paths/help.js +82 -0
  113. package/ui/server/swagger/paths/logs.js +87 -0
  114. package/ui/server/swagger/paths/playground.js +49 -0
  115. package/ui/server/swagger/paths/requests.js +178 -0
  116. package/ui/server/swagger/paths/serverManagement.js +169 -0
  117. package/ui/server/swagger/paths/sessions.js +61 -0
  118. package/ui/server/swagger/paths/settings.js +31 -0
  119. package/ui/server/swagger/paths/smartScan/discovery.js +97 -0
  120. package/ui/server/swagger/paths/smartScan/index.js +13 -0
  121. package/ui/server/swagger/paths/smartScan/scans.js +151 -0
  122. package/ui/server/swagger/paths/smartScan/token.js +71 -0
  123. package/ui/server/swagger/paths/statistics.js +40 -0
  124. package/ui/server/swagger/paths.js +38 -0
  125. package/ui/server/swagger/swagger.js +37 -0
  126. package/ui/server/utils/cleanup.js +99 -0
  127. package/ui/server/utils/config.js +18 -96
  128. package/ui/server/utils/errorHandler.js +43 -0
  129. package/ui/server/utils/logger.js +2 -2
  130. package/ui/server/utils/paths.js +27 -30
  131. package/ui/server/utils/port.js +21 -21
  132. package/ui/server/utils/process.js +18 -10
  133. package/ui/server/utils/processState.js +17 -0
  134. package/ui/server/utils/signals.js +34 -0
  135. package/ui/server/websocket/broadcast.js +33 -0
  136. package/ui/server/websocket/handler.js +52 -0
  137. package/ui/server.js +51 -230
  138. package/ui/src/App.jsx +2 -0
  139. package/ui/src/CompositeSetup.jsx +23 -9
  140. package/ui/src/PacketFilters.jsx +17 -3
  141. package/ui/src/components/AlertModal.jsx +116 -0
  142. package/ui/src/components/App/ApiDocsButton.jsx +57 -0
  143. package/ui/src/components/App/useAppState.js +43 -1
  144. package/ui/src/components/BackupList.jsx +27 -3
  145. package/ui/src/utils/requestPairing.js +35 -36
  146. package/ui/src/utils/requestUtils.js +1 -0
  147. package/lib/common/db/init.js +0 -132
  148. package/lib/common/db/logger.js +0 -349
  149. package/lib/common/db/query.js +0 -403
  150. package/lib/common/logger.js +0 -90
  151. package/mcp-server/index.js +0 -111
  152. package/mcp-server/lib/server/external/config.js +0 -57
  153. package/mcp-server/lib/server/internal/handlers/prompts-get.js +0 -20
  154. package/mcp-server/lib/server/internal/handlers/prompts-list.js +0 -13
  155. package/mcp-server/lib/server/internal/handlers/resources-list.js +0 -13
  156. package/mcp-server/lib/server/internal/handlers/resources-read.js +0 -20
  157. package/mcp-server/lib/server/internal/handlers/tools-call.js +0 -35
  158. package/mcp-server/lib/server/internal/handlers/tools-list.js +0 -15
  159. package/mcp-server/lib/server/internal/run.js +0 -37
  160. package/mcp-server/mcp-shark.js +0 -22
  161. package/ui/dist/assets/index-CFHeMNwd.js +0 -35
  162. package/ui/server/routes/backups/deleteBackup.js +0 -54
  163. package/ui/server/routes/backups/listBackups.js +0 -75
  164. package/ui/server/routes/backups/restoreBackup.js +0 -83
  165. package/ui/server/routes/backups/viewBackup.js +0 -47
  166. package/ui/server/routes/composite/setup.js +0 -129
  167. package/ui/server/routes/composite/status.js +0 -7
  168. package/ui/server/routes/composite/stop.js +0 -39
  169. package/ui/server/routes/composite/utils.js +0 -45
  170. package/ui/server/routes/smartscan/discover.js +0 -118
  171. package/ui/server/routes/smartscan/scans/clearCache.js +0 -23
  172. package/ui/server/routes/smartscan/scans/createBatchScans.js +0 -124
  173. package/ui/server/routes/smartscan/scans/createScan.js +0 -43
  174. package/ui/server/routes/smartscan/scans/getCachedResults.js +0 -52
  175. package/ui/server/routes/smartscan/scans/getScan.js +0 -42
  176. package/ui/server/routes/smartscan/scans/listScans.js +0 -25
  177. package/ui/server/routes/smartscan/scans.js +0 -13
  178. package/ui/server/routes/smartscan/token.js +0 -57
  179. package/ui/server/utils/config-update.js +0 -240
  180. package/ui/server/utils/scan-cache/all-results.js +0 -197
  181. package/ui/server/utils/scan-cache/file-operations.js +0 -107
  182. package/ui/server/utils/scan-cache/hash.js +0 -47
  183. package/ui/server/utils/scan-cache/server-operations.js +0 -85
  184. package/ui/server/utils/scan-cache.js +0 -12
  185. package/ui/server/utils/smartscan-token.js +0 -43
  186. /package/{mcp-server/lib → core/mcp-server}/server/external/kv.js +0 -0
  187. /package/{mcp-server/lib → core/mcp-server}/server/internal/handlers/common.js +0 -0
  188. /package/{mcp-server/lib → core/mcp-server}/server/internal/session.js +0 -0
@@ -0,0 +1,206 @@
1
+ import { initAuditLogger, startMcpSharkServer } from '#core/mcp-server/index.js';
2
+
3
+ /**
4
+ * Service for managing MCP Shark server lifecycle
5
+ * Handles server startup, shutdown, and status
6
+ */
7
+ export class ServerManagementService {
8
+ constructor(configService, configPatchingService, logger) {
9
+ this.configService = configService;
10
+ this.configPatchingService = configPatchingService;
11
+ this.logger = logger;
12
+ this.serverInstance = null;
13
+ }
14
+
15
+ /**
16
+ * Setup and start MCP Shark server
17
+ * Orchestrates the entire setup process: config processing, patching, and server startup
18
+ * @param {Object} options - Setup options
19
+ * @param {string} [options.filePath] - Path to config file
20
+ * @param {string} [options.fileContent] - Config file content
21
+ * @param {Array} [options.selectedServices] - Selected services to include
22
+ * @param {number} [options.port=9851] - Server port
23
+ * @param {Function} [options.onError] - Error callback
24
+ * @param {Function} [options.onReady] - Ready callback
25
+ * @returns {Promise<Object>} Setup result with convertedConfig, updatedConfig, filePath
26
+ */
27
+ async setup(options = {}) {
28
+ const { filePath, fileContent, selectedServices, port = 9851, onError, onReady } = options;
29
+
30
+ if (!filePath && !fileContent) {
31
+ return {
32
+ success: false,
33
+ error: 'Either filePath or fileContent is required',
34
+ };
35
+ }
36
+
37
+ // If filePath is provided, restore original config if already patched
38
+ // This ensures processSetup reads the original config, not the patched one
39
+ let restoreWarning = null;
40
+ if (filePath && !fileContent) {
41
+ const restoreResult = this.configPatchingService.restoreIfPatched(filePath);
42
+ if (restoreResult.warning) {
43
+ restoreWarning = restoreResult.warning;
44
+ }
45
+ }
46
+
47
+ // Process setup
48
+ const setupResult = this.configService.processSetup(filePath, fileContent, selectedServices);
49
+
50
+ if (!setupResult.success) {
51
+ return setupResult;
52
+ }
53
+
54
+ const { fileData, convertedConfig, updatedConfig } = setupResult;
55
+ const mcpsJsonPath = this.configService.getMcpConfigPath();
56
+
57
+ // Write converted config to MCP Shark config path
58
+ this.configService.writeConfigAsJson(mcpsJsonPath, convertedConfig);
59
+
60
+ // Stop existing server if running
61
+ if (this.serverInstance?.stop) {
62
+ await this.stopServer();
63
+ }
64
+
65
+ // Start server
66
+ await this.startServer({
67
+ configPath: mcpsJsonPath,
68
+ port,
69
+ onError: (err) => {
70
+ if (onError) {
71
+ onError(err);
72
+ }
73
+ throw err;
74
+ },
75
+ onReady: () => {
76
+ if (onReady) {
77
+ onReady();
78
+ }
79
+ },
80
+ });
81
+
82
+ // Patch the original config file if it exists
83
+ let patchWarning = null;
84
+ if (fileData.resolvedFilePath && this.configService.fileExists(fileData.resolvedFilePath)) {
85
+ const patchResult = this.configPatchingService.patchConfigFile(
86
+ fileData.resolvedFilePath,
87
+ updatedConfig
88
+ );
89
+ if (patchResult.warning) {
90
+ patchWarning = patchResult.warning;
91
+ }
92
+ }
93
+
94
+ return {
95
+ success: true,
96
+ message: 'MCP Shark server started successfully and config file updated',
97
+ convertedConfig,
98
+ updatedConfig,
99
+ filePath: fileData.resolvedFilePath || null,
100
+ backupPath: null, // TODO: Implement backup creation in BackupService
101
+ warning: restoreWarning || patchWarning || undefined,
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Start MCP Shark server
107
+ */
108
+ async startServer(options = {}) {
109
+ const { configPath, port = 9851, onError, onReady } = options;
110
+
111
+ const mcpsJsonPath = configPath || this.configService.getMcpConfigPath();
112
+
113
+ if (this.serverInstance?.stop) {
114
+ await this.stopServer();
115
+ }
116
+
117
+ this.logger?.info({ path: mcpsJsonPath }, 'Starting MCP-Shark server as library...');
118
+
119
+ const auditLogger = initAuditLogger(this.logger);
120
+
121
+ const serverInstance = await startMcpSharkServer({
122
+ configPath: mcpsJsonPath,
123
+ port,
124
+ auditLogger,
125
+ onError: (err) => {
126
+ this.logger?.error({ error: err.message }, 'Failed to start mcp-shark server');
127
+ this.serverInstance = null;
128
+ if (onError) {
129
+ onError(err);
130
+ }
131
+ throw err;
132
+ },
133
+ onReady: () => {
134
+ this.logger?.info('MCP Shark server is ready!');
135
+ if (onReady) {
136
+ onReady();
137
+ }
138
+ },
139
+ });
140
+
141
+ this.serverInstance = serverInstance;
142
+ this.logger?.info('MCP Shark server started successfully');
143
+
144
+ return serverInstance;
145
+ }
146
+
147
+ /**
148
+ * Stop MCP Shark server
149
+ */
150
+ async stopServer() {
151
+ if (this.serverInstance?.stop) {
152
+ await this.serverInstance.stop();
153
+ this.serverInstance = null;
154
+ this.logger?.info('MCP Shark server stopped');
155
+ return true;
156
+ }
157
+ return false;
158
+ }
159
+
160
+ /**
161
+ * Get server status
162
+ */
163
+ getServerStatus() {
164
+ return {
165
+ running: this.serverInstance !== null,
166
+ pid: null, // No process PID when using library
167
+ };
168
+ }
169
+
170
+ /**
171
+ * Get current server instance
172
+ */
173
+ getServerInstance() {
174
+ return this.serverInstance;
175
+ }
176
+
177
+ /**
178
+ * Set server instance (for external management)
179
+ */
180
+ setServerInstance(instance) {
181
+ this.serverInstance = instance;
182
+ }
183
+
184
+ /**
185
+ * Shutdown the entire application
186
+ * @param {Function} cleanup - Cleanup function to execute
187
+ */
188
+ async shutdown(cleanup) {
189
+ if (!cleanup || typeof cleanup !== 'function') {
190
+ throw new Error('Cleanup function is required');
191
+ }
192
+
193
+ this.logger?.info('Initiating application shutdown...');
194
+
195
+ // Stop MCP Shark server if running
196
+ if (this.serverInstance?.stop) {
197
+ await this.stopServer();
198
+ }
199
+
200
+ // Execute cleanup
201
+ await cleanup();
202
+
203
+ this.logger?.info('Application shutdown complete');
204
+ return { success: true, message: 'Application shutdown initiated' };
205
+ }
206
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Service for session-related business logic
3
+ * HTTP-agnostic: accepts models, returns models
4
+ */
5
+ import { Defaults } from '../constants/Defaults.js';
6
+
7
+ export class SessionService {
8
+ constructor(sessionRepository, packetRepository) {
9
+ this.sessionRepository = sessionRepository;
10
+ this.packetRepository = packetRepository;
11
+ }
12
+
13
+ /**
14
+ * Get sessions with filters
15
+ * @param {SessionFilters} filters - Typed filter model
16
+ * @returns {Array} Array of session objects (raw from repository)
17
+ */
18
+ getSessions(filters) {
19
+ const repoFilters = filters.toRepositoryFilters();
20
+ return this.sessionRepository.getSessions(repoFilters);
21
+ }
22
+
23
+ /**
24
+ * Get requests for a specific session
25
+ * @param {string} sessionId - Session ID
26
+ * @param {number} limit - Maximum number of requests to return
27
+ * @returns {Array} Array of packet objects (raw from repository)
28
+ */
29
+ getSessionRequests(sessionId, limit) {
30
+ const parsedLimit =
31
+ limit !== undefined ? Number.parseInt(limit) : Defaults.DEFAULT_SESSION_LIMIT;
32
+ return this.packetRepository.getSessionRequests(sessionId, parsedLimit);
33
+ }
34
+ }
@@ -0,0 +1,163 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { getDatabaseFile, getWorkingDirectory } from '#core/configs/index.js';
5
+ import { getScanResultsDirectory } from '#core/utils/scan-cache/directory.js';
6
+
7
+ /**
8
+ * Service for application settings
9
+ * Handles retrieving application configuration and paths
10
+ */
11
+ export class SettingsService {
12
+ constructor(tokenService, backupService, logger) {
13
+ this.tokenService = tokenService;
14
+ this.backupService = backupService;
15
+ this.logger = logger;
16
+ }
17
+
18
+ /**
19
+ * Convert absolute path to display path (replace home with ~)
20
+ */
21
+ _toDisplayPath(homeDir, absolutePath) {
22
+ return absolutePath.replace(homeDir, '~');
23
+ }
24
+
25
+ /**
26
+ * Get backup count from backup directories
27
+ */
28
+ _getBackupCount(cursorBackupDir, windsurfBackupDir, cursorConfigPath, windsurfConfigPath) {
29
+ try {
30
+ const backupDirs = [cursorBackupDir, windsurfBackupDir];
31
+ const newFormatCount = backupDirs.reduce((count, dir) => {
32
+ if (existsSync(dir)) {
33
+ const files = readdirSync(dir);
34
+ const matchingFiles = files.filter((file) => {
35
+ return /^\.(.+)-mcpshark\.\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}\.json$/.test(file);
36
+ });
37
+ return count + matchingFiles.length;
38
+ }
39
+ return count;
40
+ }, 0);
41
+
42
+ const commonPaths = [cursorConfigPath, windsurfConfigPath];
43
+ const oldFormatCount = commonPaths.reduce((count, configPath) => {
44
+ if (existsSync(`${configPath}.backup`)) {
45
+ return count + 1;
46
+ }
47
+ return count;
48
+ }, 0);
49
+
50
+ return newFormatCount + oldFormatCount;
51
+ } catch (error) {
52
+ this.logger?.error({ error: error.message }, 'Error counting backups');
53
+ return 0;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Get all settings
59
+ */
60
+ getSettings() {
61
+ const homeDir = homedir();
62
+ const workingDir = getWorkingDirectory();
63
+ const databasePath = getDatabaseFile();
64
+ const scanResultsDir = getScanResultsDirectory();
65
+ const tokenMetadata = this.tokenService.getTokenMetadata();
66
+
67
+ const cursorConfigPath = join(homeDir, '.cursor', 'mcp.json');
68
+ const windsurfConfigPath = join(homeDir, '.codeium', 'windsurf', 'mcp_config.json');
69
+
70
+ const cursorBackupDir = join(homeDir, '.cursor');
71
+ const windsurfBackupDir = join(homeDir, '.codeium', 'windsurf');
72
+
73
+ return {
74
+ paths: {
75
+ workingDirectory: {
76
+ absolute: workingDir,
77
+ display: this._toDisplayPath(homeDir, workingDir),
78
+ exists: existsSync(workingDir),
79
+ },
80
+ database: {
81
+ absolute: databasePath,
82
+ display: this._toDisplayPath(homeDir, databasePath),
83
+ exists: existsSync(databasePath),
84
+ },
85
+ smartScanResults: {
86
+ absolute: scanResultsDir,
87
+ display: this._toDisplayPath(homeDir, scanResultsDir),
88
+ exists: existsSync(scanResultsDir),
89
+ },
90
+ smartScanToken: {
91
+ absolute: tokenMetadata.path,
92
+ display: this._toDisplayPath(homeDir, tokenMetadata.path),
93
+ exists: tokenMetadata.exists,
94
+ },
95
+ backupDirectories: {
96
+ cursor: {
97
+ absolute: cursorBackupDir,
98
+ display: this._toDisplayPath(homeDir, cursorBackupDir),
99
+ exists: existsSync(cursorBackupDir),
100
+ },
101
+ windsurf: {
102
+ absolute: windsurfBackupDir,
103
+ display: this._toDisplayPath(homeDir, windsurfBackupDir),
104
+ exists: existsSync(windsurfBackupDir),
105
+ },
106
+ },
107
+ configFiles: {
108
+ cursor: {
109
+ absolute: cursorConfigPath,
110
+ display: this._toDisplayPath(homeDir, cursorConfigPath),
111
+ exists: existsSync(cursorConfigPath),
112
+ },
113
+ windsurf: {
114
+ absolute: windsurfConfigPath,
115
+ display: this._toDisplayPath(homeDir, windsurfConfigPath),
116
+ exists: existsSync(windsurfConfigPath),
117
+ },
118
+ },
119
+ },
120
+ smartScan: {
121
+ token: tokenMetadata.token,
122
+ tokenPath: {
123
+ absolute: tokenMetadata.path,
124
+ display: this._toDisplayPath(homeDir, tokenMetadata.path),
125
+ },
126
+ tokenUpdatedAt: tokenMetadata.updatedAt,
127
+ tokenExists: tokenMetadata.exists,
128
+ },
129
+ database: {
130
+ path: {
131
+ absolute: databasePath,
132
+ display: this._toDisplayPath(homeDir, databasePath),
133
+ },
134
+ exists: existsSync(databasePath),
135
+ },
136
+ system: {
137
+ platform: process.platform,
138
+ homeDirectory: {
139
+ absolute: homeDir,
140
+ display: '~',
141
+ },
142
+ },
143
+ backups: {
144
+ directories: [
145
+ {
146
+ absolute: cursorBackupDir,
147
+ display: this._toDisplayPath(homeDir, cursorBackupDir),
148
+ },
149
+ {
150
+ absolute: windsurfBackupDir,
151
+ display: this._toDisplayPath(homeDir, windsurfBackupDir),
152
+ },
153
+ ],
154
+ count: this._getBackupCount(
155
+ cursorBackupDir,
156
+ windsurfBackupDir,
157
+ cursorConfigPath,
158
+ windsurfConfigPath
159
+ ),
160
+ },
161
+ };
162
+ }
163
+ }
@@ -0,0 +1,64 @@
1
+ import { Defaults } from '../constants/Defaults.js';
2
+ import { StatusCodeRanges } from '../constants/StatusCodes.js';
3
+ /**
4
+ * Service for statistics-related business logic
5
+ * HTTP-agnostic: accepts models, returns models
6
+ */
7
+ import { RequestFilters } from '../models/RequestFilters.js';
8
+
9
+ export class StatisticsService {
10
+ constructor(statisticsRepository, packetRepository, conversationRepository) {
11
+ this.statisticsRepository = statisticsRepository;
12
+ this.packetRepository = packetRepository;
13
+ this.conversationRepository = conversationRepository;
14
+ }
15
+
16
+ /**
17
+ * Get statistics with filters
18
+ * @param {RequestFilters} filters - Typed filter model
19
+ * @returns {Object} Statistics object
20
+ */
21
+ getStatistics(filters) {
22
+ // Get all filtered requests for accurate statistics
23
+ const statsFilters = new RequestFilters({
24
+ ...filters,
25
+ limit: Defaults.STATISTICS_LIMIT,
26
+ offset: Defaults.DEFAULT_OFFSET,
27
+ });
28
+ const repoFilters = statsFilters.toRepositoryFilters();
29
+ const allRequests = this.packetRepository.queryRequests(repoFilters);
30
+
31
+ // Calculate statistics from filtered requests
32
+ const totalPackets = allRequests.length;
33
+ const totalRequests = allRequests.filter((r) => r.direction === 'request').length;
34
+ const totalResponses = allRequests.filter((r) => r.direction === 'response').length;
35
+ const totalErrors = allRequests.filter((r) => {
36
+ if (r.direction === 'response') {
37
+ const statusCode = r.status_code || r.status;
38
+ return (
39
+ statusCode >= StatusCodeRanges.CLIENT_ERROR_MIN ||
40
+ (r.body_json && typeof r.body_json === 'object' && r.body_json.error)
41
+ );
42
+ }
43
+ return false;
44
+ }).length;
45
+
46
+ // Get unique sessions
47
+ const uniqueSessions = new Set();
48
+ for (const r of allRequests) {
49
+ if (r.session_id) {
50
+ uniqueSessions.add(r.session_id);
51
+ }
52
+ }
53
+
54
+ const stats = {
55
+ total_packets: totalPackets,
56
+ total_requests: totalRequests,
57
+ total_responses: totalResponses,
58
+ total_errors: totalErrors,
59
+ unique_sessions: uniqueSessions.size,
60
+ };
61
+
62
+ return stats;
63
+ }
64
+ }
@@ -0,0 +1,94 @@
1
+ import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { getWorkingDirectory, prepareAppDataSpaces } from '#core/configs/index.js';
4
+
5
+ /**
6
+ * Service for Smart Scan token management
7
+ * Handles reading and writing Smart Scan API tokens
8
+ */
9
+ export class TokenService {
10
+ constructor(logger) {
11
+ this.logger = logger;
12
+ this.tokenFileName = 'smart-scan-token.json';
13
+ }
14
+
15
+ /**
16
+ * Get token file path
17
+ */
18
+ getTokenPath() {
19
+ return join(getWorkingDirectory(), this.tokenFileName);
20
+ }
21
+
22
+ /**
23
+ * Read Smart Scan token
24
+ */
25
+ readToken() {
26
+ try {
27
+ const tokenPath = this.getTokenPath();
28
+ if (existsSync(tokenPath)) {
29
+ const content = readFileSync(tokenPath, 'utf8');
30
+ const data = JSON.parse(content);
31
+ return data.token || null;
32
+ }
33
+ return null;
34
+ } catch (error) {
35
+ this.logger?.error({ error: error.message }, 'Error reading Smart Scan token');
36
+ return null;
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Write Smart Scan token
42
+ */
43
+ writeToken(token) {
44
+ try {
45
+ const tokenPath = this.getTokenPath();
46
+ prepareAppDataSpaces();
47
+
48
+ const data = {
49
+ token: token || null,
50
+ updatedAt: new Date().toISOString(),
51
+ };
52
+
53
+ writeFileSync(tokenPath, JSON.stringify(data, null, 2), { mode: 0o600 });
54
+ return true;
55
+ } catch (error) {
56
+ this.logger?.error({ error: error.message }, 'Error writing Smart Scan token');
57
+ return false;
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Get token metadata
63
+ */
64
+ getTokenMetadata() {
65
+ try {
66
+ const tokenPath = this.getTokenPath();
67
+ if (existsSync(tokenPath)) {
68
+ const content = readFileSync(tokenPath, 'utf8');
69
+ const data = JSON.parse(content);
70
+ const stats = statSync(tokenPath);
71
+ return {
72
+ token: data.token || null,
73
+ updatedAt: data.updatedAt || stats.mtime.toISOString(),
74
+ path: tokenPath,
75
+ exists: true,
76
+ };
77
+ }
78
+ return {
79
+ token: null,
80
+ updatedAt: null,
81
+ path: tokenPath,
82
+ exists: false,
83
+ };
84
+ } catch (error) {
85
+ this.logger?.error({ error: error.message }, 'Error reading token metadata');
86
+ return {
87
+ token: null,
88
+ updatedAt: null,
89
+ path: this.getTokenPath(),
90
+ exists: false,
91
+ };
92
+ }
93
+ }
94
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Service layer exports
3
+ * All business logic should go through services
4
+ */
5
+ export { RequestService } from './RequestService.js';
6
+ export { SessionService } from './SessionService.js';
7
+ export { ConversationService } from './ConversationService.js';
8
+ export { StatisticsService } from './StatisticsService.js';
9
+ export { AuditService } from './AuditService.js';
10
+ export { ConfigService } from './ConfigService.js';
11
+ export { ConfigFileService } from './ConfigFileService.js';
12
+ export { ConfigTransformService } from './ConfigTransformService.js';
13
+ export { ConfigDetectionService } from './ConfigDetectionService.js';
14
+ export { ServerManagementService } from './ServerManagementService.js';
15
+ export { BackupService } from './BackupService.js';
16
+ export { LogService } from './LogService.js';
17
+ export { ScanCacheService } from './ScanCacheService.js';
18
+ export { ScanService } from './ScanService.js';
19
+ export { McpDiscoveryService } from './McpDiscoveryService.js';
20
+ export { McpClientService } from './McpClientService.js';
21
+ export { TokenService } from './TokenService.js';
22
+ export { SettingsService } from './SettingsService.js';
23
+ export { ConfigPatchingService } from './ConfigPatchingService.js';
24
+ export { ExportService } from './ExportService.js';
25
+ export * from './parsers/index.js';
@@ -0,0 +1,113 @@
1
+ import * as path from 'node:path';
2
+ import { JsonConfigParser } from './JsonConfigParser.js';
3
+ import { LegacyJsonConfigParser } from './LegacyJsonConfigParser.js';
4
+ import { TomlConfigParser } from './TomlConfigParser.js';
5
+
6
+ /**
7
+ * Factory for creating appropriate config parsers based on file type
8
+ * Provides unified interface for all config format parsing
9
+ */
10
+ export class ConfigParserFactory {
11
+ constructor() {
12
+ this.tomlParser = new TomlConfigParser();
13
+ this.jsonParser = new JsonConfigParser();
14
+ this.legacyParser = new LegacyJsonConfigParser();
15
+ }
16
+
17
+ /**
18
+ * Detect file format from path
19
+ * @param {string} filePath - File path
20
+ * @returns {string} Format type: 'toml', 'json', or null
21
+ */
22
+ detectFormat(filePath) {
23
+ if (!filePath) {
24
+ return null;
25
+ }
26
+ const ext = path.extname(filePath).toLowerCase();
27
+ return ext === '.toml' ? 'toml' : ext === '.json' ? 'json' : null;
28
+ }
29
+
30
+ /**
31
+ * Get appropriate parser for file format
32
+ * @param {string} filePath - File path
33
+ * @returns {TomlConfigParser|JsonConfigParser|LegacyJsonConfigParser} Parser instance
34
+ */
35
+ getParser(filePath) {
36
+ const format = this.detectFormat(filePath);
37
+ if (format === 'toml') {
38
+ return this.tomlParser;
39
+ }
40
+ return this.jsonParser;
41
+ }
42
+
43
+ /**
44
+ * Parse config content and detect format
45
+ * @param {string} content - Config file content
46
+ * @param {string} filePath - File path (optional, for format detection)
47
+ * @returns {Object|null} Parsed config or null on error
48
+ */
49
+ parse(content, filePath = null) {
50
+ const format = filePath ? this.detectFormat(filePath) : null;
51
+
52
+ if (format === 'toml') {
53
+ return this.tomlParser.parse(content);
54
+ }
55
+
56
+ return this.jsonParser.parse(content);
57
+ }
58
+
59
+ /**
60
+ * Normalize config to internal format
61
+ * Handles all formats: TOML (Codex), JSON (standard), JSON (legacy)
62
+ * @param {Object} config - Parsed config object
63
+ * @param {string} filePath - Original file path (optional)
64
+ * @returns {Object|null} Normalized config in internal format
65
+ */
66
+ normalizeToInternalFormat(config, _filePath = null) {
67
+ if (!config || typeof config !== 'object') {
68
+ return null;
69
+ }
70
+
71
+ // Try TOML format first (Codex)
72
+ if (this.tomlParser.isCodexFormat(config)) {
73
+ return this.tomlParser.convertToMcpSharkFormat(config);
74
+ }
75
+
76
+ // Try standard JSON format
77
+ if (this.jsonParser.isMcpServersFormat(config)) {
78
+ return this.jsonParser.normalizeToInternalFormat(config);
79
+ }
80
+
81
+ // Try legacy format
82
+ if (this.legacyParser.isLegacyFormat(config)) {
83
+ return this.legacyParser.convertToInternalFormat(config);
84
+ }
85
+
86
+ // If config has both mcpServers and servers, prefer mcpServers
87
+ if (config.mcpServers && typeof config.mcpServers === 'object') {
88
+ return this.jsonParser.normalizeToInternalFormat(config);
89
+ }
90
+
91
+ // If config has servers, treat as legacy
92
+ if (config.servers && typeof config.servers === 'object') {
93
+ return this.legacyParser.convertToInternalFormat(config);
94
+ }
95
+
96
+ return null;
97
+ }
98
+
99
+ /**
100
+ * Parse and normalize config in one step
101
+ * @param {string} content - Config file content
102
+ * @param {string} filePath - File path
103
+ * @returns {Object|null} Normalized config in internal format or null
104
+ */
105
+ parseAndNormalize(content, filePath) {
106
+ const parsed = this.parse(content, filePath);
107
+ if (!parsed) {
108
+ return null;
109
+ }
110
+
111
+ return this.normalizeToInternalFormat(parsed, filePath);
112
+ }
113
+ }