chrome-cdp-cli 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -0
- package/dist/cli/CLIApplication.js +11 -1
- package/dist/cli/CLIInterface.js +20 -0
- package/dist/cli/CommandRouter.js +20 -1
- package/dist/handlers/InstallClaudeSkillHandler.js +423 -0
- package/dist/handlers/InstallCursorCommandHandler.js +280 -0
- package/dist/handlers/index.js +2 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@ A powerful command-line tool for controlling Chrome browser instances via the Ch
|
|
|
12
12
|
- 📊 **Console Monitoring**: Real-time console message capture with filtering and storage
|
|
13
13
|
- 🌐 **Network Monitoring**: Real-time network request/response monitoring with comprehensive filtering
|
|
14
14
|
- 🔧 **CLI Interface**: Full command-line interface with argument parsing and routing
|
|
15
|
+
- 🛠️ **IDE Integration**: Install Cursor commands and Claude skills with directory validation and --force option
|
|
15
16
|
- 📦 **Build System**: Complete TypeScript build pipeline with testing framework
|
|
16
17
|
|
|
17
18
|
### 🚧 Eval Workaround Available
|
|
@@ -127,6 +128,10 @@ chrome-cdp-cli get_console_message
|
|
|
127
128
|
# Monitor network requests
|
|
128
129
|
chrome-cdp-cli get_network_request
|
|
129
130
|
|
|
131
|
+
# Install IDE integrations
|
|
132
|
+
chrome-cdp-cli install-cursor-command
|
|
133
|
+
chrome-cdp-cli install-claude-skill --skill-type personal
|
|
134
|
+
|
|
130
135
|
# Get help for all commands
|
|
131
136
|
chrome-cdp-cli --help
|
|
132
137
|
|
|
@@ -223,6 +228,32 @@ chrome-cdp-cli list_network_requests
|
|
|
223
228
|
chrome-cdp-cli list_network_requests --filter '{"methods":["POST"],"statusCodes":[200,201]}'
|
|
224
229
|
```
|
|
225
230
|
|
|
231
|
+
#### IDE Integration
|
|
232
|
+
```bash
|
|
233
|
+
# Install Cursor command (creates .cursor/commands/cdp-cli.md)
|
|
234
|
+
chrome-cdp-cli install-cursor-command
|
|
235
|
+
|
|
236
|
+
# Install Cursor command with --force (bypasses directory validation)
|
|
237
|
+
chrome-cdp-cli install-cursor-command --force
|
|
238
|
+
|
|
239
|
+
# Install Claude skill for project (creates .claude/skills/cdp-cli/SKILL.md)
|
|
240
|
+
chrome-cdp-cli install-claude-skill
|
|
241
|
+
|
|
242
|
+
# Install Claude skill for personal use (creates ~/.claude/skills/cdp-cli/SKILL.md)
|
|
243
|
+
chrome-cdp-cli install-claude-skill --skill-type personal
|
|
244
|
+
|
|
245
|
+
# Install Claude skill with examples and references
|
|
246
|
+
chrome-cdp-cli install-claude-skill --include-examples --include-references
|
|
247
|
+
|
|
248
|
+
# Install with custom directory
|
|
249
|
+
chrome-cdp-cli install-cursor-command --target-directory /custom/path/.cursor/commands
|
|
250
|
+
chrome-cdp-cli install-claude-skill --target-directory /custom/path/.claude/skills
|
|
251
|
+
|
|
252
|
+
# Force install (bypasses directory validation)
|
|
253
|
+
chrome-cdp-cli install-cursor-command --force
|
|
254
|
+
chrome-cdp-cli install-claude-skill --force
|
|
255
|
+
```
|
|
256
|
+
|
|
226
257
|
### 🚧 Available via Eval Workarounds
|
|
227
258
|
|
|
228
259
|
#### Page Management
|
|
@@ -19,6 +19,10 @@ class CLIApplication {
|
|
|
19
19
|
this.cli.registerHandler(new handlers_1.TakeSnapshotHandler());
|
|
20
20
|
this.cli.registerHandler(new handlers_1.GetConsoleMessageHandler());
|
|
21
21
|
this.cli.registerHandler(new handlers_1.ListConsoleMessagesHandler());
|
|
22
|
+
this.cli.registerHandler(new handlers_1.GetNetworkRequestHandler());
|
|
23
|
+
this.cli.registerHandler(new handlers_1.ListNetworkRequestsHandler());
|
|
24
|
+
this.cli.registerHandler(new handlers_1.InstallCursorCommandHandler());
|
|
25
|
+
this.cli.registerHandler(new handlers_1.InstallClaudeSkillHandler());
|
|
22
26
|
}
|
|
23
27
|
async run(argv) {
|
|
24
28
|
try {
|
|
@@ -37,7 +41,13 @@ class CLIApplication {
|
|
|
37
41
|
}
|
|
38
42
|
}
|
|
39
43
|
needsConnection(commandName) {
|
|
40
|
-
const noConnectionCommands = [
|
|
44
|
+
const noConnectionCommands = [
|
|
45
|
+
'help',
|
|
46
|
+
'connect',
|
|
47
|
+
'disconnect',
|
|
48
|
+
'install_cursor_command',
|
|
49
|
+
'install_claude_skill'
|
|
50
|
+
];
|
|
41
51
|
return !noConnectionCommands.includes(commandName);
|
|
42
52
|
}
|
|
43
53
|
async ensureConnection(command) {
|
package/dist/cli/CLIInterface.js
CHANGED
|
@@ -291,6 +291,26 @@ class CLIInterface {
|
|
|
291
291
|
if (args[0])
|
|
292
292
|
commandArgs.command = args[0];
|
|
293
293
|
break;
|
|
294
|
+
case 'install_cursor_command':
|
|
295
|
+
if (options['target-directory'])
|
|
296
|
+
commandArgs.targetDirectory = options['target-directory'];
|
|
297
|
+
if (options['include-examples'] !== undefined)
|
|
298
|
+
commandArgs.includeExamples = options['include-examples'] !== 'false';
|
|
299
|
+
if (options['force'])
|
|
300
|
+
commandArgs.force = true;
|
|
301
|
+
break;
|
|
302
|
+
case 'install_claude_skill':
|
|
303
|
+
if (options['skill-type'])
|
|
304
|
+
commandArgs.skillType = options['skill-type'];
|
|
305
|
+
if (options['target-directory'])
|
|
306
|
+
commandArgs.targetDirectory = options['target-directory'];
|
|
307
|
+
if (options['include-examples'] !== undefined)
|
|
308
|
+
commandArgs.includeExamples = options['include-examples'] !== 'false';
|
|
309
|
+
if (options['include-references'] !== undefined)
|
|
310
|
+
commandArgs.includeReferences = options['include-references'] !== 'false';
|
|
311
|
+
if (options['force'])
|
|
312
|
+
commandArgs.force = true;
|
|
313
|
+
break;
|
|
294
314
|
default:
|
|
295
315
|
args.forEach((arg, index) => {
|
|
296
316
|
commandArgs[`arg${index}`] = arg;
|
|
@@ -69,7 +69,13 @@ class CommandRouter {
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
isSpecialCommand(commandName) {
|
|
72
|
-
const specialCommands = [
|
|
72
|
+
const specialCommands = [
|
|
73
|
+
'help',
|
|
74
|
+
'connect',
|
|
75
|
+
'disconnect',
|
|
76
|
+
'install_cursor_command',
|
|
77
|
+
'install_claude_skill'
|
|
78
|
+
];
|
|
73
79
|
return specialCommands.includes(commandName);
|
|
74
80
|
}
|
|
75
81
|
async executeSpecialCommand(command) {
|
|
@@ -80,6 +86,17 @@ class CommandRouter {
|
|
|
80
86
|
return this.executeConnectCommand(command);
|
|
81
87
|
case 'disconnect':
|
|
82
88
|
return this.executeDisconnectCommand();
|
|
89
|
+
case 'install_cursor_command':
|
|
90
|
+
case 'install_claude_skill':
|
|
91
|
+
const handler = this.registry.get(command.name);
|
|
92
|
+
if (!handler) {
|
|
93
|
+
return {
|
|
94
|
+
success: false,
|
|
95
|
+
error: `Handler not found for command: ${command.name}`,
|
|
96
|
+
exitCode: ExitCode.INVALID_COMMAND
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return await handler.execute(null, command.args);
|
|
83
100
|
default:
|
|
84
101
|
return {
|
|
85
102
|
success: false,
|
|
@@ -229,6 +246,8 @@ For more information about a specific command, use:
|
|
|
229
246
|
'snapshot': 'Capture DOM snapshot with structure and styles',
|
|
230
247
|
'console-messages': 'Get console messages',
|
|
231
248
|
'network-requests': 'Get network requests',
|
|
249
|
+
'install_cursor_command': 'Install Cursor IDE commands for Chrome automation',
|
|
250
|
+
'install_claude_skill': 'Install Claude Code skill for Chrome automation',
|
|
232
251
|
'help': 'Show help information'
|
|
233
252
|
};
|
|
234
253
|
return descriptions[commandName] || 'No description available';
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.InstallClaudeSkillHandler = void 0;
|
|
37
|
+
const fs = __importStar(require("fs/promises"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const logger_1 = require("../utils/logger");
|
|
41
|
+
class InstallClaudeSkillHandler {
|
|
42
|
+
constructor() {
|
|
43
|
+
this.name = 'install_claude_skill';
|
|
44
|
+
}
|
|
45
|
+
async execute(_client, args) {
|
|
46
|
+
try {
|
|
47
|
+
const skillType = args.skillType || 'project';
|
|
48
|
+
const targetDir = args.targetDirectory || this.getDefaultSkillDirectory(skillType);
|
|
49
|
+
const skillDir = path.join(targetDir, 'cdp-cli');
|
|
50
|
+
if (!args.targetDirectory && !args.force) {
|
|
51
|
+
if (skillType === 'project') {
|
|
52
|
+
const claudeDirExists = await this.checkDirectoryExists('.claude');
|
|
53
|
+
if (!claudeDirExists) {
|
|
54
|
+
return {
|
|
55
|
+
success: false,
|
|
56
|
+
error: `Warning: No .claude directory found in current directory. This may not be a project root directory.
|
|
57
|
+
|
|
58
|
+
To install Claude skills:
|
|
59
|
+
1. Navigate to your project root directory (where .claude folder should be), or
|
|
60
|
+
2. Use --skill-type personal to install to your home directory, or
|
|
61
|
+
3. Use --target-directory to specify a custom location, or
|
|
62
|
+
4. Use --force to install anyway
|
|
63
|
+
|
|
64
|
+
Examples:
|
|
65
|
+
chrome-cdp-cli install-claude-skill --skill-type personal
|
|
66
|
+
chrome-cdp-cli install-claude-skill --target-directory /path/to/.claude/skills
|
|
67
|
+
chrome-cdp-cli install-claude-skill --force`
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
await this.ensureDirectory(skillDir);
|
|
73
|
+
const skillConfig = this.generateClaudeSkill();
|
|
74
|
+
const skillPath = path.join(skillDir, 'SKILL.md');
|
|
75
|
+
await fs.writeFile(skillPath, this.generateSkillMarkdown(skillConfig), 'utf8');
|
|
76
|
+
logger_1.logger.info(`Created Claude skill: ${skillPath}`);
|
|
77
|
+
const createdFiles = ['SKILL.md'];
|
|
78
|
+
if (args.includeExamples) {
|
|
79
|
+
const examplesPath = path.join(skillDir, 'examples.md');
|
|
80
|
+
await fs.writeFile(examplesPath, this.generateExamplesMarkdown(), 'utf8');
|
|
81
|
+
createdFiles.push('examples.md');
|
|
82
|
+
logger_1.logger.info(`Created examples file: ${examplesPath}`);
|
|
83
|
+
}
|
|
84
|
+
if (args.includeReferences) {
|
|
85
|
+
const referencePath = path.join(skillDir, 'reference.md');
|
|
86
|
+
await fs.writeFile(referencePath, this.generateReferenceMarkdown(), 'utf8');
|
|
87
|
+
createdFiles.push('reference.md');
|
|
88
|
+
logger_1.logger.info(`Created reference file: ${referencePath}`);
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
success: true,
|
|
92
|
+
data: {
|
|
93
|
+
skillType,
|
|
94
|
+
directory: skillDir,
|
|
95
|
+
files: createdFiles,
|
|
96
|
+
skillName: skillConfig.name
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
logger_1.logger.error('Failed to install Claude skill:', error);
|
|
102
|
+
return {
|
|
103
|
+
success: false,
|
|
104
|
+
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async checkDirectoryExists(dirPath) {
|
|
109
|
+
try {
|
|
110
|
+
await fs.access(dirPath);
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async ensureDirectory(dirPath) {
|
|
118
|
+
try {
|
|
119
|
+
await fs.access(dirPath);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
123
|
+
logger_1.logger.info(`Created directory: ${dirPath}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
getDefaultSkillDirectory(skillType) {
|
|
127
|
+
return skillType === 'personal'
|
|
128
|
+
? path.join(os.homedir(), '.claude', 'skills')
|
|
129
|
+
: '.claude/skills';
|
|
130
|
+
}
|
|
131
|
+
generateClaudeSkill() {
|
|
132
|
+
return {
|
|
133
|
+
name: 'cdp-cli',
|
|
134
|
+
description: 'Chrome browser automation and testing using DevTools Protocol. Use when user needs to control Chrome browser, execute JavaScript, take screenshots, monitor console/network, or perform web automation tasks.',
|
|
135
|
+
instructions: `# Chrome Browser Automation
|
|
136
|
+
|
|
137
|
+
## Instructions
|
|
138
|
+
Use this skill when the user needs to:
|
|
139
|
+
- Execute JavaScript code in Chrome browser
|
|
140
|
+
- Take screenshots of web pages
|
|
141
|
+
- Capture DOM snapshots with layout information
|
|
142
|
+
- Monitor console messages and network requests
|
|
143
|
+
- Perform web automation and testing
|
|
144
|
+
|
|
145
|
+
## Available Commands
|
|
146
|
+
1. **eval**: Execute JavaScript code in browser context
|
|
147
|
+
2. **screenshot**: Capture page screenshots
|
|
148
|
+
3. **snapshot**: Capture complete DOM snapshots
|
|
149
|
+
4. **get_console_message**: Get latest console message
|
|
150
|
+
5. **list_console_messages**: List all console messages
|
|
151
|
+
6. **get_network_request**: Get latest network request
|
|
152
|
+
7. **list_network_requests**: List all network requests
|
|
153
|
+
|
|
154
|
+
## Usage Examples
|
|
155
|
+
- Execute: chrome-cdp-cli eval "document.title"
|
|
156
|
+
- Screenshot: chrome-cdp-cli screenshot --filename page.png
|
|
157
|
+
- Console: chrome-cdp-cli get_console_message
|
|
158
|
+
- Network: chrome-cdp-cli list_network_requests
|
|
159
|
+
|
|
160
|
+
## Prerequisites
|
|
161
|
+
Chrome browser must be running with DevTools enabled:
|
|
162
|
+
chrome --remote-debugging-port=9222`,
|
|
163
|
+
allowedTools: ['Execute', 'Read', 'Write']
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
generateSkillMarkdown(skill) {
|
|
167
|
+
const frontmatter = `---
|
|
168
|
+
name: ${skill.name}
|
|
169
|
+
description: ${skill.description}
|
|
170
|
+
${skill.allowedTools ? `allowedTools: [${skill.allowedTools.map(t => `"${t}"`).join(', ')}]` : ''}
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
`;
|
|
174
|
+
return frontmatter + skill.instructions;
|
|
175
|
+
}
|
|
176
|
+
generateExamplesMarkdown() {
|
|
177
|
+
return `# Chrome Automation Examples
|
|
178
|
+
|
|
179
|
+
## Basic JavaScript Execution
|
|
180
|
+
|
|
181
|
+
### Get Page Information
|
|
182
|
+
\`\`\`bash
|
|
183
|
+
chrome-cdp-cli eval "document.title"
|
|
184
|
+
chrome-cdp-cli eval "window.location.href"
|
|
185
|
+
chrome-cdp-cli eval "document.querySelectorAll('a').length"
|
|
186
|
+
\`\`\`
|
|
187
|
+
|
|
188
|
+
### Interact with Elements
|
|
189
|
+
\`\`\`bash
|
|
190
|
+
chrome-cdp-cli eval "document.querySelector('#button').click()"
|
|
191
|
+
chrome-cdp-cli eval "document.querySelector('#input').value = 'Hello World'"
|
|
192
|
+
chrome-cdp-cli eval "document.querySelector('#form').submit()"
|
|
193
|
+
\`\`\`
|
|
194
|
+
|
|
195
|
+
### Async Operations
|
|
196
|
+
\`\`\`bash
|
|
197
|
+
chrome-cdp-cli eval "fetch('/api/data').then(r => r.json())"
|
|
198
|
+
chrome-cdp-cli eval "new Promise(resolve => setTimeout(() => resolve('Done'), 1000))"
|
|
199
|
+
\`\`\`
|
|
200
|
+
|
|
201
|
+
## Visual Capture
|
|
202
|
+
|
|
203
|
+
### Screenshots
|
|
204
|
+
\`\`\`bash
|
|
205
|
+
chrome-cdp-cli screenshot --filename homepage.png
|
|
206
|
+
chrome-cdp-cli screenshot --filename fullpage.png --fullpage
|
|
207
|
+
\`\`\`
|
|
208
|
+
|
|
209
|
+
### DOM Snapshots
|
|
210
|
+
\`\`\`bash
|
|
211
|
+
chrome-cdp-cli snapshot --filename page-structure.json
|
|
212
|
+
\`\`\`
|
|
213
|
+
|
|
214
|
+
## Monitoring
|
|
215
|
+
|
|
216
|
+
### Console Messages
|
|
217
|
+
\`\`\`bash
|
|
218
|
+
chrome-cdp-cli get_console_message
|
|
219
|
+
chrome-cdp-cli list_console_messages --type error
|
|
220
|
+
\`\`\`
|
|
221
|
+
|
|
222
|
+
### Network Requests
|
|
223
|
+
\`\`\`bash
|
|
224
|
+
chrome-cdp-cli get_network_request
|
|
225
|
+
chrome-cdp-cli list_network_requests --method POST
|
|
226
|
+
\`\`\`
|
|
227
|
+
|
|
228
|
+
## Common Workflows
|
|
229
|
+
|
|
230
|
+
### Testing Form Submission
|
|
231
|
+
\`\`\`bash
|
|
232
|
+
# Fill form
|
|
233
|
+
chrome-cdp-cli eval "document.querySelector('#email').value = 'test@example.com'"
|
|
234
|
+
chrome-cdp-cli eval "document.querySelector('#password').value = 'password123'"
|
|
235
|
+
|
|
236
|
+
# Submit and capture
|
|
237
|
+
chrome-cdp-cli eval "document.querySelector('#submit').click()"
|
|
238
|
+
chrome-cdp-cli screenshot --filename after-submit.png
|
|
239
|
+
|
|
240
|
+
# Check for errors
|
|
241
|
+
chrome-cdp-cli list_console_messages --type error
|
|
242
|
+
\`\`\`
|
|
243
|
+
|
|
244
|
+
### API Testing
|
|
245
|
+
\`\`\`bash
|
|
246
|
+
# Make API call
|
|
247
|
+
chrome-cdp-cli eval "fetch('/api/users', {method: 'POST', body: JSON.stringify({name: 'John'}), headers: {'Content-Type': 'application/json'}})"
|
|
248
|
+
|
|
249
|
+
# Monitor network
|
|
250
|
+
chrome-cdp-cli list_network_requests --method POST
|
|
251
|
+
|
|
252
|
+
# Check response
|
|
253
|
+
chrome-cdp-cli get_network_request
|
|
254
|
+
\`\`\`
|
|
255
|
+
`;
|
|
256
|
+
}
|
|
257
|
+
generateReferenceMarkdown() {
|
|
258
|
+
return `# Chrome DevTools CLI Reference
|
|
259
|
+
|
|
260
|
+
## Command Reference
|
|
261
|
+
|
|
262
|
+
### eval
|
|
263
|
+
Execute JavaScript code in the browser context.
|
|
264
|
+
|
|
265
|
+
**Syntax:** \`chrome-cdp-cli eval <expression>\`
|
|
266
|
+
|
|
267
|
+
**Options:**
|
|
268
|
+
- \`--timeout <ms>\`: Execution timeout in milliseconds
|
|
269
|
+
- \`--await-promise\`: Wait for Promise resolution (default: true)
|
|
270
|
+
|
|
271
|
+
**Examples:**
|
|
272
|
+
- \`chrome-cdp-cli eval "document.title"\`
|
|
273
|
+
- \`chrome-cdp-cli eval "fetch('/api').then(r => r.text())"\`
|
|
274
|
+
|
|
275
|
+
### screenshot
|
|
276
|
+
Capture a screenshot of the current page.
|
|
277
|
+
|
|
278
|
+
**Syntax:** \`chrome-cdp-cli screenshot [options]\`
|
|
279
|
+
|
|
280
|
+
**Options:**
|
|
281
|
+
- \`--filename <path>\`: Output filename (default: screenshot.png)
|
|
282
|
+
- \`--fullpage\`: Capture full page instead of viewport
|
|
283
|
+
- \`--quality <0-100>\`: JPEG quality (default: 90)
|
|
284
|
+
|
|
285
|
+
### snapshot
|
|
286
|
+
Capture a complete DOM snapshot with layout information.
|
|
287
|
+
|
|
288
|
+
**Syntax:** \`chrome-cdp-cli snapshot [options]\`
|
|
289
|
+
|
|
290
|
+
**Options:**
|
|
291
|
+
- \`--filename <path>\`: Output filename (default: snapshot.json)
|
|
292
|
+
- \`--include-styles\`: Include computed styles (default: true)
|
|
293
|
+
- \`--include-layout\`: Include layout information (default: true)
|
|
294
|
+
|
|
295
|
+
### get_console_message
|
|
296
|
+
Get the latest console message.
|
|
297
|
+
|
|
298
|
+
**Syntax:** \`chrome-cdp-cli get_console_message [options]\`
|
|
299
|
+
|
|
300
|
+
**Options:**
|
|
301
|
+
- \`--type <log|info|warn|error>\`: Filter by message type
|
|
302
|
+
|
|
303
|
+
### list_console_messages
|
|
304
|
+
List all console messages.
|
|
305
|
+
|
|
306
|
+
**Syntax:** \`chrome-cdp-cli list_console_messages [options]\`
|
|
307
|
+
|
|
308
|
+
**Options:**
|
|
309
|
+
- \`--type <log|info|warn|error>\`: Filter by message type
|
|
310
|
+
- \`--limit <number>\`: Maximum number of messages to return
|
|
311
|
+
|
|
312
|
+
### get_network_request
|
|
313
|
+
Get the latest network request.
|
|
314
|
+
|
|
315
|
+
**Syntax:** \`chrome-cdp-cli get_network_request [options]\`
|
|
316
|
+
|
|
317
|
+
**Options:**
|
|
318
|
+
- \`--method <GET|POST|PUT|DELETE>\`: Filter by HTTP method
|
|
319
|
+
|
|
320
|
+
### list_network_requests
|
|
321
|
+
List all network requests.
|
|
322
|
+
|
|
323
|
+
**Syntax:** \`chrome-cdp-cli list_network_requests [options]\`
|
|
324
|
+
|
|
325
|
+
**Options:**
|
|
326
|
+
- \`--method <GET|POST|PUT|DELETE>\`: Filter by HTTP method
|
|
327
|
+
- \`--limit <number>\`: Maximum number of requests to return
|
|
328
|
+
|
|
329
|
+
## Global Options
|
|
330
|
+
|
|
331
|
+
- \`--host <hostname>\`: Chrome DevTools host (default: localhost)
|
|
332
|
+
- \`--port <number>\`: Chrome DevTools port (default: 9222)
|
|
333
|
+
- \`--output-format <json|text>\`: Output format (default: json)
|
|
334
|
+
- \`--verbose\`: Enable verbose logging
|
|
335
|
+
- \`--quiet\`: Suppress non-error output
|
|
336
|
+
- \`--timeout <ms>\`: Global timeout for operations
|
|
337
|
+
|
|
338
|
+
## Chrome Setup
|
|
339
|
+
|
|
340
|
+
### Starting Chrome with DevTools
|
|
341
|
+
\`\`\`bash
|
|
342
|
+
# Linux/Windows
|
|
343
|
+
chrome --remote-debugging-port=9222
|
|
344
|
+
|
|
345
|
+
# macOS
|
|
346
|
+
/Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --remote-debugging-port=9222
|
|
347
|
+
|
|
348
|
+
# With additional options
|
|
349
|
+
chrome --remote-debugging-port=9222 --disable-web-security --user-data-dir=/tmp/chrome-debug
|
|
350
|
+
\`\`\`
|
|
351
|
+
|
|
352
|
+
### Headless Mode
|
|
353
|
+
\`\`\`bash
|
|
354
|
+
chrome --headless --remote-debugging-port=9222
|
|
355
|
+
\`\`\`
|
|
356
|
+
|
|
357
|
+
## Error Handling
|
|
358
|
+
|
|
359
|
+
### Common Errors
|
|
360
|
+
- **Connection refused**: Chrome is not running or DevTools port is incorrect
|
|
361
|
+
- **Target not found**: No active tab or page available
|
|
362
|
+
- **JavaScript error**: Syntax error or runtime exception in eval expression
|
|
363
|
+
- **Timeout**: Operation took longer than specified timeout
|
|
364
|
+
|
|
365
|
+
### Debugging Tips
|
|
366
|
+
- Use \`--verbose\` flag for detailed logging
|
|
367
|
+
- Check Chrome DevTools at \`http://localhost:9222\` for available targets
|
|
368
|
+
- Verify JavaScript syntax before executing with eval
|
|
369
|
+
- Use shorter timeouts for testing, longer for complex operations
|
|
370
|
+
|
|
371
|
+
## Integration Examples
|
|
372
|
+
|
|
373
|
+
### CI/CD Pipeline
|
|
374
|
+
\`\`\`yaml
|
|
375
|
+
# GitHub Actions example
|
|
376
|
+
- name: Test web application
|
|
377
|
+
run: |
|
|
378
|
+
# Start Chrome
|
|
379
|
+
google-chrome --headless --remote-debugging-port=9222 &
|
|
380
|
+
|
|
381
|
+
# Wait for Chrome to start
|
|
382
|
+
sleep 2
|
|
383
|
+
|
|
384
|
+
# Navigate to application
|
|
385
|
+
chrome-cdp-cli eval "window.location.href = 'http://localhost:3000'"
|
|
386
|
+
|
|
387
|
+
# Run tests
|
|
388
|
+
chrome-cdp-cli eval "document.querySelector('#test-button').click()"
|
|
389
|
+
chrome-cdp-cli screenshot --filename test-result.png
|
|
390
|
+
|
|
391
|
+
# Check for errors
|
|
392
|
+
chrome-cdp-cli list_console_messages --type error
|
|
393
|
+
\`\`\`
|
|
394
|
+
|
|
395
|
+
### Automated Testing
|
|
396
|
+
\`\`\`bash
|
|
397
|
+
#!/bin/bash
|
|
398
|
+
# test-script.sh
|
|
399
|
+
|
|
400
|
+
# Start Chrome in background
|
|
401
|
+
chrome --headless --remote-debugging-port=9222 &
|
|
402
|
+
CHROME_PID=$!
|
|
403
|
+
|
|
404
|
+
# Wait for Chrome to start
|
|
405
|
+
sleep 2
|
|
406
|
+
|
|
407
|
+
# Run tests
|
|
408
|
+
chrome-cdp-cli eval "window.location.href = 'http://localhost:3000'"
|
|
409
|
+
chrome-cdp-cli eval "document.querySelector('#login-form input[name=username]').value = 'testuser'"
|
|
410
|
+
chrome-cdp-cli eval "document.querySelector('#login-form input[name=password]').value = 'testpass'"
|
|
411
|
+
chrome-cdp-cli eval "document.querySelector('#login-form').submit()"
|
|
412
|
+
|
|
413
|
+
# Capture results
|
|
414
|
+
chrome-cdp-cli screenshot --filename login-test.png
|
|
415
|
+
chrome-cdp-cli list_console_messages --type error > errors.log
|
|
416
|
+
|
|
417
|
+
# Cleanup
|
|
418
|
+
kill $CHROME_PID
|
|
419
|
+
\`\`\`
|
|
420
|
+
`;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
exports.InstallClaudeSkillHandler = InstallClaudeSkillHandler;
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.InstallCursorCommandHandler = void 0;
|
|
37
|
+
const fs = __importStar(require("fs/promises"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const logger_1 = require("../utils/logger");
|
|
40
|
+
class InstallCursorCommandHandler {
|
|
41
|
+
constructor() {
|
|
42
|
+
this.name = 'install_cursor_command';
|
|
43
|
+
}
|
|
44
|
+
async execute(_client, args) {
|
|
45
|
+
try {
|
|
46
|
+
const targetDir = args.targetDirectory || '.cursor/commands';
|
|
47
|
+
if (!args.targetDirectory && targetDir === '.cursor/commands' && !args.force) {
|
|
48
|
+
const cursorDirExists = await this.checkDirectoryExists('.cursor');
|
|
49
|
+
if (!cursorDirExists) {
|
|
50
|
+
return {
|
|
51
|
+
success: false,
|
|
52
|
+
error: `Warning: No .cursor directory found in current directory. This may not be a Cursor project root directory.
|
|
53
|
+
|
|
54
|
+
To install Cursor commands:
|
|
55
|
+
1. Navigate to your Cursor project root directory (where .cursor folder should be)
|
|
56
|
+
2. Run the command again, or
|
|
57
|
+
3. Use --target-directory to specify a custom location, or
|
|
58
|
+
4. Use --force to install anyway
|
|
59
|
+
|
|
60
|
+
Examples:
|
|
61
|
+
chrome-cdp-cli install-cursor-command --target-directory /path/to/.cursor/commands
|
|
62
|
+
chrome-cdp-cli install-cursor-command --force`
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
await this.ensureDirectory(targetDir);
|
|
67
|
+
const commands = this.generateCursorCommands();
|
|
68
|
+
const createdFiles = [];
|
|
69
|
+
for (const command of commands) {
|
|
70
|
+
const filePath = path.join(targetDir, `${command.name}.md`);
|
|
71
|
+
const content = this.generateCommandMarkdown(command);
|
|
72
|
+
await fs.writeFile(filePath, content, 'utf8');
|
|
73
|
+
createdFiles.push(filePath);
|
|
74
|
+
logger_1.logger.info(`Created Cursor command: ${filePath}`);
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
success: true,
|
|
78
|
+
data: {
|
|
79
|
+
installed: commands.length,
|
|
80
|
+
directory: targetDir,
|
|
81
|
+
commands: commands.map(c => c.name),
|
|
82
|
+
files: createdFiles
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
logger_1.logger.error('Failed to install Cursor commands:', error);
|
|
88
|
+
return {
|
|
89
|
+
success: false,
|
|
90
|
+
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async checkDirectoryExists(dirPath) {
|
|
95
|
+
try {
|
|
96
|
+
await fs.access(dirPath);
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async ensureDirectory(dirPath) {
|
|
104
|
+
try {
|
|
105
|
+
await fs.access(dirPath);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
109
|
+
logger_1.logger.info(`Created directory: ${dirPath}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
generateCursorCommands() {
|
|
113
|
+
return [
|
|
114
|
+
{
|
|
115
|
+
name: 'cdp-cli',
|
|
116
|
+
description: 'Chrome DevTools Protocol CLI 工具',
|
|
117
|
+
instructions: `通过 Chrome DevTools Protocol 控制 Chrome 浏览器,支持多种自动化操作。
|
|
118
|
+
|
|
119
|
+
## 功能列表
|
|
120
|
+
|
|
121
|
+
### 1. JavaScript 执行 (eval)
|
|
122
|
+
执行 JavaScript 代码并返回结果,支持异步代码和 Promise。
|
|
123
|
+
|
|
124
|
+
**用途:**
|
|
125
|
+
- 获取页面信息(标题、URL、元素等)
|
|
126
|
+
- 修改页面内容和样式
|
|
127
|
+
- 执行复杂的 JavaScript 逻辑
|
|
128
|
+
- 与页面元素交互
|
|
129
|
+
|
|
130
|
+
**示例:**
|
|
131
|
+
- \`chrome-cdp-cli eval "document.title"\`
|
|
132
|
+
- \`chrome-cdp-cli eval "window.location.href"\`
|
|
133
|
+
- \`chrome-cdp-cli eval "document.querySelector('#button').click()"\`
|
|
134
|
+
- \`chrome-cdp-cli eval "fetch('/api/data').then(r => r.json())"\`
|
|
135
|
+
|
|
136
|
+
### 2. 页面截图 (screenshot)
|
|
137
|
+
捕获当前页面的截图并保存到指定文件。
|
|
138
|
+
|
|
139
|
+
**功能:**
|
|
140
|
+
- 指定文件名和路径
|
|
141
|
+
- 全页面截图或视口截图
|
|
142
|
+
- 自定义图片质量和格式
|
|
143
|
+
- 输出 PNG 格式文件
|
|
144
|
+
|
|
145
|
+
**示例:**
|
|
146
|
+
- \`chrome-cdp-cli screenshot --filename screenshot.png\`
|
|
147
|
+
- \`chrome-cdp-cli screenshot --filename fullpage.png --full-page\`
|
|
148
|
+
- \`chrome-cdp-cli screenshot --filename reports/page-capture.png\`
|
|
149
|
+
|
|
150
|
+
### 3. DOM 快照 (snapshot)
|
|
151
|
+
捕获包含 DOM 结构、样式和布局信息的完整快照。
|
|
152
|
+
|
|
153
|
+
**包含内容:**
|
|
154
|
+
- 完整的 DOM 树结构
|
|
155
|
+
- 计算后的 CSS 样式
|
|
156
|
+
- 元素布局信息
|
|
157
|
+
- 元素属性和文本内容
|
|
158
|
+
|
|
159
|
+
**示例:**
|
|
160
|
+
- \`chrome-cdp-cli snapshot --filename dom-snapshot.json\`
|
|
161
|
+
- \`chrome-cdp-cli snapshot --filename page-structure.json\`
|
|
162
|
+
|
|
163
|
+
### 4. 控制台监控 (console)
|
|
164
|
+
获取浏览器控制台的消息,包括日志、警告和错误。
|
|
165
|
+
|
|
166
|
+
**功能:**
|
|
167
|
+
- 获取最新的控制台消息
|
|
168
|
+
- 列出所有控制台消息
|
|
169
|
+
- 按类型过滤消息(log、warn、error)
|
|
170
|
+
- 调试和监控页面运行状态
|
|
171
|
+
|
|
172
|
+
**示例:**
|
|
173
|
+
- \`chrome-cdp-cli get_console_message\`
|
|
174
|
+
- \`chrome-cdp-cli list_console_messages --type error\`
|
|
175
|
+
|
|
176
|
+
### 5. 网络请求监控 (network)
|
|
177
|
+
监控和获取页面的网络请求信息。
|
|
178
|
+
|
|
179
|
+
**功能:**
|
|
180
|
+
- 获取最新的网络请求
|
|
181
|
+
- 列出所有网络请求
|
|
182
|
+
- 按方法过滤请求(GET、POST 等)
|
|
183
|
+
- 查看请求和响应详情
|
|
184
|
+
- 分析页面的网络行为和 API 调用
|
|
185
|
+
|
|
186
|
+
**示例:**
|
|
187
|
+
- \`chrome-cdp-cli get_network_request\`
|
|
188
|
+
- \`chrome-cdp-cli list_network_requests --method POST\`
|
|
189
|
+
|
|
190
|
+
命令会自动连接到运行在 localhost:9222 的 Chrome 实例。`,
|
|
191
|
+
examples: [
|
|
192
|
+
'chrome-cdp-cli eval "document.title"',
|
|
193
|
+
'chrome-cdp-cli screenshot --filename page.png',
|
|
194
|
+
'chrome-cdp-cli snapshot --filename dom.json',
|
|
195
|
+
'chrome-cdp-cli get_console_message',
|
|
196
|
+
'chrome-cdp-cli list_network_requests'
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
];
|
|
200
|
+
}
|
|
201
|
+
generateCommandMarkdown(command) {
|
|
202
|
+
const examples = command.examples.map(example => `- ${example}`).join('\n');
|
|
203
|
+
return `# ${command.description}
|
|
204
|
+
|
|
205
|
+
${command.instructions}
|
|
206
|
+
|
|
207
|
+
## 使用示例
|
|
208
|
+
|
|
209
|
+
${examples}
|
|
210
|
+
|
|
211
|
+
## 前置条件
|
|
212
|
+
|
|
213
|
+
确保 Chrome 浏览器已启动并开启了远程调试:
|
|
214
|
+
|
|
215
|
+
\`\`\`bash
|
|
216
|
+
chrome --remote-debugging-port=9222
|
|
217
|
+
\`\`\`
|
|
218
|
+
|
|
219
|
+
或者在 macOS 上:
|
|
220
|
+
|
|
221
|
+
\`\`\`bash
|
|
222
|
+
/Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --remote-debugging-port=9222
|
|
223
|
+
\`\`\`
|
|
224
|
+
|
|
225
|
+
## 全局选项
|
|
226
|
+
|
|
227
|
+
所有命令都支持以下全局选项:
|
|
228
|
+
|
|
229
|
+
- \`--host <hostname>\`: Chrome DevTools 主机地址 (默认: localhost)
|
|
230
|
+
- \`--port <number>\`: Chrome DevTools 端口 (默认: 9222)
|
|
231
|
+
- \`--format <json|text>\`: 输出格式 (默认: json)
|
|
232
|
+
- \`--verbose\`: 启用详细日志
|
|
233
|
+
- \`--quiet\`: 静默模式
|
|
234
|
+
- \`--timeout <ms>\`: 命令超时时间
|
|
235
|
+
|
|
236
|
+
## 常用工作流程
|
|
237
|
+
|
|
238
|
+
### 网页自动化测试
|
|
239
|
+
\`\`\`bash
|
|
240
|
+
# 1. 导航到页面并截图
|
|
241
|
+
chrome-cdp-cli eval "window.location.href = 'https://example.com'"
|
|
242
|
+
chrome-cdp-cli screenshot --filename before.png
|
|
243
|
+
|
|
244
|
+
# 2. 填写表单
|
|
245
|
+
chrome-cdp-cli eval "document.querySelector('#email').value = 'test@example.com'"
|
|
246
|
+
chrome-cdp-cli eval "document.querySelector('#password').value = 'password123'"
|
|
247
|
+
|
|
248
|
+
# 3. 提交并检查结果
|
|
249
|
+
chrome-cdp-cli eval "document.querySelector('#submit').click()"
|
|
250
|
+
chrome-cdp-cli screenshot --filename after.png
|
|
251
|
+
chrome-cdp-cli list_console_messages --type error
|
|
252
|
+
\`\`\`
|
|
253
|
+
|
|
254
|
+
### API 调用监控
|
|
255
|
+
\`\`\`bash
|
|
256
|
+
# 1. 开始监控网络请求
|
|
257
|
+
chrome-cdp-cli eval "fetch('/api/users').then(r => r.json())"
|
|
258
|
+
|
|
259
|
+
# 2. 查看网络请求
|
|
260
|
+
chrome-cdp-cli list_network_requests --method POST
|
|
261
|
+
|
|
262
|
+
# 3. 获取最新请求详情
|
|
263
|
+
chrome-cdp-cli get_network_request
|
|
264
|
+
\`\`\`
|
|
265
|
+
|
|
266
|
+
### 页面分析
|
|
267
|
+
\`\`\`bash
|
|
268
|
+
# 1. 获取页面基本信息
|
|
269
|
+
chrome-cdp-cli eval "({title: document.title, url: location.href, links: document.querySelectorAll('a').length})"
|
|
270
|
+
|
|
271
|
+
# 2. 捕获完整页面结构
|
|
272
|
+
chrome-cdp-cli snapshot --filename page-analysis.json
|
|
273
|
+
|
|
274
|
+
# 3. 检查控制台错误
|
|
275
|
+
chrome-cdp-cli list_console_messages --type error
|
|
276
|
+
\`\`\`
|
|
277
|
+
`;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
exports.InstallCursorCommandHandler = InstallCursorCommandHandler;
|
package/dist/handlers/index.js
CHANGED
|
@@ -21,3 +21,5 @@ __exportStar(require("./GetConsoleMessageHandler"), exports);
|
|
|
21
21
|
__exportStar(require("./ListConsoleMessagesHandler"), exports);
|
|
22
22
|
__exportStar(require("./GetNetworkRequestHandler"), exports);
|
|
23
23
|
__exportStar(require("./ListNetworkRequestsHandler"), exports);
|
|
24
|
+
__exportStar(require("./InstallCursorCommandHandler"), exports);
|
|
25
|
+
__exportStar(require("./InstallClaudeSkillHandler"), exports);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-cdp-cli",
|
|
3
|
-
"version": "1.1
|
|
4
|
-
"description": "Command-line tool for controlling Chrome browser
|
|
3
|
+
"version": "1.2.1",
|
|
4
|
+
"description": "Command-line tool for controlling Chrome browser via Chrome DevTools Protocol. Features: JavaScript execution, screenshots, DOM snapshots, console/network monitoring, IDE integration (Cursor commands & Claude skills). Install IDE tools with --force option for directory validation bypass.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"bin": {
|