chrometools-mcp 1.9.1 → 2.3.2
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/CHANGELOG.md +305 -0
- package/README.md +279 -53
- package/browser/browser-manager.js +206 -0
- package/browser/page-manager.js +298 -0
- package/index.js +625 -1875
- package/package.json +1 -1
- package/recorder/page-object-generator.js +720 -0
- package/recorder/recorder-script.js +63 -9
- package/recorder/scenario-executor.js +47 -27
- package/recorder/scenario-storage.js +251 -29
- package/server/tool-definitions.js +655 -0
- package/server/tool-schemas.js +295 -0
- package/utils/code-generators/code-generator-base.js +61 -0
- package/utils/code-generators/file-appender.js +202 -0
- package/utils/code-generators/playwright-python.js +84 -0
- package/utils/code-generators/playwright-typescript.js +95 -0
- package/utils/code-generators/selenium-java.js +123 -0
- package/utils/code-generators/selenium-python.js +82 -0
- package/utils/css-utils.js +151 -0
- package/utils/image-processing.js +236 -0
- package/utils/platform-utils.js +62 -0
- package/utils/url-to-project.js +141 -0
- package/utils/project-detector.js +0 -87
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* utils/platform-utils.js
|
|
3
|
+
*
|
|
4
|
+
* Platform detection and platform-specific utilities
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync } from 'fs';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Detect WSL environment
|
|
11
|
+
* @returns {boolean} - True if running in WSL
|
|
12
|
+
*/
|
|
13
|
+
export const isWSL = (() => {
|
|
14
|
+
try {
|
|
15
|
+
const proc_version = readFileSync('/proc/version', 'utf8').toLowerCase();
|
|
16
|
+
return proc_version.includes('microsoft') || proc_version.includes('wsl');
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
})();
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Detect Windows environment (including WSL)
|
|
24
|
+
* @returns {boolean} - True if running on Windows or WSL
|
|
25
|
+
*/
|
|
26
|
+
export const isWindows = process.platform === 'win32' || isWSL;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get Chrome executable path based on platform
|
|
30
|
+
* @returns {string} - Path to Chrome executable
|
|
31
|
+
*/
|
|
32
|
+
export function getChromePath() {
|
|
33
|
+
if (process.platform === 'win32') {
|
|
34
|
+
// Native Windows
|
|
35
|
+
return 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe';
|
|
36
|
+
} else if (isWSL) {
|
|
37
|
+
// WSL - use Windows Chrome
|
|
38
|
+
return '/mnt/c/Program Files/Google/Chrome/Application/chrome.exe';
|
|
39
|
+
} else {
|
|
40
|
+
// Linux
|
|
41
|
+
return '/usr/bin/google-chrome';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get temp directory based on platform
|
|
47
|
+
* @returns {string} - Path to temp directory
|
|
48
|
+
*/
|
|
49
|
+
export function getTempDir() {
|
|
50
|
+
if (process.platform === 'win32') {
|
|
51
|
+
return process.env.TEMP || 'C:\\Windows\\Temp';
|
|
52
|
+
} else if (isWSL) {
|
|
53
|
+
return '/mnt/c/Windows/Temp';
|
|
54
|
+
} else {
|
|
55
|
+
return process.env.TMPDIR || '/tmp';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Chrome remote debugging port
|
|
61
|
+
*/
|
|
62
|
+
export const CHROME_DEBUG_PORT = 9222;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* utils/url-to-project.js
|
|
3
|
+
*
|
|
4
|
+
* Utilities for extracting project ID from website URLs.
|
|
5
|
+
* Used by scenario recorder to organize scenarios by domain.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Extract project ID from URL
|
|
10
|
+
*
|
|
11
|
+
* Strategy (v3.0):
|
|
12
|
+
* - Always use main domain only (strip all subdomains)
|
|
13
|
+
* - Include ports for ALL domains (localhost and others)
|
|
14
|
+
* - file:// URLs → "local"
|
|
15
|
+
* - Invalid URLs → "unknown"
|
|
16
|
+
*
|
|
17
|
+
* Examples:
|
|
18
|
+
* - https://www.google.com → "google"
|
|
19
|
+
* - https://dev.example.com:8080 → "example-8080"
|
|
20
|
+
* - https://mail.google.com → "google"
|
|
21
|
+
* - http://localhost:3000 → "localhost-3000"
|
|
22
|
+
* - https://api.stripe.com:443 → "stripe-443"
|
|
23
|
+
* - file:///C:/test.html → "local"
|
|
24
|
+
*
|
|
25
|
+
* @param {string} url - Full URL
|
|
26
|
+
* @returns {string} - Project ID (e.g., "google", "localhost-3000", "example-8080")
|
|
27
|
+
*/
|
|
28
|
+
export function urlToProjectId(url) {
|
|
29
|
+
try {
|
|
30
|
+
// Handle file:// protocol
|
|
31
|
+
if (url.startsWith('file://')) {
|
|
32
|
+
return 'local';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const urlObj = new URL(url);
|
|
36
|
+
let hostname = urlObj.hostname.toLowerCase();
|
|
37
|
+
const port = urlObj.port;
|
|
38
|
+
|
|
39
|
+
// Remove www prefix
|
|
40
|
+
hostname = hostname.replace(/^www\./, '');
|
|
41
|
+
|
|
42
|
+
// Split hostname into parts
|
|
43
|
+
const parts = hostname.split('.');
|
|
44
|
+
|
|
45
|
+
// Single-level hostnames (e.g., "localhost", "example")
|
|
46
|
+
if (parts.length === 1) {
|
|
47
|
+
const projectId = sanitizeProjectId(parts[0]);
|
|
48
|
+
return port ? `${projectId}-${port}` : projectId;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Multi-level hostnames: extract main domain (second-to-last part before TLD)
|
|
52
|
+
// Examples:
|
|
53
|
+
// - google.com → parts=['google', 'com'] → mainDomain='google'
|
|
54
|
+
// - dev.example.com → parts=['dev', 'example', 'com'] → mainDomain='example'
|
|
55
|
+
// - mail.google.co.uk → parts=['mail', 'google', 'co', 'uk'] → mainDomain='co' (not ideal, but simple)
|
|
56
|
+
const mainDomain = parts[parts.length - 2];
|
|
57
|
+
const projectId = sanitizeProjectId(mainDomain);
|
|
58
|
+
|
|
59
|
+
// Add port if present (for ALL domains, not just localhost)
|
|
60
|
+
return port ? `${projectId}-${port}` : projectId;
|
|
61
|
+
|
|
62
|
+
} catch (error) {
|
|
63
|
+
// Invalid URL, return safe fallback
|
|
64
|
+
console.error('[url-to-project] Invalid URL:', url, error);
|
|
65
|
+
return 'unknown';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Sanitize project ID
|
|
71
|
+
* - Lowercase everything
|
|
72
|
+
* - Replace non-alphanumeric characters with hyphens
|
|
73
|
+
* - Collapse multiple hyphens into one
|
|
74
|
+
* - Remove leading/trailing hyphens
|
|
75
|
+
*
|
|
76
|
+
* @param {string} id - Raw project ID
|
|
77
|
+
* @returns {string} - Sanitized ID
|
|
78
|
+
*/
|
|
79
|
+
function sanitizeProjectId(id) {
|
|
80
|
+
return id
|
|
81
|
+
.toLowerCase()
|
|
82
|
+
.replace(/[^a-z0-9-]/g, '-') // Replace non-alphanumeric with hyphens
|
|
83
|
+
.replace(/-+/g, '-') // Collapse multiple hyphens
|
|
84
|
+
.replace(/^-|-$/g, ''); // Remove leading/trailing hyphens
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get browser-compatible version of urlToProjectId for injection
|
|
89
|
+
* Returns the function as a string to be injected into browser context
|
|
90
|
+
*
|
|
91
|
+
* @returns {string} - Function code as string
|
|
92
|
+
*/
|
|
93
|
+
export function getUrlToProjectIdForBrowser() {
|
|
94
|
+
return `
|
|
95
|
+
/**
|
|
96
|
+
* Extract project ID from URL (browser version)
|
|
97
|
+
* @param {string} url - Full URL
|
|
98
|
+
* @returns {string} - Project ID
|
|
99
|
+
*/
|
|
100
|
+
function urlToProjectId(url) {
|
|
101
|
+
try {
|
|
102
|
+
if (url.startsWith('file://')) {
|
|
103
|
+
return 'local';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const urlObj = new URL(url);
|
|
107
|
+
let hostname = urlObj.hostname.toLowerCase();
|
|
108
|
+
const port = urlObj.port;
|
|
109
|
+
|
|
110
|
+
hostname = hostname.replace(/^www\\./, '');
|
|
111
|
+
const parts = hostname.split('.');
|
|
112
|
+
|
|
113
|
+
if (parts.length === 1) {
|
|
114
|
+
const projectId = sanitizeProjectId(parts[0]);
|
|
115
|
+
return port ? \`\${projectId}-\${port}\` : projectId;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const mainDomain = parts[parts.length - 2];
|
|
119
|
+
const projectId = sanitizeProjectId(mainDomain);
|
|
120
|
+
return port ? \`\${projectId}-\${port}\` : projectId;
|
|
121
|
+
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error('[url-to-project] Invalid URL:', url, error);
|
|
124
|
+
return 'unknown';
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Sanitize project ID (browser version)
|
|
130
|
+
* @param {string} id - Raw project ID
|
|
131
|
+
* @returns {string} - Sanitized ID
|
|
132
|
+
*/
|
|
133
|
+
function sanitizeProjectId(id) {
|
|
134
|
+
return id
|
|
135
|
+
.toLowerCase()
|
|
136
|
+
.replace(/[^a-z0-9-]/g, '-')
|
|
137
|
+
.replace(/-+/g, '-')
|
|
138
|
+
.replace(/^-|-$/g, '');
|
|
139
|
+
}
|
|
140
|
+
`;
|
|
141
|
+
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* utils/project-detector.js
|
|
3
|
-
*
|
|
4
|
-
* Utilities for detecting the current project root directory.
|
|
5
|
-
* Uses cascade strategy: env variables → Git root → cwd fallback
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { execSync } from 'child_process';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Find Git repository root from a starting path
|
|
13
|
-
* @param {string} startPath - Path to start searching from
|
|
14
|
-
* @returns {string|null} - Git root path or null if not in a Git repository
|
|
15
|
-
*/
|
|
16
|
-
function findGitRoot(startPath = process.cwd()) {
|
|
17
|
-
try {
|
|
18
|
-
const root = execSync('git rev-parse --show-toplevel', {
|
|
19
|
-
cwd: startPath,
|
|
20
|
-
encoding: 'utf-8',
|
|
21
|
-
stdio: ['pipe', 'pipe', 'ignore'] // Suppress stderr
|
|
22
|
-
}).trim();
|
|
23
|
-
|
|
24
|
-
// Normalize path separators for Windows
|
|
25
|
-
return path.normalize(root);
|
|
26
|
-
} catch {
|
|
27
|
-
// Not in a Git repository or git command not available
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Detect project root directory using cascade strategy
|
|
34
|
-
*
|
|
35
|
-
* Priority:
|
|
36
|
-
* 1. CLAUDE_PROJECT_DIR environment variable
|
|
37
|
-
* 2. PROJECT_DIR environment variable (custom)
|
|
38
|
-
* 3. Git repository root
|
|
39
|
-
* 4. Current working directory (fallback)
|
|
40
|
-
*
|
|
41
|
-
* @returns {string} - Detected project root path
|
|
42
|
-
*/
|
|
43
|
-
export function detectProjectRoot() {
|
|
44
|
-
// 1. Try CLAUDE_PROJECT_DIR (Claude Code specific)
|
|
45
|
-
if (process.env.CLAUDE_PROJECT_DIR) {
|
|
46
|
-
const claudeDir = path.normalize(process.env.CLAUDE_PROJECT_DIR);
|
|
47
|
-
console.log('[chrometools-mcp] Project root from CLAUDE_PROJECT_DIR:', claudeDir);
|
|
48
|
-
return claudeDir;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// 2. Try PROJECT_DIR (custom env variable)
|
|
52
|
-
if (process.env.PROJECT_DIR) {
|
|
53
|
-
const projectDir = path.normalize(process.env.PROJECT_DIR);
|
|
54
|
-
console.log('[chrometools-mcp] Project root from PROJECT_DIR:', projectDir);
|
|
55
|
-
return projectDir;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// 3. Try Git root
|
|
59
|
-
const gitRoot = findGitRoot();
|
|
60
|
-
if (gitRoot) {
|
|
61
|
-
console.log('[chrometools-mcp] Project root from Git:', gitRoot);
|
|
62
|
-
return gitRoot;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// 4. Fallback to current working directory
|
|
66
|
-
const cwd = process.cwd();
|
|
67
|
-
console.log('[chrometools-mcp] Project root fallback to cwd:', cwd);
|
|
68
|
-
return cwd;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Get scenarios directory path from base directory
|
|
73
|
-
* @param {string} baseDir - Base directory
|
|
74
|
-
* @returns {string} - Path to scenarios directory
|
|
75
|
-
*/
|
|
76
|
-
export function getScenariosDir(baseDir) {
|
|
77
|
-
return path.join(baseDir, 'scenarios');
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Get secrets directory path from base directory
|
|
82
|
-
* @param {string} baseDir - Base directory
|
|
83
|
-
* @returns {string} - Path to secrets directory
|
|
84
|
-
*/
|
|
85
|
-
export function getSecretsDir(baseDir) {
|
|
86
|
-
return path.join(baseDir, 'secrets');
|
|
87
|
-
}
|