deepdebug-local-agent 0.3.13 → 0.3.14

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.
package/index.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "deepdebug-local-agent",
3
- "version": "0.3.13",
3
+ "version": "0.3.14",
4
4
  "description": "Insptech AI — DeepDebug Local Agent. Autonomous code debugging agent for production environments.",
5
5
  "type": "module",
6
- "main": "server.js",
6
+ "main": "src/server.js",
7
7
  "bin": {
8
- "deepdebug-local-agent": "./bin/install.js"
8
+ "deepdebug-local-agent": "index.js"
9
9
  },
10
10
  "scripts": {
11
- "start": "node server.js",
12
- "dev": "NODE_ENV=development node server.js",
13
- "mcp": "node mcp-server.js"
11
+ "start": "node src/server.js",
12
+ "dev": "NODE_ENV=development node src/server.js",
13
+ "mcp": "node src/mcp-server.js"
14
14
  },
15
15
  "keywords": [
16
16
  "deepdebug",
@@ -36,6 +36,6 @@
36
36
  "properties-reader": "^2.3.0",
37
37
  "strip-ansi": "^7.1.0",
38
38
  "unidiff": "^1.0.2",
39
- "ws": "^8.19.0"
39
+ "ws": "^8.18.0"
40
40
  }
41
41
  }
package/src/server.js CHANGED
@@ -557,7 +557,10 @@ app.use(cors({
557
557
  // Cloud Run URLs (regex patterns)
558
558
  /https:\/\/.*\.run\.app$/,
559
559
  /https:\/\/.*\.web\.app$/,
560
+ <<<<<<< HEAD
560
561
  // Production frontend
562
+ =======
563
+ >>>>>>> dd7d3ab (fix: add deepdebug.ai to CORS origins)
561
564
  "https://deepdebug.ai",
562
565
  "https://www.deepdebug.ai"
563
566
  ],
@@ -5201,6 +5204,121 @@ async function startWebSocketTunnel(port, gatewayUrl, apiKey, tenantId) {
5201
5204
  return await res.json();
5202
5205
  }
5203
5206
 
5207
+ // ============================================
5208
+ // MCP TOOLS — routed via MCP HTTP Bridge (port 5056)
5209
+ // ============================================
5210
+ case 'mcp.read-file': {
5211
+ const mcpBase = `http://localhost:5056`;
5212
+ const res = await fetch(`${mcpBase}/mcp/read-file`, {
5213
+ method: 'POST',
5214
+ headers: { 'Content-Type': 'application/json' },
5215
+ body: JSON.stringify({
5216
+ workspaceId: params.workspaceId || 'default',
5217
+ path: params.path
5218
+ })
5219
+ });
5220
+ return await res.json();
5221
+ }
5222
+
5223
+ case 'mcp.read-files': {
5224
+ const mcpBase = `http://localhost:5056`;
5225
+ const res = await fetch(`${mcpBase}/mcp/read-files`, {
5226
+ method: 'POST',
5227
+ headers: { 'Content-Type': 'application/json' },
5228
+ body: JSON.stringify({
5229
+ workspaceId: params.workspaceId || 'default',
5230
+ paths: params.paths
5231
+ })
5232
+ });
5233
+ return await res.json();
5234
+ }
5235
+
5236
+ case 'mcp.list-directory': {
5237
+ const mcpBase = `http://localhost:5056`;
5238
+ const res = await fetch(`${mcpBase}/mcp/list-directory`, {
5239
+ method: 'POST',
5240
+ headers: { 'Content-Type': 'application/json' },
5241
+ body: JSON.stringify({
5242
+ workspaceId: params.workspaceId || 'default',
5243
+ path: params.path || '.',
5244
+ maxFiles: params.maxFiles || 500
5245
+ })
5246
+ });
5247
+ return await res.json();
5248
+ }
5249
+
5250
+ case 'mcp.search-code': {
5251
+ const mcpBase = `http://localhost:5056`;
5252
+ const res = await fetch(`${mcpBase}/mcp/search-code`, {
5253
+ method: 'POST',
5254
+ headers: { 'Content-Type': 'application/json' },
5255
+ body: JSON.stringify({
5256
+ workspaceId: params.workspaceId || 'default',
5257
+ query: params.query,
5258
+ filePattern: params.filePattern || '*',
5259
+ maxResults: params.maxResults || 50
5260
+ })
5261
+ });
5262
+ return await res.json();
5263
+ }
5264
+
5265
+ case 'mcp.execute-command': {
5266
+ const mcpBase = `http://localhost:5056`;
5267
+ const res = await fetch(`${mcpBase}/mcp/execute-command`, {
5268
+ method: 'POST',
5269
+ headers: { 'Content-Type': 'application/json' },
5270
+ body: JSON.stringify({
5271
+ workspaceId: params.workspaceId || 'default',
5272
+ command: params.command,
5273
+ timeoutMs: params.timeoutMs || 120000
5274
+ })
5275
+ });
5276
+ return await res.json();
5277
+ }
5278
+
5279
+ case 'mcp.project-info': {
5280
+ const mcpBase = `http://localhost:5056`;
5281
+ const res = await fetch(`${mcpBase}/mcp/project-info`, {
5282
+ method: 'POST',
5283
+ headers: { 'Content-Type': 'application/json' },
5284
+ body: JSON.stringify({
5285
+ workspaceId: params.workspaceId || 'default'
5286
+ })
5287
+ });
5288
+ return await res.json();
5289
+ }
5290
+
5291
+ case 'mcp.apply-patch': {
5292
+ const mcpBase = `http://localhost:5056`;
5293
+ const res = await fetch(`${mcpBase}/mcp/apply-patch`, {
5294
+ method: 'POST',
5295
+ headers: { 'Content-Type': 'application/json' },
5296
+ body: JSON.stringify({
5297
+ workspaceId: params.workspaceId || 'default',
5298
+ diff: params.diff
5299
+ })
5300
+ });
5301
+ return await res.json();
5302
+ }
5303
+
5304
+ case 'mcp.open': {
5305
+ const mcpBase = `http://localhost:5056`;
5306
+ // Also open in wsManager for backward compat
5307
+ if (params.root) {
5308
+ await wsManager.open(params.workspaceId || 'default', params.root);
5309
+ WORKSPACE_ROOT = params.root;
5310
+ }
5311
+ const res = await fetch(`${mcpBase}/mcp/open`, {
5312
+ method: 'POST',
5313
+ headers: { 'Content-Type': 'application/json' },
5314
+ body: JSON.stringify({
5315
+ workspaceId: params.workspaceId || 'default',
5316
+ root: params.root
5317
+ })
5318
+ });
5319
+ return await res.json();
5320
+ }
5321
+
5204
5322
  default:
5205
5323
  return { error: `Unknown command: ${command}` };
5206
5324
  }
@@ -1,446 +0,0 @@
1
- import path from "path";
2
- import { readFile, exists } from "../fs-utils.js";
3
- import yaml from "js-yaml";
4
-
5
- /**
6
- * ConfigAnalyzer
7
- *
8
- * Analyzes application configuration to extract:
9
- * - Server port
10
- * - Environment variables
11
- * - Database connections
12
- * - API keys and secrets (obfuscated)
13
- * - Spring profiles
14
- */
15
- export class ConfigAnalyzer {
16
- constructor(workspaceRoot) {
17
- this.workspaceRoot = workspaceRoot;
18
- }
19
-
20
- /**
21
- * Find all configuration files
22
- */
23
- async findConfigFiles() {
24
- const configPatterns = [
25
- "application.yml",
26
- "application.yaml",
27
- "application.properties",
28
- "application-dev.yml",
29
- "application-dev.yaml",
30
- "application-dev.properties",
31
- "application-local.yml",
32
- "application-local.yaml",
33
- "application-local.properties",
34
- "application-prod.yml",
35
- "application-prod.yaml",
36
- "application-prod.properties",
37
- "bootstrap.yml",
38
- "bootstrap.yaml",
39
- ".env",
40
- ".env.local",
41
- ".env.development",
42
- "config/application.yml",
43
- "config/application.yaml"
44
- ];
45
-
46
- const srcPaths = [
47
- "src/main/resources/",
48
- "src/main/java/resources/",
49
- ""
50
- ];
51
-
52
- const foundConfigs = [];
53
-
54
- for (const srcPath of srcPaths) {
55
- for (const configPattern of configPatterns) {
56
- const fullPath = path.join(this.workspaceRoot, srcPath, configPattern);
57
- if (await exists(fullPath)) {
58
- foundConfigs.push(path.join(srcPath, configPattern));
59
- }
60
- }
61
- }
62
-
63
- return foundConfigs;
64
- }
65
-
66
- /**
67
- * Analyze main configuration
68
- */
69
- async analyze() {
70
- const configFiles = await this.findConfigFiles();
71
- const configs = [];
72
-
73
- for (const configFile of configFiles) {
74
- const config = await this.analyzeConfigFile(configFile);
75
- if (config) {
76
- configs.push(config);
77
- }
78
- }
79
-
80
- // Merge all configs into a unified view
81
- const merged = this.mergeConfigs(configs);
82
-
83
- // Extract server config and add available profiles
84
- const serverConfig = this.extractServerConfig(merged);
85
- serverConfig.availableProfiles = this.detectProfiles(configFiles);
86
-
87
- return {
88
- files: configFiles,
89
- configs: configs,
90
- merged: merged,
91
- server: serverConfig,
92
- database: this.extractDatabaseConfig(merged),
93
- security: this.extractSecurityConfig(merged),
94
- envVars: this.extractEnvVars(configs)
95
- };
96
- }
97
-
98
- /**
99
- * Analyze a single config file
100
- */
101
- async analyzeConfigFile(configPath) {
102
- try {
103
- const fullPath = path.join(this.workspaceRoot, configPath);
104
- const content = await readFile(fullPath, "utf8");
105
- const fileName = path.basename(configPath);
106
-
107
- if (fileName.endsWith(".yml") || fileName.endsWith(".yaml")) {
108
- return {
109
- file: configPath,
110
- type: "yaml",
111
- content: yaml.load(content) || {}
112
- };
113
- } else if (fileName.endsWith(".properties")) {
114
- return {
115
- file: configPath,
116
- type: "properties",
117
- content: this.parseProperties(content)
118
- };
119
- } else if (fileName.startsWith(".env")) {
120
- return {
121
- file: configPath,
122
- type: "env",
123
- content: this.parseEnvFile(content)
124
- };
125
- }
126
-
127
- return null;
128
- } catch (err) {
129
- console.error(`Failed to analyze config ${configPath}:`, err.message);
130
- return null;
131
- }
132
- }
133
-
134
- /**
135
- * Parse .properties file
136
- */
137
- parseProperties(content) {
138
- const result = {};
139
- const lines = content.split("\n");
140
-
141
- for (const line of lines) {
142
- const trimmed = line.trim();
143
- if (!trimmed || trimmed.startsWith("#")) continue;
144
-
145
- const eqIndex = trimmed.indexOf("=");
146
- if (eqIndex > 0) {
147
- const key = trimmed.substring(0, eqIndex).trim();
148
- const value = trimmed.substring(eqIndex + 1).trim();
149
- this.setNestedValue(result, key, value);
150
- }
151
- }
152
-
153
- return result;
154
- }
155
-
156
- /**
157
- * Parse .env file
158
- */
159
- parseEnvFile(content) {
160
- const result = {};
161
- const lines = content.split("\n");
162
-
163
- for (const line of lines) {
164
- const trimmed = line.trim();
165
- if (!trimmed || trimmed.startsWith("#")) continue;
166
-
167
- const eqIndex = trimmed.indexOf("=");
168
- if (eqIndex > 0) {
169
- const key = trimmed.substring(0, eqIndex).trim();
170
- let value = trimmed.substring(eqIndex + 1).trim();
171
- // Remove quotes
172
- value = value.replace(/^["']|["']$/g, "");
173
- result[key] = this.obfuscateSensitive(key, value);
174
- }
175
- }
176
-
177
- return result;
178
- }
179
-
180
- /**
181
- * Set nested value from dot notation key
182
- */
183
- setNestedValue(obj, key, value) {
184
- const parts = key.split(".");
185
- let current = obj;
186
-
187
- for (let i = 0; i < parts.length - 1; i++) {
188
- const part = parts[i];
189
- if (!current[part]) {
190
- current[part] = {};
191
- }
192
- current = current[part];
193
- }
194
-
195
- current[parts[parts.length - 1]] = value;
196
- }
197
-
198
- /**
199
- * Merge multiple config files
200
- */
201
- mergeConfigs(configs) {
202
- const merged = {};
203
-
204
- for (const config of configs) {
205
- if (config && config.content) {
206
- this.deepMerge(merged, config.content);
207
- }
208
- }
209
-
210
- return merged;
211
- }
212
-
213
- /**
214
- * Deep merge two objects
215
- */
216
- deepMerge(target, source) {
217
- for (const key in source) {
218
- if (source[key] instanceof Object && key in target) {
219
- this.deepMerge(target[key], source[key]);
220
- } else {
221
- target[key] = source[key];
222
- }
223
- }
224
- return target;
225
- }
226
-
227
- /**
228
- * Extract server configuration
229
- */
230
- extractServerConfig(config) {
231
- const server = {
232
- port: 8080, // default
233
- contextPath: "/",
234
- ssl: false,
235
- activeProfile: "dev" // default profile
236
- };
237
-
238
- // Spring Boot server config
239
- if (config.server) {
240
- server.port = config.server.port || server.port;
241
- server.contextPath = config.server.servlet?.["context-path"] ||
242
- config.server["servlet.context-path"] ||
243
- config.server.contextPath ||
244
- server.contextPath;
245
- server.ssl = !!config.server.ssl;
246
- }
247
-
248
- // Also check for PORT env var placeholder
249
- if (typeof server.port === "string" && server.port.includes("${")) {
250
- const match = server.port.match(/\$\{([^:}]+)(?::(\d+))?\}/);
251
- if (match) {
252
- server.portEnvVar = match[1];
253
- server.port = parseInt(match[2]) || 8080;
254
- }
255
- }
256
-
257
- // Extract active profile from spring.profiles.active
258
- if (config.spring?.profiles?.active) {
259
- server.activeProfile = config.spring.profiles.active;
260
- }
261
-
262
- return server;
263
- }
264
-
265
- /**
266
- * Extract database configuration
267
- */
268
- extractDatabaseConfig(config) {
269
- const database = {
270
- type: null,
271
- host: null,
272
- port: null,
273
- name: null,
274
- configured: false
275
- };
276
-
277
- // Spring Boot data source
278
- const datasource = config.spring?.datasource;
279
- if (datasource) {
280
- database.configured = true;
281
- database.url = this.obfuscateSensitive("url", datasource.url);
282
-
283
- // Parse URL to extract type
284
- if (datasource.url) {
285
- if (datasource.url.includes("mysql")) database.type = "mysql";
286
- else if (datasource.url.includes("postgresql") || datasource.url.includes("postgres")) database.type = "postgresql";
287
- else if (datasource.url.includes("mongodb")) database.type = "mongodb";
288
- else if (datasource.url.includes("h2")) database.type = "h2";
289
- else if (datasource.url.includes("oracle")) database.type = "oracle";
290
- else if (datasource.url.includes("sqlserver")) database.type = "sqlserver";
291
- }
292
- }
293
-
294
- // MongoDB
295
- const mongodb = config.spring?.data?.mongodb;
296
- if (mongodb) {
297
- database.configured = true;
298
- database.type = "mongodb";
299
- database.uri = this.obfuscateSensitive("uri", mongodb.uri);
300
- database.database = mongodb.database;
301
- }
302
-
303
- // Redis
304
- const redis = config.spring?.data?.redis || config.spring?.redis;
305
- if (redis) {
306
- database.redis = {
307
- configured: true,
308
- host: redis.host || "localhost",
309
- port: redis.port || 6379
310
- };
311
- }
312
-
313
- return database;
314
- }
315
-
316
- /**
317
- * Extract security configuration
318
- */
319
- extractSecurityConfig(config) {
320
- const security = {
321
- hasAuth: false,
322
- type: null,
323
- cors: null
324
- };
325
-
326
- // Check for Spring Security
327
- if (config.spring?.security) {
328
- security.hasAuth = true;
329
-
330
- if (config.spring.security.oauth2) {
331
- security.type = "oauth2";
332
- } else if (config.spring.security.jwt) {
333
- security.type = "jwt";
334
- } else {
335
- security.type = "basic";
336
- }
337
- }
338
-
339
- // Check for JWT config
340
- if (config.jwt || config.security?.jwt) {
341
- security.hasAuth = true;
342
- security.type = "jwt";
343
- }
344
-
345
- return security;
346
- }
347
-
348
- /**
349
- * Extract environment variables referenced in configs
350
- */
351
- extractEnvVars(configs) {
352
- const envVars = new Set();
353
-
354
- const extractFromObject = (obj) => {
355
- if (typeof obj === "string") {
356
- const matches = obj.matchAll(/\$\{([^:}]+)(?::[^}]*)?\}/g);
357
- for (const match of matches) {
358
- envVars.add(match[1]);
359
- }
360
- } else if (obj && typeof obj === "object") {
361
- for (const value of Object.values(obj)) {
362
- extractFromObject(value);
363
- }
364
- }
365
- };
366
-
367
- for (const config of configs) {
368
- if (config && config.content) {
369
- extractFromObject(config.content);
370
- }
371
- }
372
-
373
- return Array.from(envVars);
374
- }
375
-
376
- /**
377
- * Obfuscate sensitive values
378
- */
379
- obfuscateSensitive(key, value) {
380
- if (!value || typeof value !== "string") return value;
381
-
382
- const sensitivePatterns = [
383
- "password", "secret", "key", "token", "credential",
384
- "api_key", "apikey", "auth", "jwt"
385
- ];
386
-
387
- const keyLower = key.toLowerCase();
388
- for (const pattern of sensitivePatterns) {
389
- if (keyLower.includes(pattern)) {
390
- if (value.length > 4) {
391
- return value.substring(0, 2) + "****" + value.substring(value.length - 2);
392
- }
393
- return "****";
394
- }
395
- }
396
-
397
- return value;
398
- }
399
-
400
- /**
401
- * Get runtime configuration for starting the server
402
- */
403
- async getRuntimeConfig() {
404
- const analysis = await this.analyze();
405
-
406
- return {
407
- port: analysis.server.port,
408
- contextPath: analysis.server.contextPath,
409
- envVars: analysis.envVars,
410
- profiles: this.detectProfiles(analysis.files),
411
- jvmArgs: this.buildJvmArgs(analysis)
412
- };
413
- }
414
-
415
- /**
416
- * Detect available Spring profiles
417
- */
418
- detectProfiles(configFiles) {
419
- const profiles = ["default"];
420
-
421
- for (const file of configFiles) {
422
- const match = file.match(/application-(\w+)\./);
423
- if (match && match[1] !== "test") {
424
- profiles.push(match[1]);
425
- }
426
- }
427
-
428
- return [...new Set(profiles)];
429
- }
430
-
431
- /**
432
- * Build JVM arguments for running the application
433
- */
434
- buildJvmArgs(analysis) {
435
- const args = [];
436
-
437
- // Add profile if dev or local exists
438
- if (analysis.files.some(f => f.includes("-dev") || f.includes("-local"))) {
439
- args.push("-Dspring.profiles.active=dev,local");
440
- }
441
-
442
- return args;
443
- }
444
- }
445
-
446
- export default ConfigAnalyzer;