@democratize-quality/mcp-server 1.1.8 → 1.2.0
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/LICENSE +658 -12
- package/README.md +11 -6
- package/dist/server.d.ts +41 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +225 -0
- package/dist/server.js.map +1 -0
- package/package.json +24 -25
- package/browserControl.js +0 -113
- package/cli.js +0 -228
- package/mcpServer.js +0 -335
- package/run-server.js +0 -140
- package/src/chatmodes//360/237/214/220 api-generator.chatmode.md" +0 -409
- package/src/chatmodes//360/237/214/220 api-healer.chatmode.md" +0 -494
- package/src/chatmodes//360/237/214/220 api-planner.chatmode.md" +0 -954
- package/src/config/environments/api-only.js +0 -53
- package/src/config/environments/development.js +0 -54
- package/src/config/environments/production.js +0 -69
- package/src/config/index.js +0 -341
- package/src/config/server.js +0 -41
- package/src/config/tools/api.js +0 -67
- package/src/config/tools/browser.js +0 -90
- package/src/config/tools/default.js +0 -32
- package/src/docs/Agent_README.md +0 -310
- package/src/docs/QUICK_REFERENCE.md +0 -111
- package/src/services/browserService.js +0 -325
- package/src/skills/api-planning/SKILL.md +0 -224
- package/src/skills/test-execution/SKILL.md +0 -728
- package/src/skills/test-generation/SKILL.md +0 -309
- package/src/skills/test-healing/SKILL.md +0 -405
- package/src/tools/api/api-generator.js +0 -1865
- package/src/tools/api/api-healer.js +0 -617
- package/src/tools/api/api-planner.js +0 -2598
- package/src/tools/api/api-project-setup.js +0 -313
- package/src/tools/api/api-request.js +0 -641
- package/src/tools/api/api-session-report.js +0 -1278
- package/src/tools/api/api-session-status.js +0 -395
- package/src/tools/api/prompts/README.md +0 -293
- package/src/tools/api/prompts/generation-prompts.js +0 -703
- package/src/tools/api/prompts/healing-prompts.js +0 -195
- package/src/tools/api/prompts/index.js +0 -25
- package/src/tools/api/prompts/orchestrator.js +0 -334
- package/src/tools/api/prompts/validation-rules.js +0 -339
- package/src/tools/base/ToolBase.js +0 -230
- package/src/tools/base/ToolRegistry.js +0 -269
- package/src/tools/browser/advanced/browser-console.js +0 -384
- package/src/tools/browser/advanced/browser-dialog.js +0 -319
- package/src/tools/browser/advanced/browser-evaluate.js +0 -337
- package/src/tools/browser/advanced/browser-file.js +0 -480
- package/src/tools/browser/advanced/browser-keyboard.js +0 -343
- package/src/tools/browser/advanced/browser-mouse.js +0 -332
- package/src/tools/browser/advanced/browser-network.js +0 -421
- package/src/tools/browser/advanced/browser-pdf.js +0 -407
- package/src/tools/browser/advanced/browser-tabs.js +0 -497
- package/src/tools/browser/advanced/browser-wait.js +0 -378
- package/src/tools/browser/click.js +0 -168
- package/src/tools/browser/close.js +0 -60
- package/src/tools/browser/dom.js +0 -70
- package/src/tools/browser/launch.js +0 -67
- package/src/tools/browser/navigate.js +0 -270
- package/src/tools/browser/screenshot.js +0 -351
- package/src/tools/browser/type.js +0 -174
- package/src/tools/index.js +0 -95
- package/src/utils/agentInstaller.js +0 -418
- package/src/utils/browserHelpers.js +0 -83
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
const ToolBase = require('../base/ToolBase');
|
|
2
|
-
const browserService = require('../../services/browserService');
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Browser Type Tool
|
|
6
|
-
* Types text into a specific input field in the browser with Playwright-style locators
|
|
7
|
-
*/
|
|
8
|
-
class BrowserTypeTool extends ToolBase {
|
|
9
|
-
static definition = {
|
|
10
|
-
name: "browser_type",
|
|
11
|
-
description: "Types text into a specific input field in the browser.",
|
|
12
|
-
input_schema: {
|
|
13
|
-
type: "object",
|
|
14
|
-
properties: {
|
|
15
|
-
browserId: {
|
|
16
|
-
type: "string",
|
|
17
|
-
description: "The ID of the browser instance."
|
|
18
|
-
},
|
|
19
|
-
locatorType: {
|
|
20
|
-
type: "string",
|
|
21
|
-
enum: ["css", "xpath", "text", "role", "label", "placeholder", "testId", "altText"],
|
|
22
|
-
default: "css",
|
|
23
|
-
description: "The type of locator to use"
|
|
24
|
-
},
|
|
25
|
-
locatorValue: {
|
|
26
|
-
type: "string",
|
|
27
|
-
description: "The locator value (CSS selector, XPath, text content, etc.)"
|
|
28
|
-
},
|
|
29
|
-
text: {
|
|
30
|
-
type: "string",
|
|
31
|
-
description: "The text to type into the input field."
|
|
32
|
-
},
|
|
33
|
-
// Keep backward compatibility
|
|
34
|
-
selector: {
|
|
35
|
-
type: "string",
|
|
36
|
-
description: "The CSS selector of the input field (deprecated, use locatorType and locatorValue instead)."
|
|
37
|
-
},
|
|
38
|
-
options: {
|
|
39
|
-
type: "object",
|
|
40
|
-
properties: {
|
|
41
|
-
timeout: {
|
|
42
|
-
type: "number",
|
|
43
|
-
default: 30000,
|
|
44
|
-
description: "Timeout in milliseconds"
|
|
45
|
-
},
|
|
46
|
-
delay: {
|
|
47
|
-
type: "number",
|
|
48
|
-
default: 0,
|
|
49
|
-
description: "Delay between keystrokes in milliseconds"
|
|
50
|
-
},
|
|
51
|
-
clear: {
|
|
52
|
-
type: "boolean",
|
|
53
|
-
default: true,
|
|
54
|
-
description: "Clear the field before typing"
|
|
55
|
-
},
|
|
56
|
-
noWaitAfter: {
|
|
57
|
-
type: "boolean",
|
|
58
|
-
default: false,
|
|
59
|
-
description: "Do not wait for navigation after typing"
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
description: "Additional typing options"
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
anyOf: [
|
|
66
|
-
{ required: ["browserId", "locatorType", "locatorValue", "text"] },
|
|
67
|
-
{ required: ["browserId", "selector", "text"] }
|
|
68
|
-
]
|
|
69
|
-
},
|
|
70
|
-
output_schema: {
|
|
71
|
-
type: "object",
|
|
72
|
-
properties: {
|
|
73
|
-
success: { type: "boolean", description: "Indicates if the typing was successful." },
|
|
74
|
-
locatorType: { type: "string", description: "The locator type that was used." },
|
|
75
|
-
locatorValue: { type: "string", description: "The locator value that was used." },
|
|
76
|
-
text: { type: "string", description: "The text that was typed." },
|
|
77
|
-
browserId: { type: "string", description: "The browser instance ID that was used." },
|
|
78
|
-
elementFound: { type: "boolean", description: "Whether the element was found." },
|
|
79
|
-
message: { type: "string", description: "Success or error message." }
|
|
80
|
-
},
|
|
81
|
-
required: ["success", "browserId"]
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
async execute(parameters) {
|
|
86
|
-
const { browserId, locatorType, locatorValue, selector, text, options = {} } = parameters;
|
|
87
|
-
|
|
88
|
-
// Handle backward compatibility
|
|
89
|
-
let finalLocatorType, finalLocatorValue;
|
|
90
|
-
if (selector) {
|
|
91
|
-
finalLocatorType = "css";
|
|
92
|
-
finalLocatorValue = selector;
|
|
93
|
-
} else {
|
|
94
|
-
finalLocatorType = locatorType || "css";
|
|
95
|
-
finalLocatorValue = locatorValue;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
console.error(`[BrowserTypeTool] Typing in browser ${browserId} with locator: ${finalLocatorType}="${finalLocatorValue}"`);
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
// Convert Playwright-style locator to appropriate format for browser service
|
|
102
|
-
const elementSelector = this.convertLocatorToSelector(finalLocatorType, finalLocatorValue);
|
|
103
|
-
|
|
104
|
-
await browserService.typeIntoElement(browserId, elementSelector, text, options);
|
|
105
|
-
|
|
106
|
-
console.error(`[BrowserTypeTool] Successfully typed in browser: ${browserId}`);
|
|
107
|
-
|
|
108
|
-
return {
|
|
109
|
-
success: true,
|
|
110
|
-
browserId: browserId,
|
|
111
|
-
locatorType: finalLocatorType,
|
|
112
|
-
locatorValue: finalLocatorValue,
|
|
113
|
-
text: text,
|
|
114
|
-
elementFound: true,
|
|
115
|
-
message: `Successfully typed "${text}" into element with ${finalLocatorType} locator: ${finalLocatorValue}`
|
|
116
|
-
};
|
|
117
|
-
} catch (error) {
|
|
118
|
-
console.error(`[BrowserTypeTool] Failed to type in element:`, error.message);
|
|
119
|
-
|
|
120
|
-
return {
|
|
121
|
-
success: false,
|
|
122
|
-
browserId: browserId,
|
|
123
|
-
locatorType: finalLocatorType,
|
|
124
|
-
locatorValue: finalLocatorValue,
|
|
125
|
-
text: text,
|
|
126
|
-
elementFound: false,
|
|
127
|
-
message: `Failed to type into element: ${error.message}`
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Convert Playwright-style locator to CSS selector or XPath
|
|
134
|
-
*/
|
|
135
|
-
convertLocatorToSelector(locatorType, locatorValue) {
|
|
136
|
-
switch (locatorType) {
|
|
137
|
-
case "css":
|
|
138
|
-
return locatorValue;
|
|
139
|
-
|
|
140
|
-
case "xpath":
|
|
141
|
-
return locatorValue;
|
|
142
|
-
|
|
143
|
-
case "text":
|
|
144
|
-
// Convert text locator to XPath for input elements
|
|
145
|
-
return `//input[contains(@placeholder, "${locatorValue}")] | //input[contains(@value, "${locatorValue}")] | //textarea[contains(text(), "${locatorValue}")] | //label[contains(text(), "${locatorValue}")]/following-sibling::input | //label[contains(text(), "${locatorValue}")]/following::input[1]`;
|
|
146
|
-
|
|
147
|
-
case "role":
|
|
148
|
-
// Convert role locator to CSS attribute selector
|
|
149
|
-
return `[role="${locatorValue}"]`;
|
|
150
|
-
|
|
151
|
-
case "label":
|
|
152
|
-
// Convert label locator to XPath for label association
|
|
153
|
-
return `//input[@aria-label="${locatorValue}"] | //input[@id=//label[contains(text(), "${locatorValue}")]/@for] | //label[contains(text(), "${locatorValue}")]/following-sibling::input | //label[contains(text(), "${locatorValue}")]/following::input[1]`;
|
|
154
|
-
|
|
155
|
-
case "placeholder":
|
|
156
|
-
// Convert placeholder locator to CSS attribute selector
|
|
157
|
-
return `[placeholder="${locatorValue}"]`;
|
|
158
|
-
|
|
159
|
-
case "testId":
|
|
160
|
-
// Convert test ID to CSS attribute selector (assuming data-testid)
|
|
161
|
-
return `[data-testid="${locatorValue}"]`;
|
|
162
|
-
|
|
163
|
-
case "altText":
|
|
164
|
-
// Convert alt text to CSS attribute selector (less common for input fields)
|
|
165
|
-
return `[alt="${locatorValue}"]`;
|
|
166
|
-
|
|
167
|
-
default:
|
|
168
|
-
// Default to CSS selector
|
|
169
|
-
return locatorValue;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
module.exports = BrowserTypeTool;
|
package/src/tools/index.js
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
const ToolRegistry = require('./base/ToolRegistry');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Initialize and configure the tool registry
|
|
6
|
-
* This file serves as the main entry point for the tools system
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
// Create the global tool registry instance
|
|
10
|
-
const toolRegistry = new ToolRegistry();
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Initialize the tool registry by discovering and loading all tools
|
|
14
|
-
* @param {boolean} debugMode - Whether to enable debug logging
|
|
15
|
-
* @returns {Promise<ToolRegistry>} - The initialized tool registry
|
|
16
|
-
*/
|
|
17
|
-
async function initializeTools(debugMode = false) {
|
|
18
|
-
if (debugMode) {
|
|
19
|
-
console.error('[Tools] Initializing tool system...');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
// Get the tools directory path
|
|
24
|
-
const toolsDir = path.join(__dirname);
|
|
25
|
-
|
|
26
|
-
// Discover and load all tools
|
|
27
|
-
await toolRegistry.discoverTools(toolsDir, debugMode);
|
|
28
|
-
|
|
29
|
-
// Log registry statistics
|
|
30
|
-
const stats = toolRegistry.getStats();
|
|
31
|
-
console.error(`[Tools] Tool system initialized successfully:`);
|
|
32
|
-
console.error(`[Tools] - Total tools: ${stats.total_tools}`);
|
|
33
|
-
if (debugMode) {
|
|
34
|
-
console.error(`[Tools] - Available tools: ${stats.tool_names.join(', ')}`);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return toolRegistry;
|
|
38
|
-
|
|
39
|
-
} catch (error) {
|
|
40
|
-
console.error('[Tools] Failed to initialize tool system:', error.message);
|
|
41
|
-
throw error;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Get the tool registry instance
|
|
47
|
-
* @returns {ToolRegistry} - The tool registry instance
|
|
48
|
-
*/
|
|
49
|
-
function getToolRegistry() {
|
|
50
|
-
return toolRegistry;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Get all tool definitions for MCP tools/list response
|
|
55
|
-
* @returns {Array} - Array of tool definitions
|
|
56
|
-
*/
|
|
57
|
-
function getToolDefinitions() {
|
|
58
|
-
return toolRegistry.getDefinitions();
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Execute a tool by name
|
|
63
|
-
* @param {string} toolName - The name of the tool to execute
|
|
64
|
-
* @param {object} parameters - The parameters to pass to the tool
|
|
65
|
-
* @returns {Promise<object>} - The tool execution result
|
|
66
|
-
*/
|
|
67
|
-
async function executeTool(toolName, parameters) {
|
|
68
|
-
return await toolRegistry.executeTool(toolName, parameters);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Check if a tool is available
|
|
73
|
-
* @param {string} toolName - The name of the tool to check
|
|
74
|
-
* @returns {boolean} - True if the tool is available
|
|
75
|
-
*/
|
|
76
|
-
function isToolAvailable(toolName) {
|
|
77
|
-
return toolRegistry.hasTool(toolName);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Get list of available tool names
|
|
82
|
-
* @returns {Array<string>} - Array of available tool names
|
|
83
|
-
*/
|
|
84
|
-
function getAvailableTools() {
|
|
85
|
-
return toolRegistry.getToolNames();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
module.exports = {
|
|
89
|
-
initializeTools,
|
|
90
|
-
getToolRegistry,
|
|
91
|
-
getToolDefinitions,
|
|
92
|
-
executeTool,
|
|
93
|
-
isToolAvailable,
|
|
94
|
-
getAvailableTools
|
|
95
|
-
};
|
|
@@ -1,418 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const { promisify } = require('util');
|
|
4
|
-
|
|
5
|
-
const readFile = promisify(fs.readFile);
|
|
6
|
-
const writeFile = promisify(fs.writeFile);
|
|
7
|
-
const mkdir = promisify(fs.mkdir);
|
|
8
|
-
const copyFile = promisify(fs.copyFile);
|
|
9
|
-
const stat = promisify(fs.stat);
|
|
10
|
-
const readdir = promisify(fs.readdir);
|
|
11
|
-
const symlink = promisify(fs.symlink);
|
|
12
|
-
const unlink = promisify(fs.unlink);
|
|
13
|
-
const lstat = promisify(fs.lstat);
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Agent Installer - Handles installation of Agent Skills and MCP configuration
|
|
17
|
-
*/
|
|
18
|
-
class AgentInstaller {
|
|
19
|
-
constructor(options = {}) {
|
|
20
|
-
this.verbose = options.verbose || false;
|
|
21
|
-
this.targetDir = options.targetDir || process.cwd();
|
|
22
|
-
this.sourceDir = options.sourceDir || path.join(__dirname, '..', '..');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Main installation function
|
|
27
|
-
*/
|
|
28
|
-
async install() {
|
|
29
|
-
console.log('🚀 Installing Democratize Quality Agent Skills...\n');
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
// Step 1: Install Agent Skills
|
|
33
|
-
await this.installSkills();
|
|
34
|
-
|
|
35
|
-
// Step 2: Install Chat Modes
|
|
36
|
-
await this.installChatModes();
|
|
37
|
-
|
|
38
|
-
// Step 3: Setup MCP configuration
|
|
39
|
-
await this.setupMCPConfiguration();
|
|
40
|
-
|
|
41
|
-
// Step 4: Show completion message
|
|
42
|
-
this.showCompletionMessage();
|
|
43
|
-
|
|
44
|
-
} catch (error) {
|
|
45
|
-
console.error('❌ Installation failed:', error.message);
|
|
46
|
-
if (this.verbose) {
|
|
47
|
-
console.error('Stack trace:', error.stack);
|
|
48
|
-
}
|
|
49
|
-
throw error;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Install Agent Skills to .agents/skills/ with symlinks for compatibility
|
|
55
|
-
*/
|
|
56
|
-
async installSkills() {
|
|
57
|
-
console.log('📁 Installing Agent Skills...');
|
|
58
|
-
|
|
59
|
-
const sourceSkillsDir = path.join(this.sourceDir, 'src', 'skills');
|
|
60
|
-
const primarySkillsDir = path.join(this.targetDir, '.agents', 'skills');
|
|
61
|
-
|
|
62
|
-
// Create primary skills directory
|
|
63
|
-
try {
|
|
64
|
-
await mkdir(primarySkillsDir, { recursive: true });
|
|
65
|
-
} catch (error) {
|
|
66
|
-
if (error.code !== 'EEXIST') {
|
|
67
|
-
throw new Error(`Failed to create .agents/skills directory: ${error.message}`);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Get list of skill directories
|
|
72
|
-
let skillDirs;
|
|
73
|
-
try {
|
|
74
|
-
const allItems = await readdir(sourceSkillsDir);
|
|
75
|
-
// Filter for directories only
|
|
76
|
-
skillDirs = [];
|
|
77
|
-
for (const item of allItems) {
|
|
78
|
-
const itemPath = path.join(sourceSkillsDir, item);
|
|
79
|
-
const itemStat = await stat(itemPath);
|
|
80
|
-
if (itemStat.isDirectory()) {
|
|
81
|
-
skillDirs.push(item);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
} catch (error) {
|
|
85
|
-
throw new Error(`Failed to read source skills directory: ${error.message}`);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (skillDirs.length === 0) {
|
|
89
|
-
console.log(' ⚠️ No skills found to install');
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Copy each skill directory
|
|
94
|
-
for (const skillDir of skillDirs) {
|
|
95
|
-
const sourceSkillPath = path.join(sourceSkillsDir, skillDir);
|
|
96
|
-
const targetSkillPath = path.join(primarySkillsDir, skillDir);
|
|
97
|
-
|
|
98
|
-
try {
|
|
99
|
-
await this.copyDirectory(sourceSkillPath, targetSkillPath);
|
|
100
|
-
console.log(` ✅ ${skillDir}/`);
|
|
101
|
-
} catch (error) {
|
|
102
|
-
console.error(` ❌ Failed to copy ${skillDir}: ${error.message}`);
|
|
103
|
-
throw error;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
console.log('');
|
|
108
|
-
|
|
109
|
-
// Create symlinks for maximum compatibility
|
|
110
|
-
await this.createCompatibilitySymlinks(primarySkillsDir);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Recursively copy a directory
|
|
115
|
-
*/
|
|
116
|
-
async copyDirectory(source, target) {
|
|
117
|
-
await mkdir(target, { recursive: true });
|
|
118
|
-
|
|
119
|
-
const items = await readdir(source);
|
|
120
|
-
|
|
121
|
-
for (const item of items) {
|
|
122
|
-
const sourcePath = path.join(source, item);
|
|
123
|
-
const targetPath = path.join(target, item);
|
|
124
|
-
const itemStat = await stat(sourcePath);
|
|
125
|
-
|
|
126
|
-
if (itemStat.isDirectory()) {
|
|
127
|
-
await this.copyDirectory(sourcePath, targetPath);
|
|
128
|
-
} else {
|
|
129
|
-
await copyFile(sourcePath, targetPath);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Create symlinks for compatibility with different tools
|
|
136
|
-
*/
|
|
137
|
-
async createCompatibilitySymlinks(primarySkillsDir) {
|
|
138
|
-
console.log('🔗 Creating compatibility symlinks...');
|
|
139
|
-
|
|
140
|
-
const symlinks = [
|
|
141
|
-
{ from: primarySkillsDir, to: path.join(this.targetDir, '.github', 'skills') },
|
|
142
|
-
{ from: primarySkillsDir, to: path.join(this.targetDir, '.claude', 'skills') }
|
|
143
|
-
];
|
|
144
|
-
|
|
145
|
-
for (const link of symlinks) {
|
|
146
|
-
try {
|
|
147
|
-
// Check if symlink or directory already exists
|
|
148
|
-
try {
|
|
149
|
-
const linkStat = await lstat(link.to);
|
|
150
|
-
if (linkStat.isSymbolicLink()) {
|
|
151
|
-
// Remove existing symlink
|
|
152
|
-
await unlink(link.to);
|
|
153
|
-
if (this.verbose) {
|
|
154
|
-
console.log(` 🔄 Removed existing symlink: ${path.relative(this.targetDir, link.to)}`);
|
|
155
|
-
}
|
|
156
|
-
} else if (linkStat.isDirectory()) {
|
|
157
|
-
console.log(` ⚠️ Directory already exists, skipping symlink: ${path.relative(this.targetDir, link.to)}`);
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
} catch (error) {
|
|
161
|
-
if (error.code !== 'ENOENT') {
|
|
162
|
-
throw error;
|
|
163
|
-
}
|
|
164
|
-
// Path doesn't exist, proceed with symlink creation
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Create parent directory if it doesn't exist
|
|
168
|
-
const parentDir = path.dirname(link.to);
|
|
169
|
-
await mkdir(parentDir, { recursive: true });
|
|
170
|
-
|
|
171
|
-
// Create relative symlink
|
|
172
|
-
const relativeTarget = path.relative(path.dirname(link.to), link.from);
|
|
173
|
-
await symlink(relativeTarget, link.to, 'dir');
|
|
174
|
-
console.log(` ✅ ${path.relative(this.targetDir, link.to)} → ${path.relative(this.targetDir, link.from)}`);
|
|
175
|
-
} catch (error) {
|
|
176
|
-
console.warn(` ⚠️ Could not create symlink ${path.relative(this.targetDir, link.to)}: ${error.message}`);
|
|
177
|
-
if (this.verbose) {
|
|
178
|
-
console.warn(` (This is not critical - skills will still work from .agents/skills/)`);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
console.log('');
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Install Chat Modes to .github/agents/
|
|
188
|
-
*/
|
|
189
|
-
async installChatModes() {
|
|
190
|
-
console.log('💬 Installing Chat Modes...');
|
|
191
|
-
|
|
192
|
-
const sourceChatModesDir = path.join(this.sourceDir, 'src', 'chatmodes');
|
|
193
|
-
const targetAgentsDir = path.join(this.targetDir, '.github', 'agents');
|
|
194
|
-
|
|
195
|
-
// Create .github/agents directory
|
|
196
|
-
try {
|
|
197
|
-
await mkdir(targetAgentsDir, { recursive: true });
|
|
198
|
-
} catch (error) {
|
|
199
|
-
if (error.code !== 'EEXIST') {
|
|
200
|
-
throw new Error(`Failed to create .github/agents directory: ${error.message}`);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Get list of chatmode files
|
|
205
|
-
let chatModeFiles;
|
|
206
|
-
try {
|
|
207
|
-
const allItems = await readdir(sourceChatModesDir);
|
|
208
|
-
// Filter for .chatmode.md files only
|
|
209
|
-
chatModeFiles = allItems.filter(item => item.endsWith('.chatmode.md'));
|
|
210
|
-
} catch (error) {
|
|
211
|
-
if (error.code === 'ENOENT') {
|
|
212
|
-
console.log(' ⚠️ No chatmodes directory found, skipping...');
|
|
213
|
-
console.log('');
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
throw new Error(`Failed to read source chatmodes directory: ${error.message}`);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (chatModeFiles.length === 0) {
|
|
220
|
-
console.log(' ⚠️ No chatmode files found to install');
|
|
221
|
-
console.log('');
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Copy each chatmode file
|
|
226
|
-
for (const chatModeFile of chatModeFiles) {
|
|
227
|
-
const sourcePath = path.join(sourceChatModesDir, chatModeFile);
|
|
228
|
-
const targetPath = path.join(targetAgentsDir, chatModeFile);
|
|
229
|
-
|
|
230
|
-
try {
|
|
231
|
-
await copyFile(sourcePath, targetPath);
|
|
232
|
-
console.log(` ✅ ${chatModeFile}`);
|
|
233
|
-
} catch (error) {
|
|
234
|
-
console.error(` ❌ Failed to copy ${chatModeFile}: ${error.message}`);
|
|
235
|
-
throw error;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
console.log('');
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Setup MCP configuration in .vscode/mcp.json
|
|
244
|
-
*/
|
|
245
|
-
async setupMCPConfiguration() {
|
|
246
|
-
console.log('⚙️ Setting up MCP configuration...');
|
|
247
|
-
|
|
248
|
-
const vscodeDir = path.join(this.targetDir, '.vscode');
|
|
249
|
-
const mcpConfigPath = path.join(vscodeDir, 'mcp.json');
|
|
250
|
-
|
|
251
|
-
// Create .vscode directory if it doesn't exist
|
|
252
|
-
try {
|
|
253
|
-
await mkdir(vscodeDir, { recursive: true });
|
|
254
|
-
} catch (error) {
|
|
255
|
-
if (error.code !== 'EEXIST') {
|
|
256
|
-
throw new Error(`Failed to create .vscode directory: ${error.message}`);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Check if mcp.json already exists
|
|
261
|
-
let existingConfig = null;
|
|
262
|
-
let configExists = false;
|
|
263
|
-
|
|
264
|
-
try {
|
|
265
|
-
await stat(mcpConfigPath);
|
|
266
|
-
configExists = true;
|
|
267
|
-
console.log(' 📋 Found existing .vscode/mcp.json');
|
|
268
|
-
|
|
269
|
-
const configContent = await readFile(mcpConfigPath, 'utf8');
|
|
270
|
-
existingConfig = JSON.parse(configContent);
|
|
271
|
-
|
|
272
|
-
} catch (error) {
|
|
273
|
-
if (error.code === 'ENOENT') {
|
|
274
|
-
console.log(' 📋 Creating new .vscode/mcp.json');
|
|
275
|
-
configExists = false;
|
|
276
|
-
} else if (error instanceof SyntaxError) {
|
|
277
|
-
throw new Error(`Existing mcp.json is malformed: ${error.message}`);
|
|
278
|
-
} else {
|
|
279
|
-
throw new Error(`Failed to read existing mcp.json: ${error.message}`);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Create new server configuration
|
|
284
|
-
const newServerConfig = {
|
|
285
|
-
type: "stdio",
|
|
286
|
-
command: "npx",
|
|
287
|
-
args: ["@democratize-quality/mcp-server@latest"],
|
|
288
|
-
cwd: "${workspaceFolder}",
|
|
289
|
-
env: {
|
|
290
|
-
OUTPUT_DIR: "./api-test-reports"
|
|
291
|
-
}
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
let finalConfig;
|
|
295
|
-
|
|
296
|
-
if (configExists && existingConfig) {
|
|
297
|
-
// Handle existing configuration
|
|
298
|
-
await this.handleExistingMCPConfig(existingConfig, newServerConfig, mcpConfigPath);
|
|
299
|
-
return;
|
|
300
|
-
} else {
|
|
301
|
-
// Create new configuration
|
|
302
|
-
finalConfig = {
|
|
303
|
-
servers: {
|
|
304
|
-
"democratize-quality": newServerConfig
|
|
305
|
-
},
|
|
306
|
-
inputs: []
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Write the configuration
|
|
311
|
-
try {
|
|
312
|
-
await writeFile(mcpConfigPath, JSON.stringify(finalConfig, null, 2), 'utf8');
|
|
313
|
-
console.log(' ✅ MCP configuration created');
|
|
314
|
-
} catch (error) {
|
|
315
|
-
throw new Error(`Failed to write mcp.json: ${error.message}`);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
console.log('');
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Handle existing MCP configuration
|
|
323
|
-
*/
|
|
324
|
-
async handleExistingMCPConfig(existingConfig, newServerConfig, mcpConfigPath) {
|
|
325
|
-
// Ensure servers object exists
|
|
326
|
-
if (!existingConfig.servers) {
|
|
327
|
-
existingConfig.servers = {};
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Check if democratize-quality server already exists
|
|
331
|
-
if (existingConfig.servers['democratize-quality']) {
|
|
332
|
-
console.log(' 🔍 Found existing "democratize-quality" server configuration');
|
|
333
|
-
|
|
334
|
-
const existingServer = existingConfig.servers['democratize-quality'];
|
|
335
|
-
console.log(' Current configuration:');
|
|
336
|
-
console.log(' ', JSON.stringify(existingServer, null, 4));
|
|
337
|
-
console.log(' \n Proposed configuration:');
|
|
338
|
-
console.log(' ', JSON.stringify(newServerConfig, null, 4));
|
|
339
|
-
|
|
340
|
-
// For now, we'll update automatically with backup
|
|
341
|
-
// In a real implementation, you might want to prompt the user
|
|
342
|
-
console.log(' 💾 Creating backup and updating configuration...');
|
|
343
|
-
|
|
344
|
-
// Create backup
|
|
345
|
-
const backupPath = `${mcpConfigPath}.backup.${new Date().toISOString().replace(/[:.]/g, '-')}`;
|
|
346
|
-
await writeFile(backupPath, JSON.stringify(existingConfig, null, 2), 'utf8');
|
|
347
|
-
console.log(` 📄 Backup created: ${path.basename(backupPath)}`);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// Update configuration
|
|
351
|
-
const updatedConfig = {
|
|
352
|
-
...existingConfig,
|
|
353
|
-
servers: {
|
|
354
|
-
...existingConfig.servers,
|
|
355
|
-
'democratize-quality': newServerConfig
|
|
356
|
-
},
|
|
357
|
-
inputs: existingConfig.inputs || []
|
|
358
|
-
};
|
|
359
|
-
|
|
360
|
-
// Write updated configuration
|
|
361
|
-
try {
|
|
362
|
-
await writeFile(mcpConfigPath, JSON.stringify(updatedConfig, null, 2), 'utf8');
|
|
363
|
-
console.log(' ✅ MCP configuration updated');
|
|
364
|
-
} catch (error) {
|
|
365
|
-
throw new Error(`Failed to update mcp.json: ${error.message}`);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* Show completion message with next steps
|
|
371
|
-
*/
|
|
372
|
-
showCompletionMessage() {
|
|
373
|
-
console.log('🎉 Installation complete!\n');
|
|
374
|
-
|
|
375
|
-
console.log('📁 Agent Skills installed to:');
|
|
376
|
-
console.log(' → .agents/skills/api-planning/');
|
|
377
|
-
console.log(' → .agents/skills/test-generation/');
|
|
378
|
-
console.log(' → .agents/skills/test-execution/');
|
|
379
|
-
console.log(' → .agents/skills/test-healing/');
|
|
380
|
-
console.log(' → .github/skills/ (symlink for GitHub Copilot)');
|
|
381
|
-
console.log(' → .claude/skills/ (symlink for Claude Code)\n');
|
|
382
|
-
|
|
383
|
-
console.log('💬 Chat Modes installed to:');
|
|
384
|
-
console.log(' → .github/agents/🌐 api-planner.chatmode.md');
|
|
385
|
-
console.log(' → .github/agents/🌐 api-generator.chatmode.md');
|
|
386
|
-
console.log(' → .github/agents/🌐 api-healer.chatmode.md\n');
|
|
387
|
-
|
|
388
|
-
console.log('⚙️ MCP configuration updated:');
|
|
389
|
-
console.log(' → .vscode/mcp.json (democratize-quality server added/updated)\n');
|
|
390
|
-
|
|
391
|
-
console.log('🚀 Next steps:');
|
|
392
|
-
console.log(' 1. Restart VS Code / IDE to reload configuration');
|
|
393
|
-
console.log(' 2. Use skills in your AI coding assistant:');
|
|
394
|
-
console.log(' • /api-planning - Create comprehensive API test plans');
|
|
395
|
-
console.log(' • /test-generation - Generate executable API tests');
|
|
396
|
-
console.log(' • /test-execution - Execute API tests from test plans');
|
|
397
|
-
console.log(' • /test-healing - Debug and fix failing API tests');
|
|
398
|
-
console.log(' 3. Skills work automatically when you mention relevant tasks!');
|
|
399
|
-
console.log(' 4. Example: "Help me create an API test plan for my REST API"\n');
|
|
400
|
-
|
|
401
|
-
console.log('✨ Compatible with:');
|
|
402
|
-
console.log(' • GitHub Copilot (VS Code, CLI)');
|
|
403
|
-
console.log(' • Codex CLI (OpenAI)');
|
|
404
|
-
console.log(' • Claude Code');
|
|
405
|
-
|
|
406
|
-
console.log('📚 For more information, visit: https://github.com/uppadhyayraj/democratize-quality-mcp-server');
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/**
|
|
410
|
-
* Rollback installation (future feature)
|
|
411
|
-
*/
|
|
412
|
-
async rollback() {
|
|
413
|
-
console.log('🔄 Rollback functionality will be implemented in a future version');
|
|
414
|
-
// TODO: Implement rollback functionality
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
module.exports = AgentInstaller;
|