#
tokens: 48592/50000 67/87 files (page 1/4)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 1 of 4. Use http://codebase.md/cheffromspace/mcpcontrol?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .claude
│   └── settings.local.json
├── .github
│   ├── dependabot.yml
│   ├── FUNDING.yml
│   ├── pr-webhook-utils.cjs
│   └── workflows
│       ├── ci.yml
│       ├── codeql.yml
│       └── npm-publish.yml
├── .gitignore
├── .husky
│   └── pre-commit
├── .lintstagedrc
├── .prettierignore
├── .prettierrc
├── CLAUDE.md
├── CONTRIBUTING.md
├── docs
│   ├── llms-full.txt
│   ├── providers.md
│   └── sse-transport.md
├── eslint.config.js
├── LICENSE
├── mcpcontrol-wrapper.sh
├── package-lock.json
├── package.json
├── README.md
├── RELEASE_NOTES_v0.2.0.md
├── scripts
│   ├── build.js
│   ├── compare-providers.js
│   ├── generate-test-certs.sh
│   ├── test-provider.js
│   ├── test-screenshot.cjs
│   ├── test-screenshot.mjs
│   ├── test-window.cjs
│   └── test-window.js
├── src
│   ├── config.ts
│   ├── handlers
│   │   ├── tools.test.ts
│   │   ├── tools.ts
│   │   └── tools.zod.ts
│   ├── index.ts
│   ├── interfaces
│   │   ├── automation.ts
│   │   └── provider.ts
│   ├── logger.ts
│   ├── providers
│   │   ├── autohotkey
│   │   │   ├── clipboard.ts
│   │   │   ├── index.test.ts
│   │   │   ├── index.ts
│   │   │   ├── keyboard.ts
│   │   │   ├── mouse.ts
│   │   │   ├── README.md
│   │   │   ├── screen.ts
│   │   │   └── utils.ts
│   │   ├── clipboard
│   │   │   ├── clipboardy
│   │   │   │   └── index.ts
│   │   │   └── powershell
│   │   │       ├── index.test.ts
│   │   │       └── index.ts
│   │   ├── factory.modular.test.ts
│   │   ├── factory.test.ts
│   │   ├── factory.ts
│   │   ├── keysender
│   │   │   ├── clipboard.ts
│   │   │   ├── index.test.ts
│   │   │   ├── index.ts
│   │   │   ├── keyboard.ts
│   │   │   ├── mouse.ts
│   │   │   ├── screen.test.ts
│   │   │   └── screen.ts
│   │   ├── registry.test.ts
│   │   └── registry.ts
│   ├── server.test.ts
│   ├── server.ts
│   ├── tools
│   │   ├── clipboard.ts
│   │   ├── keyboard.test.ts
│   │   ├── keyboard.ts
│   │   ├── mouse.test.ts
│   │   ├── mouse.ts
│   │   ├── screen.test.ts
│   │   ├── screen.ts
│   │   ├── screenshot-file.ts
│   │   ├── screenshot.test.ts
│   │   ├── screenshot.ts
│   │   ├── validation.zod.test.ts
│   │   └── validation.zod.ts
│   └── types
│       ├── common.ts
│       ├── keysender.d.ts
│       ├── responses.ts
│       └── transport.ts
├── test
│   ├── e2e-test.sh
│   ├── server-port.txt
│   ├── test-results.json
│   └── test-server.js
├── test-autohotkey-direct.js
├── test-autohotkey.js
├── test-panel.html
├── tsconfig.json
└── vitest.config.ts
```

# Files

--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------

```
 1 | # Build output
 2 | build/
 3 | coverage/
 4 | 
 5 | # Dependencies
 6 | node_modules/
 7 | 
 8 | # Misc
 9 | .DS_Store
10 | *.log
```

--------------------------------------------------------------------------------
/.lintstagedrc:
--------------------------------------------------------------------------------

```
1 | {
2 |   "src/**/*.ts": ["prettier --write", "npm run lint:fix"],
3 |   "test/**/*.js": ["prettier --write"]
4 | }
```

--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------

```
 1 | {
 2 |   "printWidth": 100,
 3 |   "tabWidth": 2,
 4 |   "useTabs": false,
 5 |   "semi": true,
 6 |   "singleQuote": true,
 7 |   "quoteProps": "as-needed",
 8 |   "jsxSingleQuote": false,
 9 |   "trailingComma": "all",
10 |   "bracketSpacing": true,
11 |   "bracketSameLine": false,
12 |   "arrowParens": "always",
13 |   "endOfLine": "lf"
14 | }
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Dependencies
 2 | node_modules/
 3 | libnut-core/
 4 | 
 5 | # Build output
 6 | build/
 7 | dist/
 8 | 
 9 | # Coverage
10 | coverage/
11 | 
12 | # IDE
13 | .vscode/
14 | .idea/
15 | *.sublime-workspace
16 | *.sublime-project
17 | 
18 | # Logs
19 | logs
20 | *.log
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 | 
25 | # OS
26 | .DS_Store
27 | Thumbs.db
28 | 
29 | **/.claude/settings.local.json
30 | 
31 | # Test certificates
32 | test/certs/
33 | *.pem
34 | *.crt
35 | *.key
36 | 
```

--------------------------------------------------------------------------------
/src/providers/autohotkey/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # AutoHotkey Provider for MCPControl
  2 | 
  3 | This provider implements the MCPControl automation interfaces using AutoHotkey v2.
  4 | 
  5 | ## Prerequisites
  6 | 
  7 | - AutoHotkey v2.0 or later must be installed on the system
  8 | - `AutoHotkey.exe` must be available in the system PATH
  9 | - Windows operating system (AutoHotkey is Windows-only)
 10 | 
 11 | ## Installation
 12 | 
 13 | AutoHotkey can be downloaded from: https://www.autohotkey.com/
 14 | 
 15 | Make sure to install version 2.0 or later.
 16 | 
 17 | ## Usage
 18 | 
 19 | ### Using as the primary provider
 20 | 
 21 | ```javascript
 22 | const provider = createAutomationProvider({ provider: 'autohotkey' });
 23 | ```
 24 | 
 25 | ### Using in modular configuration
 26 | 
 27 | ```javascript
 28 | const provider = createAutomationProvider({
 29 |   providers: {
 30 |     keyboard: 'autohotkey',
 31 |     mouse: 'autohotkey',
 32 |     screen: 'autohotkey',
 33 |     clipboard: 'autohotkey',
 34 |   },
 35 | });
 36 | ```
 37 | 
 38 | ### Environment Variables
 39 | 
 40 | Set the automation provider to AutoHotkey:
 41 | 
 42 | ```bash
 43 | export AUTOMATION_PROVIDER=autohotkey
 44 | ```
 45 | 
 46 | Configure the AutoHotkey executable path (optional):
 47 | 
 48 | ```bash
 49 | export AUTOHOTKEY_PATH="C:\Program Files\AutoHotkey\v2\AutoHotkey.exe"
 50 | ```
 51 | 
 52 | Or use modular configuration:
 53 | 
 54 | ```bash
 55 | export AUTOMATION_KEYBOARD_PROVIDER=autohotkey
 56 | export AUTOMATION_MOUSE_PROVIDER=autohotkey
 57 | export AUTOMATION_SCREEN_PROVIDER=autohotkey
 58 | export AUTOMATION_CLIPBOARD_PROVIDER=autohotkey
 59 | ```
 60 | 
 61 | ## Features
 62 | 
 63 | ### Keyboard Automation
 64 | - Type text
 65 | - Press individual keys
 66 | - Press key combinations
 67 | - Hold and release keys
 68 | 
 69 | ### Mouse Automation
 70 | - Move mouse to position
 71 | - Click mouse buttons
 72 | - Double-click
 73 | - Scroll
 74 | - Drag operations
 75 | - Get cursor position
 76 | 
 77 | ### Screen Automation
 78 | - Get screen size
 79 | - Capture screenshots
 80 | - Get pixel colors
 81 | - Window management (focus, resize, reposition)
 82 | - Get active window information
 83 | 
 84 | ### Clipboard Automation
 85 | - Set clipboard content
 86 | - Get clipboard content
 87 | - Check if clipboard has text
 88 | - Clear clipboard
 89 | 
 90 | ## Implementation Notes
 91 | 
 92 | The AutoHotkey provider executes AutoHotkey v2 scripts for each operation. This means:
 93 | 
 94 | 1. Each operation creates a temporary `.ahk` script file
 95 | 2. The script is executed via `AutoHotkey.exe`
 96 | 3. Results are captured through temporary files or script output
 97 | 4. Temporary files are cleaned up after execution
 98 | 
 99 | ## Performance Considerations
100 | 
101 | Since each operation requires creating and executing a script, there is some overhead compared to native implementations. For high-frequency operations, consider batching operations or using a different provider.
102 | 
103 | ## Error Handling
104 | 
105 | If AutoHotkey is not installed or not in the PATH, operations will fail with an error message. Make sure AutoHotkey v2 is properly installed and accessible.
106 | 
107 | ## Known Limitations
108 | 
109 | 1. Screenshot functionality is basic and uses Windows built-in tools (Paint, Snipping Tool)
110 | 2. Some operations may have timing issues due to the script execution model
111 | 3. Only works on Windows systems
112 | 4. Requires AutoHotkey v2 syntax (not compatible with v1)
113 | 
114 | ## Debugging
115 | 
116 | To debug AutoHotkey scripts, you can:
117 | 
118 | 1. Check the temporary script files generated in the system temp directory
119 | 2. Run the scripts manually with AutoHotkey to see any error messages
120 | 3. Enable AutoHotkey debugging features
121 | 
122 | ## Contributing
123 | 
124 | When contributing to the AutoHotkey provider:
125 | 
126 | 1. Ensure all scripts use AutoHotkey v2 syntax
127 | 2. Test on Windows with AutoHotkey v2 installed
128 | 3. Handle errors gracefully
129 | 4. Clean up temporary files properly
130 | 5. Follow the existing code structure and patterns
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # MCPControl
  2 | 
  3 | <p align="center">
  4 |   <img src="https://github.com/user-attachments/assets/1c577e56-7b8d-49e9-aaf5-b8550cc6cfc0" alt="MCPControl Logo" width="250">
  5 | </p>
  6 | 
  7 | <p align="center">
  8 |   <a href="https://github.com/Cheffromspace/MCPControl/releases/tag/v0.2.0">
  9 |     <img src="https://img.shields.io/badge/release-v0.2.0-blue.svg" alt="Latest Release">
 10 |   </a>
 11 | </p>
 12 | 
 13 | Windows control server for the [Model Context Protocol](https://modelcontextprotocol.io/), providing programmatic control over system operations including mouse, keyboard, window management, and screen capture functionality.
 14 | 
 15 | > **Note**: This project currently supports Windows only.
 16 | 
 17 | ## 🔥 Why MCPControl?
 18 | 
 19 | MCPControl bridges the gap between AI models and your desktop, enabling secure, programmatic control of:
 20 | 
 21 | - 🖱️ **Mouse movements and clicks**
 22 | - ⌨️ **Keyboard input and shortcuts**
 23 | - 🪟 **Window management**
 24 | - 📸 **Screen capture and analysis**
 25 | - 📋 **Clipboard operations**
 26 | 
 27 | ## 🔌 Quick Start
 28 | 
 29 | ### Prerequisites
 30 | 
 31 | 1. **Install Build Tools (including VC++ workload)**
 32 |    ```powershell
 33 |    # Run as Administrator - may take a few minutes to complete
 34 |    winget install Microsoft.VisualStudio.2022.BuildTools --override "--wait --passive --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended"
 35 |    ```
 36 | 
 37 | 2. **Install Python** (if not already installed)
 38 |    ```powershell
 39 |    # Install Python (required for node-gyp)
 40 |    winget install Python.Python.3.12
 41 |    ```
 42 | 
 43 | 3. **Install Node.js**
 44 |    ```powershell
 45 |    # Install latest LTS version
 46 |    winget install OpenJS.NodeJS
 47 |    ```
 48 | 
 49 | ### Installation
 50 | 
 51 | 1. **Install MCPControl Package**
 52 |    ```powershell
 53 |    npm install -g mcp-control
 54 |    ```
 55 | 
 56 | ### Configuration
 57 | 
 58 | MCPControl works best in a **virtual machine at 1280x720 resolution** for optimal click accuracy.
 59 | 
 60 | Configure your Claude client to connect to MCPControl via SSE transport:
 61 | 
 62 | #### Option 1: Direct SSE Connection
 63 | 
 64 | For connecting to an MCPControl server running on a VM or remote machine:
 65 | 
 66 | ```json
 67 | {
 68 |   "mcpServers": {
 69 |     "MCPControl": {
 70 |       "transport": "sse",
 71 |       "url": "http://192.168.1.100:3232/mcp"
 72 |     }
 73 |   }
 74 | }
 75 | ```
 76 | 
 77 | Replace `192.168.1.100:3232` with your server's IP address and port.
 78 | 
 79 | #### Option 2: Local Launch with SSE
 80 | 
 81 | To launch MCPControl locally with SSE transport:
 82 | 
 83 | ```json
 84 | {
 85 |   "mcpServers": {
 86 |     "MCPControl": {
 87 |       "command": "mcp-control",
 88 |       "args": ["--sse"]
 89 |     }
 90 |   }
 91 | }
 92 | ```
 93 | 
 94 | ### Starting the Server
 95 | 
 96 | First, start the MCPControl server on your VM or local machine:
 97 | 
 98 | ```bash
 99 | mcp-control --sse
100 | ```
101 | 
102 | The server will display:
103 | - Available network interfaces and their IP addresses
104 | - The port number (default: 3232)
105 | - Connection status messages
106 | 
107 | ### VM Setup Example
108 | 
109 | 1. **Start your Windows VM** with 1280x720 resolution
110 | 2. **Install MCPControl** on the VM:
111 |    ```bash
112 |    npm install -g mcp-control
113 |    ```
114 | 3. **Run the server** with SSE transport:
115 |    ```bash
116 |    mcp-control --sse
117 |    ```
118 | 4. **Note the VM's IP address** (e.g., `192.168.1.100`)
119 | 5. **Configure Claude** with the SSE URL:
120 |    ```json
121 |    {
122 |      "mcpServers": {
123 |        "MCPControl": {
124 |          "transport": "sse",
125 |          "url": "http://192.168.1.100:3232/mcp"
126 |        }
127 |      }
128 |    }
129 |    ```
130 | 6. **Restart Claude** and MCPControl will appear in your MCP menu!
131 | 
132 | ## 🔧 CLI Options
133 | 
134 | MCPControl supports several command-line flags for advanced configurations:
135 | 
136 | ```bash
137 | # Run with SSE transport on default port (3232)
138 | mcp-control --sse
139 | 
140 | # Run with SSE on custom port
141 | mcp-control --sse --port 3000
142 | 
143 | # Run with HTTPS/TLS (required for production deployments)
144 | mcp-control --sse --https --cert /path/to/cert.pem --key /path/to/key.pem
145 | 
146 | # Run with HTTPS on custom port
147 | mcp-control --sse --https --port 8443 --cert /path/to/cert.pem --key /path/to/key.pem
148 | ```
149 | 
150 | ### Command Line Arguments
151 | 
152 | - `--sse` - Enable SSE (Server-Sent Events) transport for network access
153 | - `--port [number]` - Specify custom port (default: 3232)
154 | - `--https` - Enable HTTPS/TLS (required for remote deployments per MCP spec)
155 | - `--cert [path]` - Path to TLS certificate file (required with --https)
156 | - `--key [path]` - Path to TLS private key file (required with --https)
157 | 
158 | ### Security Note
159 | 
160 | According to the MCP specification, HTTPS is **mandatory** for all HTTP-based transports in production environments. When deploying MCPControl for remote access, always use the `--https` flag with valid TLS certificates.
161 | 
162 | ## 🚀 Popular Use Cases
163 | 
164 | ### Assisted Automation
165 | 
166 | - **Application Testing**: Delegate repetitive UI testing to Claude, allowing AI to navigate through applications and report issues
167 | - **Workflow Automation**: Have Claude operate applications on your behalf, handling repetitive tasks while you focus on creative work
168 | - **Form Filling**: Let Claude handle data entry tasks with your supervision
169 | 
170 | ### AI Experimentation
171 | 
172 | - **AI Gaming**: Watch Claude learn to play simple games through visual feedback
173 | - **Visual Reasoning**: Test Claude's ability to navigate visual interfaces and solve visual puzzles
174 | - **Human-AI Collaboration**: Explore new interaction paradigms where Claude can see your screen and help with complex tasks
175 | 
176 | ### Development and Testing
177 | 
178 | - **Cross-Application Integration**: Bridge applications that don't normally communicate
179 | - **UI Testing Framework**: Create robust testing scenarios with visual validation
180 | - **Demo Creation**: Automate the creation of product demonstrations
181 | 
182 | ## ⚠️ IMPORTANT DISCLAIMER
183 | 
184 | **THIS SOFTWARE IS EXPERIMENTAL AND POTENTIALLY DANGEROUS**
185 | 
186 | By using this software, you acknowledge and accept that:
187 | 
188 | - Giving AI models direct control over your computer through this tool is inherently risky
189 | - This software can control your mouse, keyboard, and other system functions which could potentially cause unintended consequences
190 | - You are using this software entirely at your own risk
191 | - The creators and contributors of this project accept NO responsibility for any damage, data loss, or other consequences that may arise from using this software
192 | - This tool should only be used in controlled environments with appropriate safety measures in place
193 | 
194 | **USE AT YOUR OWN RISK**
195 | 
196 | ## 🌟 Features
197 | 
198 | <table>
199 |   <tr>
200 |     <td>
201 |       <h3>🪟 Window Management</h3>
202 |       <ul>
203 |         <li>List all windows</li>
204 |         <li>Get active window info</li>
205 |         <li>Focus, resize & reposition</li>
206 |       </ul>
207 |     </td>
208 |     <td>
209 |       <h3>🖱️ Mouse Control</h3>
210 |       <ul>
211 |         <li>Precision movement</li>
212 |         <li>Click & drag operations</li>
213 |         <li>Scrolling & position tracking</li>
214 |       </ul>
215 |     </td>
216 |   </tr>
217 |   <tr>
218 |     <td>
219 |       <h3>⌨️ Keyboard Control</h3>
220 |       <ul>
221 |         <li>Text input & key combos</li>
222 |         <li>Key press/release control</li>
223 |         <li>Hold key functionality</li>
224 |       </ul>
225 |     </td>
226 |     <td>
227 |       <h3>📸 Screen Operations</h3>
228 |       <ul>
229 |         <li>High-quality screenshots</li>
230 |         <li>Screen size detection</li>
231 |         <li>Active window capture</li>
232 |       </ul>
233 |     </td>
234 |   </tr>
235 | </table>
236 | 
237 | ## 🔧 Automation Providers
238 | 
239 | MCPControl supports multiple automation providers for different use cases:
240 | 
241 | - **keysender** (default) - Native Windows automation with high reliability
242 | - **powershell** - Windows PowerShell-based automation for simpler operations
243 | - **autohotkey** - AutoHotkey v2 scripting for advanced automation needs
244 | 
245 | ### Provider Configuration
246 | 
247 | You can configure the automation provider using environment variables:
248 | 
249 | ```bash
250 | # Use a specific provider for all operations
251 | export AUTOMATION_PROVIDER=autohotkey
252 | 
253 | # Configure AutoHotkey executable path (if not in PATH)
254 | export AUTOHOTKEY_PATH="C:\Program Files\AutoHotkey\v2\AutoHotkey.exe"
255 | ```
256 | 
257 | Or use modular configuration for specific operations:
258 | 
259 | ```bash
260 | # Mix and match providers for different operations
261 | export AUTOMATION_KEYBOARD_PROVIDER=autohotkey
262 | export AUTOMATION_MOUSE_PROVIDER=keysender
263 | export AUTOMATION_SCREEN_PROVIDER=keysender  
264 | export AUTOMATION_CLIPBOARD_PROVIDER=powershell
265 | ```
266 | 
267 | See provider-specific documentation:
268 | - [AutoHotkey Provider](src/providers/autohotkey/README.md)
269 | 
270 | ## 🛠️ Development Setup
271 | 
272 | If you're interested in contributing or building from source, please see [CONTRIBUTING.md](CONTRIBUTING.md) for detailed instructions.
273 | 
274 | ### Development Requirements
275 | 
276 | To build this project for development, you'll need:
277 | 
278 | 1. Windows operating system (required for the keysender dependency)
279 | 2. Node.js 18 or later (install using the official Windows installer which includes build tools)
280 | 3. npm package manager
281 | 4. Native build tools:
282 |    - node-gyp: `npm install -g node-gyp`
283 |    - cmake-js: `npm install -g cmake-js`
284 | 
285 | The keysender dependency relies on Windows-specific native modules that require these build tools.
286 | 
287 | ## 📋 Project Structure
288 | 
289 | - `/src`
290 |   - `/handlers` - Request handlers and tool management
291 |   - `/tools` - Core functionality implementations
292 |   - `/types` - TypeScript type definitions
293 |   - `index.ts` - Main application entry point
294 | 
295 | ## 🔖 Repository Branches
296 | 
297 | - **`main`** - Main development branch with the latest features and changes
298 | - **`release`** - Stable release branch that mirrors the latest stable tag (currently v0.2.0)
299 | 
300 | ### Version Installation
301 | 
302 | You can install specific versions of MCPControl using npm:
303 | 
304 | ```bash
305 | # Install the latest stable release (from release branch)
306 | npm install mcp-control
307 | 
308 | # Install a specific version
309 | npm install [email protected]
310 | ```
311 | 
312 | ## 📚 Dependencies
313 | 
314 | - [@modelcontextprotocol/sdk](https://www.npmjs.com/package/@modelcontextprotocol/sdk) - MCP SDK for protocol implementation
315 | - [keysender](https://www.npmjs.com/package/keysender) - Windows-only UI automation library
316 | - [clipboardy](https://www.npmjs.com/package/clipboardy) - Clipboard handling
317 | - [sharp](https://www.npmjs.com/package/sharp) - Image processing
318 | - [uuid](https://www.npmjs.com/package/uuid) - UUID generation
319 | 
320 | ## 🚧 Known Limitations
321 | 
322 | - Window minimize/restore operations are currently unsupported
323 | - Multiple screen functions may not work as expected, depending on setup
324 | - The get_screenshot utility does not work with the VS Code Extension Cline. See [GitHub issue #1865](https://github.com/cline/cline/issues/1865)
325 | - Some operations may require elevated permissions depending on the target application
326 | - Only Windows is supported
327 | - MCPControl works best at 1280x720 resolution, single screen. Click accuracy is optimized for this resolution. We're working on an offset/scaling bug and looking for testers or help creating testing tools
328 | 
329 | ## 👥 Contributing
330 | 
331 | See [CONTRIBUTING.md](CONTRIBUTING.md)
332 | 
333 | ## ⚖️ License
334 | 
335 | This project is licensed under the MIT License - see the LICENSE file for details.
336 | 
337 | ## 📖 References
338 | 
339 | - [Model Context Protocol Documentation](https://modelcontextprotocol.github.io/)
340 | 
341 | [![MseeP.ai Security Assessment Badge](https://mseep.net/pr/cheffromspace-mcpcontrol-badge.png)](https://mseep.ai/app/cheffromspace-mcpcontrol)
342 | 
343 | 
```

--------------------------------------------------------------------------------
/CLAUDE.md:
--------------------------------------------------------------------------------

```markdown
 1 | # MCPControl - Development Guide
 2 | 
 3 | ## Build & Test Commands
 4 | - Build: `pwsh.exe -c "npm run build"` - Compiles TypeScript to JavaScript
 5 | - Lint: `pwsh.exe -c "npm run lint"` - Runs ESLint to check code quality (TS and JS)
 6 | - Format: `pwsh.exe -c "npm run format"` - Runs Prettier to format code
 7 | - Format Check: `pwsh.exe -c "npm run format:check"` - Checks if files are properly formatted
 8 | - Test: `pwsh.exe -c "npm run test"` - Runs all Vitest tests
 9 | - Run single test: `pwsh.exe -c "npm run test -- tools/keyboard.test.ts"` or `pwsh.exe -c "npm run test -- -t \"specific test name\""`
10 | - Watch tests: `pwsh.exe -c "npm run test:watch"` - Runs tests in watch mode
11 | - Coverage: `pwsh.exe -c "npm run test:coverage"` - Generates test coverage report
12 | - E2E Test: `cd test && ./e2e-test.sh [iterations]` - Runs end-to-end tests with Claude and MCPControl
13 | 
14 | ## Running with HTTPS/TLS
15 | MCPControl supports HTTPS for secure SSE connections (mandatory per MCP spec for production):
16 | - `node build/index.js --sse --https --cert /path/to/cert.pem --key /path/to/key.pem`
17 | - Default HTTPS port is still 3232 (use --port to change)
18 | - Both --cert and --key are required when using --https
19 | 
20 | > Note: MCP Servers are typically launched by the Client as a subprocess.
21 | 
22 | ## Code Style Guidelines
23 | - **Imports**: Use ES module syntax with named imports
24 | - **Types**: Define TypeScript interfaces for inputs/outputs in `types/` directory
25 | - **Error Handling**: Use try/catch with standardized response objects
26 | - **Naming**: camelCase for variables/functions, PascalCase for interfaces
27 | - **Functions**: Keep functions small and focused on single responsibility
28 | - **Comments**: Add JSDoc comments for public APIs
29 | - **Testing**: 
30 |   - Unit tests: Place in same directory as implementation with `.test.ts` suffix
31 |   - E2E tests: Added to the `test/` directory
32 | - **Formatting**: Code is formatted using Prettier (pre-commit hooks will run automatically)
33 | - **Error Responses**: Return `{ success: false, message: string }` for errors
34 | - **Success Responses**: Return `{ success: true, data?: any }` for success
35 | - **Linting**: Both TypeScript and JavaScript files are linted with ESLint
```

--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Contributing to MCP Control
  2 | 
  3 | Thank you for your interest in contributing to MCP Control! This document provides guidelines and instructions for contributing to the project.
  4 | 
  5 | ## Table of Contents
  6 | 
  7 | - [Code of Conduct](#code-of-conduct)
  8 | - [Getting Started](#getting-started)
  9 |   - [Prerequisites](#prerequisites)
 10 |   - [Setup](#setup)
 11 | - [Development Workflow](#development-workflow)
 12 |   - [Branching Strategy](#branching-strategy)
 13 |   - [Commit Guidelines](#commit-guidelines)
 14 |   - [Pull Requests](#pull-requests)
 15 | - [Code Style and Standards](#code-style-and-standards)
 16 | - [Testing](#testing)
 17 | - [Documentation](#documentation)
 18 | - [Project Structure](#project-structure)
 19 | - [Issue Tracking](#issue-tracking)
 20 | - [Future Roadmap](#future-roadmap)
 21 | 
 22 | ## Code of Conduct
 23 | 
 24 | Please be respectful and considerate of others when contributing to this project. We aim to foster an inclusive and welcoming community.
 25 | 
 26 | ## Getting Started
 27 | 
 28 | ### Prerequisites
 29 | 
 30 | - Node.js (latest LTS version recommended)
 31 | - npm
 32 | - git
 33 | - C++ compiler (for building native modules)
 34 | 
 35 | ### Setup
 36 | 
 37 | 1. Fork the repository
 38 | 2. Clone your fork:
 39 |    ```bash
 40 |    git clone https://github.com/YOUR-USERNAME/MCPControl.git
 41 |    cd MCPControl
 42 |    ```
 43 | 
 44 | 3. Build the project:
 45 |    ```bash
 46 |    # Install dependencies
 47 |    npm install
 48 | 
 49 |    # Build the project
 50 |    npm run build
 51 |    ```
 52 | 
 53 | ## Development Workflow
 54 | 
 55 | ### Branching Strategy
 56 | 
 57 | - `main` branch contains the latest stable code
 58 | - Create feature branches from `main` using the naming convention:
 59 |   - `feature/feature-name` for new features
 60 |   - `bugfix/issue-description` for bug fixes
 61 |   - `docs/description` for documentation changes
 62 |   - `refactor/description` for code refactoring
 63 | 
 64 | ### Commit Guidelines
 65 | 
 66 | - Write clear, descriptive commit messages
 67 | - Reference issue numbers in commit messages when applicable
 68 | - Keep commits focused on a single logical change
 69 | 
 70 | ### Pull Requests
 71 | 
 72 | 1. Create your feature branch: `git checkout -b feature/amazing-feature`
 73 | 2. Commit your changes: `git commit -m 'Add some amazing feature'`
 74 | 3. Push to the branch: `git push origin feature/amazing-feature`
 75 | 4. Open a Pull Request
 76 | 5. Ensure all tests pass and code meets the project standards
 77 | 6. Request a review from a maintainer
 78 | 
 79 | ## Code Style and Standards
 80 | 
 81 | - Use ES module syntax with named imports
 82 | - Define TypeScript interfaces for inputs/outputs in the `types/` directory
 83 | - Use try/catch with standardized response objects for error handling
 84 | - Follow naming conventions:
 85 |   - camelCase for variables/functions
 86 |   - PascalCase for interfaces
 87 | - Keep functions small and focused on single responsibility
 88 | - Add JSDoc comments for public APIs
 89 | - Use 2-space indentation and semicolons
 90 | - For errors, return `{ success: false, message: string }`
 91 | - For success, return `{ success: true, data?: any }`
 92 | 
 93 | ## Testing
 94 | 
 95 | - Place tests in the same directory as implementation with `.test.ts` suffix
 96 | - Run tests with `npm run test`
 97 | - Generate coverage report with `npm run test:coverage`
 98 | - Run a single test with `npm run test -- tools/keyboard.test.ts` or `npm run test -- -t "specific test name"`
 99 | - Run tests in watch mode with `npm run test:watch`
100 | 
101 | All new features should include appropriate test coverage. The project uses Vitest for testing.
102 | 
103 | ## Documentation
104 | 
105 | - Document public APIs with JSDoc comments
106 | - Update README.md when adding new features or changing functionality
107 | - Keep code comments clear and focused on explaining "why" rather than "what"
108 | 
109 | ## Project Structure
110 | 
111 | - `/src`
112 |   - `/handlers` - Request handlers and tool management
113 |   - `/tools` - Core functionality implementations
114 |   - `/types` - TypeScript type definitions
115 |   - `index.ts` - Main application entry point
116 | 
117 | ## Issue Tracking
118 | 
119 | Check the [GitHub issues](https://github.com/Cheffromspace/MCPControl/issues) for existing issues you might want to contribute to. Current focus areas include:
120 | 
121 | 1. Creating an npm package for easy installation
122 | 2. Adding remote computer control support
123 | 3. Building a dedicated test application
124 | 
125 | When creating a new issue:
126 | - Use descriptive titles
127 | - Include steps to reproduce for bugs
128 | - For feature requests, explain the use case and potential implementation approach
129 | 
130 | ## Future Roadmap
131 | 
132 | Current roadmap and planned features include:
133 | 
134 | - Fixing click accuracy issues with different resolutions and multi-screen setups
135 | - Security implementation improvements
136 | - Comprehensive testing
137 | - Error handling enhancements
138 | - Performance optimization
139 | - Automation framework
140 | - Enhanced window management
141 | - Advanced integration features
142 | 
143 | ## Publishing
144 | 
145 | This project uses GitHub Actions to automatically publish to npm when a version tag is pushed to main:
146 | 
147 | 1. Ensure changes are merged to main
148 | 2. Create and push a tag with the version number:
149 |    ```bash
150 |    git tag v1.2.3
151 |    git push origin v1.2.3
152 |    ```
153 | 3. The GitHub Action will automatically:
154 |    - Build and test the package
155 |    - Update the version in package.json
156 |    - Publish to npm
157 | 
158 | Note: You need to have the `NPM_TOKEN` secret configured in the GitHub repository settings.
159 | 
160 | ---
161 | 
162 | Thank you for contributing to MCP Control!
163 | 
```

--------------------------------------------------------------------------------
/test/server-port.txt:
--------------------------------------------------------------------------------

```
1 | 8080
```

--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------

```yaml
1 | ko_fi: Cheffromspace
2 | 
```

--------------------------------------------------------------------------------
/src/providers/autohotkey/utils.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Utility functions for AutoHotkey provider
 3 |  */
 4 | 
 5 | /**
 6 |  * Get the path to AutoHotkey executable
 7 |  * Can be configured via AUTOHOTKEY_PATH environment variable
 8 |  */
 9 | export function getAutoHotkeyPath(): string {
10 |   return process.env.AUTOHOTKEY_PATH || 'AutoHotkey.exe';
11 | }
12 | 
```

--------------------------------------------------------------------------------
/.claude/settings.local.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "permissions": {
 3 |     "allow": [
 4 |       "Bash(gh repo view:*)",
 5 |       "Bash(gh repo view:*)",
 6 |       "Bash(gh label create:*)",
 7 |       "Bash(gh label create:*)",
 8 |       "Bash(gh issue create:*)",
 9 |       "Bash(gh issue create:*)",
10 |       "Bash(mkdir:*)"
11 |     ],
12 |     "deny": []
13 |   }
14 | }
```

--------------------------------------------------------------------------------
/src/interfaces/provider.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {
 2 |   KeyboardAutomation,
 3 |   MouseAutomation,
 4 |   ScreenAutomation,
 5 |   ClipboardAutomation,
 6 | } from './automation.js';
 7 | 
 8 | export interface AutomationProvider {
 9 |   keyboard: KeyboardAutomation;
10 |   mouse: MouseAutomation;
11 |   screen: ScreenAutomation;
12 |   clipboard: ClipboardAutomation;
13 | }
14 | 
```

--------------------------------------------------------------------------------
/src/types/transport.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Enumeration of supported transport types for MCP server communication
 3 |  */
 4 | export enum TransportType {
 5 |   /**
 6 |    * HTTP transport for standard request-response communication
 7 |    */
 8 |   HTTP = 'HTTP',
 9 | 
10 |   /**
11 |    * Server-Sent Events transport for real-time unidirectional event streaming
12 |    */
13 |   SSE = 'SSE',
14 | }
15 | 
```

--------------------------------------------------------------------------------
/mcpcontrol-wrapper.sh:
--------------------------------------------------------------------------------

```bash
1 | #!/bin/bash
2 | # mcpcontrol-wrapper.sh - Bridge script for Claude CLI to run MCPControl from WSL
3 | 
4 | # Windows path translation
5 | WIN_PATH=$(echo "$PWD" | sed -E 's|^/mnt/([a-z])(/.*)|\1:\\\2|g' | sed 's|/|\\|g')
6 | 
7 | # Run PowerShell.exe with absolute path to ensure it's found
8 | exec /mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -c "cd '$WIN_PATH'; npm run build; node build/index.js"
9 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2020",
 4 |     "module": "ES2020",
 5 |     "moduleResolution": "node",
 6 |     "outDir": "./build",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true,
12 |     "declaration": true,
13 |     "noImplicitAny": true
14 |   },
15 |   "include": ["src/**/*"],
16 |   "exclude": ["node_modules", "build"]
17 | }
18 | 
```

--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { defineConfig } from 'vitest/config'
 2 | 
 3 | export default defineConfig({
 4 |   test: {
 5 |     globals: true,
 6 |     environment: 'node',
 7 |     coverage: {
 8 |       provider: 'v8',
 9 |       reporter: ['text', 'html'],
10 |       exclude: [
11 |         'node_modules/**',
12 |         'build/**',
13 |         '**/*.d.ts',
14 |         'vitest.config.ts'
15 |       ]
16 |     },
17 |     include: ['src/**/*.test.ts'],
18 |     exclude: ['node_modules', 'build']
19 |   }
20 | })
21 | 
```

--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------

```yaml
 1 | version: 2
 2 | updates:
 3 |   - package-ecosystem: "npm"
 4 |     directory: "/"
 5 |     schedule:
 6 |       interval: "weekly"
 7 |     open-pull-requests-limit: 10
 8 |     versioning-strategy: increase
 9 |     groups:
10 |       dev-dependencies:
11 |         patterns:
12 |           - "@types/*"
13 |           - "vitest"
14 |           - "@vitest/*"
15 |       production-dependencies:
16 |         patterns:
17 |           - "@modelcontextprotocol/*"
18 |           - "@nut-tree/*"
19 |           - "sharp"
20 |           - "jimp"
21 |     commit-message:
22 |       prefix: "chore"
23 |       include: "scope"
24 | 
```

--------------------------------------------------------------------------------
/test/test-results.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "buttonClicks": [
 3 |     {
 4 |       "timestamp": "2025-04-26T21:31:57.589Z",
 5 |       "buttonId": "A",
 6 |       "count": 1
 7 |     },
 8 |     {
 9 |       "timestamp": "2025-04-26T23:50:04.745Z",
10 |       "buttonId": "8",
11 |       "count": 1
12 |     }
13 |   ],
14 |   "sequences": [
15 |     {
16 |       "timestamp": "2025-04-26T21:31:57.591Z",
17 |       "sequence": "A"
18 |     },
19 |     {
20 |       "timestamp": "2025-04-26T23:50:04.747Z",
21 |       "sequence": "A8"
22 |     }
23 |   ],
24 |   "finalSequence": "A8",
25 |   "startTime": "2025-04-26T21:31:42.896Z",
26 |   "endTime": "2025-04-26T23:50:04.747Z"
27 | }
```

--------------------------------------------------------------------------------
/src/handlers/tools.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
 2 | import { setupTools as setupToolsWithZod } from './tools.zod.js';
 3 | import { AutomationProvider } from '../interfaces/provider.js';
 4 | 
 5 | /**
 6 |  * Set up automation tools on the MCP server using Zod validation.
 7 |  * This function provides robust validation with better error messages.
 8 |  *
 9 |  * @param server The Model Context Protocol server instance
10 |  * @param provider The automation provider implementation
11 |  */
12 | export function setupTools(server: Server, provider: AutomationProvider): void {
13 |   setupToolsWithZod(server, provider);
14 | }
15 | 
```

--------------------------------------------------------------------------------
/src/types/responses.ts:
--------------------------------------------------------------------------------

```typescript
 1 | interface ImageContent {
 2 |   type: 'image';
 3 |   data: Buffer | string; // Buffer for binary data, string for base64
 4 |   mimeType: string;
 5 |   encoding?: 'binary' | 'base64'; // Specify the encoding type
 6 | }
 7 | 
 8 | export interface ScreenshotResponse {
 9 |   screenshot: Buffer | string; // Buffer for binary data, string for base64
10 |   timestamp: string;
11 |   encoding: 'binary' | 'base64';
12 | }
13 | 
14 | export interface WindowsControlResponse {
15 |   success: boolean;
16 |   message: string;
17 |   data?: unknown;
18 |   screenshot?: Buffer | string; // Buffer for binary data, string for base64
19 |   content?: ImageContent[]; // MCP image content for screenshots
20 |   encoding?: 'binary' | 'base64'; // Specify the encoding type
21 | }
22 | 
```

--------------------------------------------------------------------------------
/test-autohotkey-direct.js:
--------------------------------------------------------------------------------

```javascript
 1 | // Direct test of AutoHotkey provider without factory
 2 | import { AutoHotkeyProvider } from './build/providers/autohotkey/index.js';
 3 | 
 4 | // Create the provider directly
 5 | const provider = new AutoHotkeyProvider();
 6 | 
 7 | console.log('AutoHotkey provider created successfully');
 8 | console.log('Provider has keyboard:', !!provider.keyboard);
 9 | console.log('Provider has mouse:', !!provider.mouse);
10 | console.log('Provider has screen:', !!provider.screen);
11 | console.log('Provider has clipboard:', !!provider.clipboard);
12 | 
13 | // Test a simple keyboard operation
14 | console.log('\nTesting keyboard.typeText method...');
15 | const result = provider.keyboard.typeText({ text: 'Hello from AutoHotkey!' });
16 | console.log('Result:', result);
17 | 
18 | console.log('\nAutoHotkey provider is ready to use!');
```

--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: "CodeQL"
 2 | 
 3 | on:
 4 |   push:
 5 |     branches: [ "master" ]
 6 |   pull_request:
 7 |     branches: [ "master" ]
 8 |   schedule:
 9 |     - cron: '30 1 * * 0'
10 | 
11 | jobs:
12 |   analyze:
13 |     name: Analyze
14 |     runs-on: windows-latest
15 |     permissions:
16 |       actions: read
17 |       contents: read
18 |       security-events: write
19 | 
20 |     strategy:
21 |       fail-fast: false
22 |       matrix:
23 |         language: [ 'typescript' ]
24 | 
25 |     steps:
26 |     - name: Checkout repository
27 |       uses: actions/checkout@v4
28 | 
29 |     - name: Initialize CodeQL
30 |       uses: github/codeql-action/init@v3
31 |       with:
32 |         languages: ${{ matrix.language }}
33 | 
34 |     - name: Autobuild
35 |       uses: github/codeql-action/autobuild@v3
36 | 
37 |     - name: Perform CodeQL Analysis
38 |       uses: github/codeql-action/analyze@v3
39 |       with:
40 |         category: "/language:${{matrix.language}}"
41 | 
```

--------------------------------------------------------------------------------
/test-autohotkey.js:
--------------------------------------------------------------------------------

```javascript
 1 | // Simple test script to verify AutoHotkey provider works
 2 | import { createAutomationProvider } from './build/providers/factory.js';
 3 | 
 4 | // Use AutoHotkey as the provider
 5 | const provider = createAutomationProvider({ provider: 'autohotkey' });
 6 | 
 7 | console.log('AutoHotkey provider created successfully');
 8 | console.log('Provider has keyboard:', !!provider.keyboard);
 9 | console.log('Provider has mouse:', !!provider.mouse);
10 | console.log('Provider has screen:', !!provider.screen);
11 | console.log('Provider has clipboard:', !!provider.clipboard);
12 | 
13 | // You can also use modular configuration
14 | const modularProvider = createAutomationProvider({
15 |   providers: {
16 |     keyboard: 'autohotkey',
17 |     mouse: 'autohotkey',
18 |     screen: 'autohotkey',
19 |     clipboard: 'autohotkey',
20 |   },
21 | });
22 | 
23 | console.log('\nModular provider created successfully');
```

--------------------------------------------------------------------------------
/scripts/generate-test-certs.sh:
--------------------------------------------------------------------------------

```bash
 1 | #!/bin/bash
 2 | 
 3 | # Generate self-signed certificates for testing HTTPS support
 4 | # NOT FOR PRODUCTION USE
 5 | 
 6 | echo "Generating self-signed certificates for testing..."
 7 | 
 8 | # Create certs directory if it doesn't exist
 9 | mkdir -p test/certs
10 | 
11 | # Generate private key
12 | openssl genrsa -out test/certs/key.pem 2048
13 | 
14 | # Generate certificate signing request
15 | openssl req -new -key test/certs/key.pem -out test/certs/csr.pem \
16 |     -subj "/C=US/ST=Test/L=Test/O=MCPControl/OU=Test/CN=localhost"
17 | 
18 | # Generate self-signed certificate
19 | openssl x509 -req -days 365 -in test/certs/csr.pem \
20 |     -signkey test/certs/key.pem -out test/certs/cert.pem
21 | 
22 | # Clean up CSR
23 | rm test/certs/csr.pem
24 | 
25 | echo "Test certificates generated successfully!"
26 | echo "Certificate: test/certs/cert.pem"
27 | echo "Private key: test/certs/key.pem"
28 | echo ""
29 | echo "To test HTTPS support, run:"
30 | echo "node build/index.js --sse --https --cert test/certs/cert.pem --key test/certs/key.pem"
```

--------------------------------------------------------------------------------
/src/tools/screenshot.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { createAutomationProvider } from '../providers/factory.js';
 2 | import { ScreenshotOptions } from '../types/common.js';
 3 | import { WindowsControlResponse } from '../types/responses.js';
 4 | 
 5 | /**
 6 |  * Captures a screenshot with various options for optimization
 7 |  *
 8 |  * @param options Optional configuration for the screenshot
 9 |  * @returns Promise resolving to a WindowsControlResponse with the screenshot data
10 |  */
11 | export async function getScreenshot(options?: ScreenshotOptions): Promise<WindowsControlResponse> {
12 |   try {
13 |     // Create a provider instance to handle the screenshot
14 |     const provider = createAutomationProvider();
15 | 
16 |     // Delegate to the provider's screenshot implementation
17 |     const result = await provider.screen.getScreenshot(options);
18 | 
19 |     // Return the result directly from the provider
20 |     return result;
21 |   } catch (error) {
22 |     return {
23 |       success: false,
24 |       message: `Failed to capture screenshot: ${error instanceof Error ? error.message : String(error)}`,
25 |     };
26 |   }
27 | }
28 | 
```

--------------------------------------------------------------------------------
/src/providers/autohotkey/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { AutomationProvider } from '../../interfaces/provider.js';
 2 | import {
 3 |   KeyboardAutomation,
 4 |   MouseAutomation,
 5 |   ScreenAutomation,
 6 |   ClipboardAutomation,
 7 | } from '../../interfaces/automation.js';
 8 | import { AutoHotkeyKeyboardAutomation } from './keyboard.js';
 9 | import { AutoHotkeyMouseAutomation } from './mouse.js';
10 | import { AutoHotkeyScreenAutomation } from './screen.js';
11 | import { AutoHotkeyClipboardAutomation } from './clipboard.js';
12 | 
13 | /**
14 |  * AutoHotkey implementation of the AutomationProvider
15 |  *
16 |  * NOTE: This provider requires AutoHotkey v2.0+ to be installed on the system.
17 |  * It executes AutoHotkey scripts to perform automation tasks.
18 |  */
19 | export class AutoHotkeyProvider implements AutomationProvider {
20 |   keyboard: KeyboardAutomation;
21 |   mouse: MouseAutomation;
22 |   screen: ScreenAutomation;
23 |   clipboard: ClipboardAutomation;
24 | 
25 |   constructor() {
26 |     this.keyboard = new AutoHotkeyKeyboardAutomation();
27 |     this.mouse = new AutoHotkeyMouseAutomation();
28 |     this.screen = new AutoHotkeyScreenAutomation();
29 |     this.clipboard = new AutoHotkeyClipboardAutomation();
30 |   }
31 | }
32 | 
```

--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Configuration interface for automation settings
 3 |  */
 4 | export interface AutomationConfig {
 5 |   /**
 6 |    * Legacy: The provider to use for all automation
 7 |    * Currently supported: 'keysender'
 8 |    */
 9 |   provider?: string;
10 | 
11 |   /**
12 |    * New: Modular provider configuration
13 |    * Allows mixing different providers for different components
14 |    */
15 |   providers?: {
16 |     keyboard?: string;
17 |     mouse?: string;
18 |     screen?: string;
19 |     clipboard?: string;
20 |   };
21 | }
22 | 
23 | /**
24 |  * Load configuration from environment variables
25 |  */
26 | export function loadConfig(): AutomationConfig {
27 |   // Check for new modular configuration
28 |   const keyboard = process.env.AUTOMATION_KEYBOARD_PROVIDER;
29 |   const mouse = process.env.AUTOMATION_MOUSE_PROVIDER;
30 |   const screen = process.env.AUTOMATION_SCREEN_PROVIDER;
31 |   const clipboard = process.env.AUTOMATION_CLIPBOARD_PROVIDER;
32 | 
33 |   if (keyboard || mouse || screen || clipboard) {
34 |     return {
35 |       providers: {
36 |         keyboard,
37 |         mouse,
38 |         screen,
39 |         clipboard,
40 |       },
41 |     };
42 |   }
43 | 
44 |   // Fall back to legacy configuration
45 |   return {
46 |     provider: process.env.AUTOMATION_PROVIDER || 'keysender',
47 |   };
48 | }
49 | 
```

--------------------------------------------------------------------------------
/src/providers/factory.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, it, expect, vi } from 'vitest';
 2 | import { createAutomationProvider } from './factory.js';
 3 | import { KeysenderProvider } from './keysender/index.js';
 4 | 
 5 | // Mock the providers
 6 | vi.mock('./keysender/index.js', () => {
 7 |   return {
 8 |     KeysenderProvider: vi.fn().mockImplementation(() => ({
 9 |       keyboard: {},
10 |       mouse: {},
11 |       screen: {},
12 |       clipboard: {},
13 |     })),
14 |   };
15 | });
16 | 
17 | describe('createAutomationProvider', () => {
18 |   it('should create KeysenderProvider by default', () => {
19 |     const provider = createAutomationProvider();
20 |     expect(KeysenderProvider).toHaveBeenCalled();
21 |     expect(provider).toBeDefined();
22 |   });
23 | 
24 |   it('should create KeysenderProvider when explicitly specified', () => {
25 |     const provider = createAutomationProvider({ provider: 'keysender' });
26 |     expect(KeysenderProvider).toHaveBeenCalled();
27 |     expect(provider).toBeDefined();
28 |   });
29 | 
30 |   it('should be case insensitive for KeysenderProvider', () => {
31 |     const provider = createAutomationProvider({ provider: 'KeYsEnDeR' });
32 |     expect(KeysenderProvider).toHaveBeenCalled();
33 |     expect(provider).toBeDefined();
34 |   });
35 | 
36 |   it('should throw error for unknown provider type', () => {
37 |     expect(() => createAutomationProvider({ provider: 'unknown' })).toThrow(
38 |       'Unknown provider type: unknown',
39 |     );
40 |   });
41 | });
42 | 
```

--------------------------------------------------------------------------------
/src/providers/keysender/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { AutomationProvider } from '../../interfaces/provider.js';
 2 | import {
 3 |   KeyboardAutomation,
 4 |   MouseAutomation,
 5 |   ScreenAutomation,
 6 |   ClipboardAutomation,
 7 | } from '../../interfaces/automation.js';
 8 | import { KeysenderKeyboardAutomation } from './keyboard.js';
 9 | import { KeysenderMouseAutomation } from './mouse.js';
10 | import { KeysenderScreenAutomation } from './screen.js';
11 | import { KeysenderClipboardAutomation } from './clipboard.js';
12 | 
13 | /**
14 |  * Keysender implementation of the AutomationProvider
15 |  *
16 |  * NOTE: This provider requires the Windows operating system to compile native dependencies.
17 |  * Building this module on non-Windows platforms will fail.
18 |  * Development requires:
19 |  * - Node.js installed via the official Windows installer (includes necessary build tools)
20 |  * - node-gyp installed globally (npm install -g node-gyp)
21 |  * - cmake-js installed globally (npm install -g cmake-js)
22 |  */
23 | export class KeysenderProvider implements AutomationProvider {
24 |   keyboard: KeyboardAutomation;
25 |   mouse: MouseAutomation;
26 |   screen: ScreenAutomation;
27 |   clipboard: ClipboardAutomation;
28 | 
29 |   constructor() {
30 |     this.keyboard = new KeysenderKeyboardAutomation();
31 |     this.mouse = new KeysenderMouseAutomation();
32 |     this.screen = new KeysenderScreenAutomation();
33 |     this.clipboard = new KeysenderClipboardAutomation();
34 |   }
35 | }
36 | 
```

--------------------------------------------------------------------------------
/src/tools/mouse.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, it, expect, vi, beforeEach } from 'vitest';
 2 | import { clickAt } from './mouse.js';
 3 | 
 4 | // Mock the provider
 5 | vi.mock('../providers/factory.js', () => ({
 6 |   createAutomationProvider: () => ({
 7 |     mouse: {
 8 |       clickAt: vi.fn().mockImplementation((x, y, button) => ({
 9 |         success: true,
10 |         message: `Clicked ${button} button at position (${x}, ${y})`,
11 |       })),
12 |       getCursorPosition: vi.fn().mockReturnValue({
13 |         success: true,
14 |         message: 'Current cursor position',
15 |         data: { x: 10, y: 20 },
16 |       }),
17 |     },
18 |   }),
19 | }));
20 | 
21 | describe('Mouse Tools', () => {
22 |   beforeEach(() => {
23 |     vi.clearAllMocks();
24 |   });
25 | 
26 |   describe('clickAt', () => {
27 |     it('should click at the specified position', () => {
28 |       const result = clickAt(100, 200);
29 | 
30 |       // Verify success response
31 |       expect(result.success).toBe(true);
32 |       expect(result.message).toContain('Clicked left button at position (100, 200)');
33 |     });
34 | 
35 |     it('should support different mouse buttons', () => {
36 |       const result = clickAt(100, 200, 'right');
37 | 
38 |       expect(result.success).toBe(true);
39 |       expect(result.message).toContain('Clicked right button');
40 |     });
41 | 
42 |     it('should handle invalid coordinates', () => {
43 |       const result = clickAt(NaN, 200);
44 | 
45 |       expect(result.success).toBe(false);
46 |       expect(result.message).toBe('Invalid coordinates provided');
47 |     });
48 |   });
49 | });
50 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "mcp-control",
 3 |   "version": "0.2.0",
 4 |   "description": "Windows control server for the Model Context Protocol",
 5 |   "license": "MIT",
 6 |   "type": "module",
 7 |   "main": "build/index.js",
 8 |   "bin": "build/index.js",
 9 |   "scripts": {
10 |     "build": "tsc",
11 |     "build:all": "node scripts/build.js",
12 |     "start": "node build/index.js",
13 |     "dev": "tsc -w",
14 |     "test": "vitest run",
15 |     "test:watch": "vitest",
16 |     "test:coverage": "vitest run --coverage",
17 |     "lint": "eslint src --ext .ts",
18 |     "lint:fix": "eslint src --ext .ts --fix",
19 |     "format": "prettier --write \"src/**/*.{ts,js}\" \"test/**/*.js\"",
20 |     "format:check": "prettier --check \"src/**/*.{ts,js}\" \"test/**/*.js\"",
21 |     "prepare": "husky"
22 |   },
23 |   "dependencies": {
24 |     "@modelcontextprotocol/sdk": "^1.16.0",
25 |     "clipboardy": "^4.0.0",
26 |     "keysender": "^2.3.0",
27 |     "sharp": "^0.34.3",
28 |     "ulid": "^3.0.1",
29 |     "uuid": "^11.1.0",
30 |     "zod": "^3.25.1"
31 |   },
32 |   "devDependencies": {
33 |     "@eslint/js": "^9.31.0",
34 |     "@types/express": "^5.0.2",
35 |     "@types/node": "^22.15.19",
36 |     "@types/uuid": "^10.0.0",
37 |     "@typescript-eslint/eslint-plugin": "^8.37.0",
38 |     "@typescript-eslint/parser": "^8.36.0",
39 |     "@vitest/coverage-v8": "^3.1.3",
40 |     "audit-ci": "^7.1.0",
41 |     "eslint": "^9.31.0",
42 |     "husky": "^9.1.7",
43 |     "lint-staged": "^16.1.2",
44 |     "prettier": "^3.5.3",
45 |     "typescript": "^5.8.3",
46 |     "typescript-eslint": "^8.32.1",
47 |     "vitest": "^3.0.8"
48 |   }
49 | }
50 | 
```

--------------------------------------------------------------------------------
/src/tools/clipboard.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import clipboardy from 'clipboardy';
 2 | import { ClipboardInput } from '../types/common.js';
 3 | 
 4 | export async function getClipboardContent(): Promise<{
 5 |   success: boolean;
 6 |   content?: string;
 7 |   error?: string;
 8 | }> {
 9 |   try {
10 |     const content = await clipboardy.read();
11 |     return {
12 |       success: true,
13 |       content,
14 |     };
15 |   } catch (error) {
16 |     return {
17 |       success: false,
18 |       error: error instanceof Error ? error.message : String(error),
19 |     };
20 |   }
21 | }
22 | 
23 | export async function setClipboardContent(
24 |   input: ClipboardInput,
25 | ): Promise<{ success: boolean; error?: string }> {
26 |   try {
27 |     await clipboardy.write(input.text);
28 |     return {
29 |       success: true,
30 |     };
31 |   } catch (error) {
32 |     return {
33 |       success: false,
34 |       error: error instanceof Error ? error.message : String(error),
35 |     };
36 |   }
37 | }
38 | 
39 | export async function hasClipboardText(): Promise<{
40 |   success: boolean;
41 |   hasText?: boolean;
42 |   error?: string;
43 | }> {
44 |   try {
45 |     const content = await clipboardy.read();
46 |     return {
47 |       success: true,
48 |       hasText: content.length > 0,
49 |     };
50 |   } catch (error) {
51 |     return {
52 |       success: false,
53 |       error: error instanceof Error ? error.message : String(error),
54 |     };
55 |   }
56 | }
57 | 
58 | export async function clearClipboard(): Promise<{ success: boolean; error?: string }> {
59 |   try {
60 |     await clipboardy.write('');
61 |     return {
62 |       success: true,
63 |     };
64 |   } catch (error) {
65 |     return {
66 |       success: false,
67 |       error: error instanceof Error ? error.message : String(error),
68 |     };
69 |   }
70 | }
71 | 
```

--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------

```javascript
 1 | import eslint from '@eslint/js';
 2 | import tseslint from 'typescript-eslint';
 3 | 
 4 | // Create a simplified configuration that only lints src TypeScript files
 5 | export default tseslint.config(
 6 |   {
 7 |     ignores: [
 8 |       'build/**',
 9 |       'coverage/**',
10 |       '*.html',
11 |       'mcpcontrol-wrapper.sh',
12 |       'eslint.config.js',
13 |       '.github/**',
14 |       'scripts/**',
15 |       'test/**'
16 |     ]
17 |   },
18 |   {
19 |     files: ['src/**/*.ts'],
20 |     extends: [
21 |       eslint.configs.recommended,
22 |       ...tseslint.configs.recommended,
23 |       ...tseslint.configs.recommendedTypeChecked
24 |     ],
25 |     languageOptions: {
26 |       parserOptions: {
27 |         project: true,
28 |         tsconfigRootDir: import.meta.dirname,
29 |       },
30 |     },
31 |     rules: {
32 |       'no-console': 'off',
33 |       '@typescript-eslint/no-explicit-any': 'warn',
34 |       '@typescript-eslint/explicit-module-boundary-types': 'error',
35 |       '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
36 |     }
37 |   },
38 |   {
39 |     // Test files specific configuration
40 |     files: ['src/**/*.test.ts', 'src/**/*.spec.ts'],
41 |     rules: {
42 |       '@typescript-eslint/no-explicit-any': 'off',
43 |       '@typescript-eslint/no-non-null-assertion': 'off',
44 |       '@typescript-eslint/no-unsafe-assignment': 'off',
45 |       '@typescript-eslint/no-unsafe-member-access': 'off',
46 |       '@typescript-eslint/no-unsafe-call': 'off',
47 |       '@typescript-eslint/no-unsafe-return': 'off',
48 |       '@typescript-eslint/no-unsafe-argument': 'off',
49 |       '@typescript-eslint/unbound-method': 'off',
50 |     },
51 |   }
52 | );
```

--------------------------------------------------------------------------------
/src/interfaces/automation.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {
 2 |   MousePosition,
 3 |   KeyboardInput,
 4 |   KeyCombination,
 5 |   KeyHoldOperation,
 6 |   ScreenshotOptions,
 7 |   ClipboardInput,
 8 | } from '../types/common.js';
 9 | import { WindowsControlResponse } from '../types/responses.js';
10 | 
11 | export interface KeyboardAutomation {
12 |   typeText(input: KeyboardInput): WindowsControlResponse;
13 |   pressKey(key: string): WindowsControlResponse;
14 |   pressKeyCombination(combination: KeyCombination): Promise<WindowsControlResponse>;
15 |   holdKey(operation: KeyHoldOperation): Promise<WindowsControlResponse>;
16 | }
17 | 
18 | export interface MouseAutomation {
19 |   moveMouse(position: MousePosition): WindowsControlResponse;
20 |   clickMouse(button?: 'left' | 'right' | 'middle'): WindowsControlResponse;
21 |   doubleClick(position?: MousePosition): WindowsControlResponse;
22 |   getCursorPosition(): WindowsControlResponse;
23 |   scrollMouse(amount: number): WindowsControlResponse;
24 |   dragMouse(
25 |     from: MousePosition,
26 |     to: MousePosition,
27 |     button?: 'left' | 'right' | 'middle',
28 |   ): WindowsControlResponse;
29 |   clickAt(x: number, y: number, button?: 'left' | 'right' | 'middle'): WindowsControlResponse;
30 | }
31 | 
32 | export interface ScreenAutomation {
33 |   getScreenSize(): WindowsControlResponse;
34 |   getActiveWindow(): WindowsControlResponse;
35 |   focusWindow(title: string): WindowsControlResponse;
36 |   resizeWindow(title: string, width: number, height: number): Promise<WindowsControlResponse>;
37 |   repositionWindow(title: string, x: number, y: number): Promise<WindowsControlResponse>;
38 |   getScreenshot(options?: ScreenshotOptions): Promise<WindowsControlResponse>;
39 | }
40 | 
41 | export interface ClipboardAutomation {
42 |   getClipboardContent(): Promise<WindowsControlResponse>;
43 |   setClipboardContent(input: ClipboardInput): Promise<WindowsControlResponse>;
44 |   hasClipboardText(): Promise<WindowsControlResponse>;
45 |   clearClipboard(): Promise<WindowsControlResponse>;
46 | }
47 | 
```

--------------------------------------------------------------------------------
/src/tools/screen.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WindowsControlResponse } from '../types/responses.js';
 2 | import { createAutomationProvider } from '../providers/factory.js';
 3 | 
 4 | export function getScreenSize(): WindowsControlResponse {
 5 |   try {
 6 |     const provider = createAutomationProvider();
 7 |     return provider.screen.getScreenSize();
 8 |   } catch (error) {
 9 |     return {
10 |       success: false,
11 |       message: `Failed to get screen size: ${error instanceof Error ? error.message : String(error)}`,
12 |     };
13 |   }
14 | }
15 | 
16 | export function getActiveWindow(): WindowsControlResponse {
17 |   try {
18 |     const provider = createAutomationProvider();
19 |     return provider.screen.getActiveWindow();
20 |   } catch (error) {
21 |     return {
22 |       success: false,
23 |       message: `Failed to get active window information: ${error instanceof Error ? error.message : String(error)}`,
24 |     };
25 |   }
26 | }
27 | 
28 | export function focusWindow(title: string): WindowsControlResponse {
29 |   try {
30 |     const provider = createAutomationProvider();
31 |     return provider.screen.focusWindow(title);
32 |   } catch (error) {
33 |     return {
34 |       success: false,
35 |       message: `Failed to focus window: ${error instanceof Error ? error.message : String(error)}`,
36 |     };
37 |   }
38 | }
39 | 
40 | export async function resizeWindow(
41 |   title: string,
42 |   width: number,
43 |   height: number,
44 | ): Promise<WindowsControlResponse> {
45 |   try {
46 |     const provider = createAutomationProvider();
47 |     return await provider.screen.resizeWindow(title, width, height);
48 |   } catch (error) {
49 |     return {
50 |       success: false,
51 |       message: `Failed to resize window: ${error instanceof Error ? error.message : String(error)}`,
52 |     };
53 |   }
54 | }
55 | 
56 | export async function repositionWindow(
57 |   title: string,
58 |   x: number,
59 |   y: number,
60 | ): Promise<WindowsControlResponse> {
61 |   try {
62 |     const provider = createAutomationProvider();
63 |     return await provider.screen.repositionWindow(title, x, y);
64 |   } catch (error) {
65 |     return {
66 |       success: false,
67 |       message: `Failed to reposition window: ${error instanceof Error ? error.message : String(error)}`,
68 |     };
69 |   }
70 | }
71 | 
```

--------------------------------------------------------------------------------
/src/types/common.ts:
--------------------------------------------------------------------------------

```typescript
 1 | export interface MousePosition {
 2 |   x: number;
 3 |   y: number;
 4 | }
 5 | 
 6 | export interface KeyboardInput {
 7 |   text: string;
 8 | }
 9 | 
10 | export interface KeyCombination {
11 |   keys: string[]; // Array of keys to be pressed together, e.g. ["control", "c"]
12 | }
13 | 
14 | export interface KeyHoldOperation {
15 |   key: string; // The key to hold
16 |   duration?: number; // Duration in milliseconds (optional when state is 'up')
17 |   state: 'down' | 'up'; // Whether to press down or release the key
18 | }
19 | 
20 | export interface WindowInfo {
21 |   title: string;
22 |   position: {
23 |     x: number;
24 |     y: number;
25 |   };
26 |   size: {
27 |     width: number;
28 |     height: number;
29 |   };
30 | }
31 | 
32 | export interface ClipboardInput {
33 |   text: string;
34 | }
35 | 
36 | // Type for mouse button mapping
37 | export type ButtonMap = {
38 |   [key: string]: string;
39 |   left: string;
40 |   right: string;
41 |   middle: string;
42 | };
43 | 
44 | // New types for screen search functionality
45 | export interface ImageSearchOptions {
46 |   confidence?: number; // Match confidence threshold (0-1)
47 |   searchRegion?: {
48 |     // Region to search within
49 |     x: number;
50 |     y: number;
51 |     width: number;
52 |     height: number;
53 |   };
54 |   waitTime?: number; // Max time to wait for image in ms
55 | }
56 | 
57 | export interface ImageSearchResult {
58 |   location: {
59 |     x: number;
60 |     y: number;
61 |   };
62 |   confidence: number;
63 |   width: number;
64 |   height: number;
65 | }
66 | 
67 | export interface HighlightOptions {
68 |   duration?: number; // Duration to show highlight in ms
69 |   color?: string; // Color of highlight (hex format)
70 | }
71 | 
72 | // New interface for screenshot options
73 | export interface ScreenshotOptions {
74 |   region?: {
75 |     x: number;
76 |     y: number;
77 |     width: number;
78 |     height: number;
79 |   };
80 |   quality?: number; // JPEG quality (1-100), only used if format is 'jpeg'
81 |   format?: 'png' | 'jpeg'; // Output format
82 |   grayscale?: boolean; // Convert to grayscale
83 |   resize?: {
84 |     // Resize options
85 |     width?: number; // Target width (maintains aspect ratio if only one dimension provided)
86 |     height?: number; // Target height
87 |     fit?: 'contain' | 'cover' | 'fill' | 'inside' | 'outside'; // Resize fit option
88 |   };
89 |   compressionLevel?: number; // PNG compression level (0-9), only used if format is 'png'
90 | }
91 | 
```

--------------------------------------------------------------------------------
/src/providers/clipboard/clipboardy/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import clipboardy from 'clipboardy';
 2 | import { ClipboardInput } from '../../../types/common.js';
 3 | import { WindowsControlResponse } from '../../../types/responses.js';
 4 | import { ClipboardAutomation } from '../../../interfaces/automation.js';
 5 | 
 6 | /**
 7 |  * Clipboardy implementation of the ClipboardAutomation interface
 8 |  *
 9 |  * Uses the clipboardy library for cross-platform clipboard operations
10 |  */
11 | export class ClipboardyProvider implements ClipboardAutomation {
12 |   async getClipboardContent(): Promise<WindowsControlResponse> {
13 |     try {
14 |       const content = await clipboardy.read();
15 |       return {
16 |         success: true,
17 |         message: 'Clipboard content retrieved',
18 |         data: content,
19 |       };
20 |     } catch (error) {
21 |       return {
22 |         success: false,
23 |         message: `Failed to get clipboard content: ${error instanceof Error ? error.message : String(error)}`,
24 |       };
25 |     }
26 |   }
27 | 
28 |   async setClipboardContent(input: ClipboardInput): Promise<WindowsControlResponse> {
29 |     try {
30 |       await clipboardy.write(input.text);
31 |       return {
32 |         success: true,
33 |         message: 'Clipboard content set',
34 |       };
35 |     } catch (error) {
36 |       return {
37 |         success: false,
38 |         message: `Failed to set clipboard content: ${error instanceof Error ? error.message : String(error)}`,
39 |       };
40 |     }
41 |   }
42 | 
43 |   async hasClipboardText(): Promise<WindowsControlResponse> {
44 |     try {
45 |       const content = await clipboardy.read();
46 |       const hasText = content.length > 0;
47 |       return {
48 |         success: true,
49 |         message: `Clipboard ${hasText ? 'has' : 'does not have'} text`,
50 |         data: hasText,
51 |       };
52 |     } catch (error) {
53 |       return {
54 |         success: false,
55 |         message: `Failed to check clipboard: ${error instanceof Error ? error.message : String(error)}`,
56 |       };
57 |     }
58 |   }
59 | 
60 |   async clearClipboard(): Promise<WindowsControlResponse> {
61 |     try {
62 |       await clipboardy.write('');
63 |       return {
64 |         success: true,
65 |         message: 'Clipboard cleared',
66 |       };
67 |     } catch (error) {
68 |       return {
69 |         success: false,
70 |         message: `Failed to clear clipboard: ${error instanceof Error ? error.message : String(error)}`,
71 |       };
72 |     }
73 |   }
74 | }
75 | 
```

--------------------------------------------------------------------------------
/scripts/build.js:
--------------------------------------------------------------------------------

```javascript
 1 | #!/usr/bin/env node
 2 | 
 3 | import { execSync } from 'child_process';
 4 | import fs from 'fs';
 5 | import path from 'path';
 6 | 
 7 | /**
 8 |  * Build script for MCPControl
 9 |  * Handles the complete build process
10 |  */
11 | 
12 | // ANSI color codes for terminal output
13 | const colors = {
14 |   reset: '\x1b[0m',
15 |   green: '\x1b[32m',
16 |   yellow: '\x1b[33m',
17 |   blue: '\x1b[34m',
18 |   red: '\x1b[31m',
19 |   cyan: '\x1b[36m'
20 | };
21 | 
22 | /**
23 |  * Executes a shell command and pipes output to console
24 |  * @param {string} command - Command to execute
25 |  * @param {Object} options - Options for child_process.execSync
26 |  * @returns {Buffer} Command output
27 |  */
28 | function execute(command, options = {}) {
29 |   console.log(`${colors.cyan}> ${command}${colors.reset}`);
30 |   
31 |   const defaultOptions = { 
32 |     stdio: 'inherit',
33 |     ...options
34 |   };
35 |   
36 |   try {
37 |     return execSync(command, defaultOptions);
38 |   } catch (error) {
39 |     console.error(`${colors.red}Command failed: ${command}${colors.reset}`);
40 |     process.exit(1);
41 |   }
42 | }
43 | 
44 | // Main build process
45 | function build() {
46 |   console.log(`\n${colors.green}===== MCPControl Build Process =====${colors.reset}\n`);
47 |   
48 |   // Install dependencies using npm ci for faster and deterministic installs
49 |   console.log(`\n${colors.blue}Installing dependencies...${colors.reset}`);
50 |   
51 |   // Check if package-lock.json exists before running npm ci
52 |   if (fs.existsSync(path.join(process.cwd(), 'package-lock.json'))) {
53 |     try {
54 |       // Use a different execution method for npm ci to allow falling back to npm install
55 |       console.log(`${colors.cyan}> npm ci${colors.reset}`);
56 |       execSync('npm ci', { stdio: 'inherit' });
57 |     } catch (error) {
58 |       console.warn(`\n${colors.yellow}Warning: npm ci failed, falling back to npm install${colors.reset}`);
59 |       execute('npm install');
60 |     }
61 |   } else {
62 |     console.log(`\n${colors.yellow}package-lock.json not found, using npm install instead${colors.reset}`);
63 |     execute('npm install');
64 |   }
65 |   
66 |   // Build MCPControl
67 |   console.log(`\n${colors.blue}Building MCPControl...${colors.reset}`);
68 |   execute('npm run build');
69 |   
70 |   console.log(`\n${colors.green}===== Build Complete =====${colors.reset}`);
71 |   console.log(`${colors.green}MCPControl has been successfully built!${colors.reset}\n`);
72 | }
73 | 
74 | // Run the build process
75 | build();
76 | 
```

--------------------------------------------------------------------------------
/src/providers/keysender/clipboard.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import clipboardy from 'clipboardy';
 2 | import { ClipboardInput } from '../../types/common.js';
 3 | import { WindowsControlResponse } from '../../types/responses.js';
 4 | import { ClipboardAutomation } from '../../interfaces/automation.js';
 5 | 
 6 | /**
 7 |  * Keysender implementation of the ClipboardAutomation interface
 8 |  *
 9 |  * Note: Since keysender doesn't provide direct clipboard functionality,
10 |  * we use the clipboardy library (same as the NutJS implementation)
11 |  */
12 | export class KeysenderClipboardAutomation implements ClipboardAutomation {
13 |   async getClipboardContent(): Promise<WindowsControlResponse> {
14 |     try {
15 |       const content = await clipboardy.read();
16 |       return {
17 |         success: true,
18 |         message: 'Clipboard content retrieved',
19 |         data: content,
20 |       };
21 |     } catch (error) {
22 |       return {
23 |         success: false,
24 |         message: `Failed to get clipboard content: ${error instanceof Error ? error.message : String(error)}`,
25 |       };
26 |     }
27 |   }
28 | 
29 |   async setClipboardContent(input: ClipboardInput): Promise<WindowsControlResponse> {
30 |     try {
31 |       await clipboardy.write(input.text);
32 |       return {
33 |         success: true,
34 |         message: 'Clipboard content set',
35 |       };
36 |     } catch (error) {
37 |       return {
38 |         success: false,
39 |         message: `Failed to set clipboard content: ${error instanceof Error ? error.message : String(error)}`,
40 |       };
41 |     }
42 |   }
43 | 
44 |   async hasClipboardText(): Promise<WindowsControlResponse> {
45 |     try {
46 |       const content = await clipboardy.read();
47 |       const hasText = content.length > 0;
48 |       return {
49 |         success: true,
50 |         message: `Clipboard ${hasText ? 'has' : 'does not have'} text`,
51 |         data: hasText,
52 |       };
53 |     } catch (error) {
54 |       return {
55 |         success: false,
56 |         message: `Failed to check clipboard: ${error instanceof Error ? error.message : String(error)}`,
57 |       };
58 |     }
59 |   }
60 | 
61 |   async clearClipboard(): Promise<WindowsControlResponse> {
62 |     try {
63 |       await clipboardy.write('');
64 |       return {
65 |         success: true,
66 |         message: 'Clipboard cleared',
67 |       };
68 |     } catch (error) {
69 |       return {
70 |         success: false,
71 |         message: `Failed to clear clipboard: ${error instanceof Error ? error.message : String(error)}`,
72 |       };
73 |     }
74 |   }
75 | }
76 | 
```

--------------------------------------------------------------------------------
/src/tools/keyboard.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { KeyboardInput, KeyCombination, KeyHoldOperation } from '../types/common.js';
 2 | import { WindowsControlResponse } from '../types/responses.js';
 3 | import { createAutomationProvider } from '../providers/factory.js';
 4 | import {
 5 |   MAX_TEXT_LENGTH,
 6 |   KeySchema,
 7 |   KeyCombinationSchema,
 8 |   KeyHoldOperationSchema,
 9 | } from './validation.zod.js';
10 | 
11 | // Get the automation provider
12 | const provider = createAutomationProvider();
13 | 
14 | export function typeText(input: KeyboardInput): WindowsControlResponse {
15 |   try {
16 |     // Validate text length
17 |     if (!input.text) {
18 |       throw new Error('Text is required');
19 |     }
20 | 
21 |     if (input.text.length > MAX_TEXT_LENGTH) {
22 |       throw new Error(`Text too long: ${input.text.length} characters (max ${MAX_TEXT_LENGTH})`);
23 |     }
24 | 
25 |     return provider.keyboard.typeText(input);
26 |   } catch (error) {
27 |     return {
28 |       success: false,
29 |       message: `Failed to type text: ${error instanceof Error ? error.message : String(error)}`,
30 |     };
31 |   }
32 | }
33 | 
34 | export function pressKey(key: string): WindowsControlResponse {
35 |   try {
36 |     // Validate key using Zod schema
37 |     KeySchema.parse(key);
38 | 
39 |     return provider.keyboard.pressKey(key);
40 |   } catch (error) {
41 |     return {
42 |       success: false,
43 |       message: `Failed to press key: ${error instanceof Error ? error.message : String(error)}`,
44 |     };
45 |   }
46 | }
47 | 
48 | export async function pressKeyCombination(
49 |   combination: KeyCombination,
50 | ): Promise<WindowsControlResponse> {
51 |   try {
52 |     // Validate the key combination using Zod schema
53 |     KeyCombinationSchema.parse(combination);
54 | 
55 |     return await provider.keyboard.pressKeyCombination(combination);
56 |   } catch (error) {
57 |     return {
58 |       success: false,
59 |       message: `Failed to press key combination: ${error instanceof Error ? error.message : String(error)}`,
60 |     };
61 |   }
62 | }
63 | 
64 | export async function holdKey(operation: KeyHoldOperation): Promise<WindowsControlResponse> {
65 |   try {
66 |     // Validate key hold operation using Zod schema
67 |     KeyHoldOperationSchema.parse(operation);
68 | 
69 |     return await provider.keyboard.holdKey(operation);
70 |   } catch (error) {
71 |     return {
72 |       success: false,
73 |       message: `Failed to ${operation.state} key ${operation.key}: ${
74 |         error instanceof Error ? error.message : String(error)
75 |       }`,
76 |     };
77 |   }
78 | }
79 | 
```

--------------------------------------------------------------------------------
/src/types/keysender.d.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Type definitions for keysender module
 3 |  */
 4 | 
 5 | declare module 'keysender' {
 6 |   interface ScreenSize {
 7 |     width: number;
 8 |     height: number;
 9 |   }
10 | 
11 |   interface WindowInfo {
12 |     title: string;
13 |     className: string;
14 |     handle: number;
15 |   }
16 | 
17 |   interface ViewInfo {
18 |     x: number;
19 |     y: number;
20 |     width: number;
21 |     height: number;
22 |   }
23 | 
24 |   interface CaptureResult {
25 |     data: Buffer;
26 |     width: number;
27 |     height: number;
28 |   }
29 | 
30 |   interface MousePosition {
31 |     x: number;
32 |     y: number;
33 |   }
34 | 
35 |   // eslint-disable-next-line @typescript-eslint/no-unused-vars
36 |   class Hardware {
37 |     constructor(windowHandle?: number);
38 | 
39 |     keyboard: {
40 |       printText(text: string): Promise<void>;
41 |       sendKey(key: string): Promise<void>;
42 |       toggleKey(key: string | string[], down: boolean, delay?: Delay): Promise<void>;
43 |     };
44 | 
45 |     mouse: {
46 |       moveTo(x: number, y: number): Promise<void>;
47 |       click(button?: string): Promise<void>;
48 |       toggle(button: string, down: boolean): Promise<void>;
49 |       getPos(): MousePosition;
50 |       scrollWheel(amount: number): Promise<void>;
51 |     };
52 | 
53 |     workwindow: {
54 |       get(): WindowInfo;
55 |       set(handle: number): boolean;
56 |       getView(): ViewInfo;
57 |       setView(view: Partial<ViewInfo>): void;
58 |       setForeground(): void;
59 |       isForeground(): boolean;
60 |       isOpen(): boolean;
61 |       capture(
62 |         region?: { x: number; y: number; width: number; height: number },
63 |         format?: string,
64 |       ): CaptureResult;
65 |       capture(format?: string): CaptureResult;
66 |     };
67 |   }
68 | 
69 |   // eslint-disable-next-line @typescript-eslint/no-unused-vars
70 |   function getScreenSize(): ScreenSize;
71 |   // eslint-disable-next-line @typescript-eslint/no-unused-vars
72 |   function getAllWindows(): WindowInfo[];
73 |   // eslint-disable-next-line @typescript-eslint/no-unused-vars
74 |   function getWindowChildren(handle: number): WindowInfo[];
75 | 
76 |   // eslint-disable-next-line @typescript-eslint/no-unused-vars
77 |   const KeyboardButton: { [key: string]: string };
78 |   // eslint-disable-next-line @typescript-eslint/no-unused-vars
79 |   const MouseButton: { [key: string]: string };
80 | 
81 |   const keysender: {
82 |     Hardware: typeof Hardware;
83 |     KeyboardButton: typeof KeyboardButton;
84 |     MouseButton: typeof MouseButton;
85 |     getScreenSize: typeof getScreenSize;
86 |     getAllWindows: typeof getAllWindows;
87 |     getWindowChildren: typeof getWindowChildren;
88 |   };
89 | 
90 |   export default keysender;
91 | }
92 | 
```

--------------------------------------------------------------------------------
/docs/providers.md:
--------------------------------------------------------------------------------

```markdown
 1 | # MCPControl Automation Providers
 2 | 
 3 | MCPControl supports multiple automation providers to give users flexibility in how they control their systems. Each provider has its own strengths and may work better in different environments.
 4 | 
 5 | ## Available Providers
 6 | 
 7 | ### Keysender Provider (Default)
 8 | 
 9 | The Keysender provider uses the [keysender](https://github.com/garrettlynch/keysender) library for system automation. It provides comprehensive support for keyboard, mouse, screen, and clipboard operations.
10 | 
11 | ## Selecting a Provider
12 | 
13 | You can select which provider to use by setting the `AUTOMATION_PROVIDER` environment variable:
14 | 
15 | ```bash
16 | # Use the Keysender provider (default)
17 | AUTOMATION_PROVIDER=keysender node build/index.js
18 | ```
19 | 
20 | ### Screen Automation Considerations
21 | 
22 | The Keysender provider has the following considerations for screen automation:
23 | 
24 | - **Window Detection Challenges**: Getting accurate window information can be challenging, especially with:
25 |   - Window handles that may not always be valid
26 |   - Window titles that may be empty or not match expected values
27 |   - Position and size information that may be unavailable or return extreme negative values for minimized windows
28 | - **Window Repositioning and Resizing**: Operations work but may not always report accurate results due to limitations in the underlying API
29 | - **Window Focusing**: May not work reliably for all window types or applications
30 | - **Screenshot Functionality**: May not work consistently in all environments
31 | 
32 | We've implemented significant fallbacks and robust error handling for window operations, including:
33 | 
34 | - Advanced window selection strategy that prioritizes common applications for better reliability
35 | - Detailed logging to help diagnose window handling issues
36 | - Fallback mechanisms when window operations don't produce the expected results
37 | - Safe property access with type checking to handle edge cases
38 | 
39 | ### Recent Improvements
40 | 
41 | Recent updates to the provider include:
42 | 
43 | - Added a sophisticated window finding algorithm that tries multiple strategies to locate usable windows
44 | - Enhanced window resizing and repositioning with better error handling and result verification
45 | - Improved window information retrieval with multiple fallback layers for missing data
46 | - Better window focusing with proper foreground window management and status reporting
47 | - More robust error handling throughout window operations with detailed logging
48 | - Added support for child window detection and management
49 | 
```

--------------------------------------------------------------------------------
/src/providers/registry.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {
 2 |   KeyboardAutomation,
 3 |   MouseAutomation,
 4 |   ScreenAutomation,
 5 |   ClipboardAutomation,
 6 | } from '../interfaces/automation.js';
 7 | 
 8 | export interface ProviderRegistry {
 9 |   registerKeyboard(name: string, provider: KeyboardAutomation): void;
10 |   registerMouse(name: string, provider: MouseAutomation): void;
11 |   registerScreen(name: string, provider: ScreenAutomation): void;
12 |   registerClipboard(name: string, provider: ClipboardAutomation): void;
13 | 
14 |   getKeyboard(name: string): KeyboardAutomation | undefined;
15 |   getMouse(name: string): MouseAutomation | undefined;
16 |   getScreen(name: string): ScreenAutomation | undefined;
17 |   getClipboard(name: string): ClipboardAutomation | undefined;
18 | }
19 | 
20 | /**
21 |  * Central registry for automation providers
22 |  * Allows registration and retrieval of individual automation components
23 |  */
24 | export class DefaultProviderRegistry implements ProviderRegistry {
25 |   private keyboards = new Map<string, KeyboardAutomation>();
26 |   private mice = new Map<string, MouseAutomation>();
27 |   private screens = new Map<string, ScreenAutomation>();
28 |   private clipboards = new Map<string, ClipboardAutomation>();
29 | 
30 |   registerKeyboard(name: string, provider: KeyboardAutomation): void {
31 |     this.keyboards.set(name, provider);
32 |   }
33 | 
34 |   registerMouse(name: string, provider: MouseAutomation): void {
35 |     this.mice.set(name, provider);
36 |   }
37 | 
38 |   registerScreen(name: string, provider: ScreenAutomation): void {
39 |     this.screens.set(name, provider);
40 |   }
41 | 
42 |   registerClipboard(name: string, provider: ClipboardAutomation): void {
43 |     this.clipboards.set(name, provider);
44 |   }
45 | 
46 |   getKeyboard(name: string): KeyboardAutomation | undefined {
47 |     return this.keyboards.get(name);
48 |   }
49 | 
50 |   getMouse(name: string): MouseAutomation | undefined {
51 |     return this.mice.get(name);
52 |   }
53 | 
54 |   getScreen(name: string): ScreenAutomation | undefined {
55 |     return this.screens.get(name);
56 |   }
57 | 
58 |   getClipboard(name: string): ClipboardAutomation | undefined {
59 |     return this.clipboards.get(name);
60 |   }
61 | 
62 |   /**
63 |    * Get a list of all registered provider names for each component type
64 |    */
65 |   getAvailableProviders(): {
66 |     keyboards: string[];
67 |     mice: string[];
68 |     screens: string[];
69 |     clipboards: string[];
70 |   } {
71 |     return {
72 |       keyboards: Array.from(this.keyboards.keys()),
73 |       mice: Array.from(this.mice.keys()),
74 |       screens: Array.from(this.screens.keys()),
75 |       clipboards: Array.from(this.clipboards.keys()),
76 |     };
77 |   }
78 | }
79 | 
80 | // Singleton instance
81 | export const registry = new DefaultProviderRegistry();
82 | 
```

--------------------------------------------------------------------------------
/scripts/test-screenshot.cjs:
--------------------------------------------------------------------------------

```
 1 | #!/usr/bin/env node
 2 | // Test script for the screenshot utility
 3 | // This script tests the optimized screenshot functionality to ensure:
 4 | // 1. No "Maximum call stack size exceeded" errors
 5 | // 2. Reasonable file sizes (not 20MB)
 6 | 
 7 | // Import the built version of the screenshot utility
 8 | const { getScreenshot } = require('../build/tools/screenshot.js');
 9 | 
10 | async function testScreenshot() {
11 |   console.log('Testing screenshot utility with various settings...');
12 |   
13 |   // Test 1: Default settings (should use 1280px width, JPEG format)
14 |   console.log('\nTest 1: Default settings (1280px width, JPEG)');
15 |   try {
16 |     const result1 = await getScreenshot();
17 |     if (result1.success) {
18 |       console.log('✅ Default screenshot successful');
19 |     } else {
20 |       console.error('❌ Default screenshot failed:', result1.message);
21 |     }
22 |   } catch (error) {
23 |     console.error('❌ Default screenshot threw exception:', error);
24 |   }
25 |   
26 |   // Test 2: Small size (50x50px)
27 |   console.log('\nTest 2: Small size (50x50px)');
28 |   try {
29 |     const result2 = await getScreenshot({
30 |       resize: {
31 |         width: 50,
32 |         height: 50,
33 |         fit: 'fill'
34 |       }
35 |     });
36 |     if (result2.success) {
37 |       console.log('✅ Small screenshot successful');
38 |     } else {
39 |       console.error('❌ Small screenshot failed:', result2.message);
40 |     }
41 |   } catch (error) {
42 |     console.error('❌ Small screenshot threw exception:', error);
43 |   }
44 |   
45 |   // Test 3: PNG format with high compression
46 |   console.log('\nTest 3: PNG format with high compression');
47 |   try {
48 |     const result3 = await getScreenshot({
49 |       format: 'png',
50 |       compressionLevel: 9,
51 |       resize: {
52 |         width: 800
53 |       }
54 |     });
55 |     if (result3.success) {
56 |       console.log('✅ PNG screenshot successful');
57 |     } else {
58 |       console.error('❌ PNG screenshot failed:', result3.message);
59 |     }
60 |   } catch (error) {
61 |     console.error('❌ PNG screenshot threw exception:', error);
62 |   }
63 |   
64 |   // Test 4: Grayscale JPEG with low quality
65 |   console.log('\nTest 4: Grayscale JPEG with low quality');
66 |   try {
67 |     const result4 = await getScreenshot({
68 |       format: 'jpeg',
69 |       quality: 50,
70 |       grayscale: true
71 |     });
72 |     if (result4.success) {
73 |       console.log('✅ Grayscale screenshot successful');
74 |     } else {
75 |       console.error('❌ Grayscale screenshot failed:', result4.message);
76 |     }
77 |   } catch (error) {
78 |     console.error('❌ Grayscale screenshot threw exception:', error);
79 |   }
80 |   
81 |   console.log('\nScreenshot testing complete');
82 | }
83 | 
84 | // Run the tests
85 | testScreenshot().catch(console.error);
86 | 
```

--------------------------------------------------------------------------------
/scripts/test-screenshot.mjs:
--------------------------------------------------------------------------------

```
 1 | #!/usr/bin/env node
 2 | // Test script for the screenshot utility
 3 | // This script tests the optimized screenshot functionality to ensure:
 4 | // 1. No "Maximum call stack size exceeded" errors
 5 | // 2. Reasonable file sizes (not 20MB)
 6 | 
 7 | // Use dynamic import for ES modules
 8 | async function runTests() {
 9 |   // Import the built version of the screenshot utility
10 |   const { getScreenshot } = await import('../build/tools/screenshot.js');
11 |   
12 |   console.log('Testing screenshot utility with various settings...');
13 |   
14 |   // Test 1: Default settings (should use 1280px width, JPEG format)
15 |   console.log('\nTest 1: Default settings (1280px width, JPEG)');
16 |   try {
17 |     const result1 = await getScreenshot();
18 |     if (result1.success) {
19 |       console.log('✅ Default screenshot successful');
20 |     } else {
21 |       console.error('❌ Default screenshot failed:', result1.message);
22 |     }
23 |   } catch (error) {
24 |     console.error('❌ Default screenshot threw exception:', error);
25 |   }
26 |   
27 |   // Test 2: Small size (50x50px)
28 |   console.log('\nTest 2: Small size (50x50px)');
29 |   try {
30 |     const result2 = await getScreenshot({
31 |       resize: {
32 |         width: 50,
33 |         height: 50,
34 |         fit: 'fill'
35 |       }
36 |     });
37 |     if (result2.success) {
38 |       console.log('✅ Small screenshot successful');
39 |     } else {
40 |       console.error('❌ Small screenshot failed:', result2.message);
41 |     }
42 |   } catch (error) {
43 |     console.error('❌ Small screenshot threw exception:', error);
44 |   }
45 |   
46 |   // Test 3: PNG format with high compression
47 |   console.log('\nTest 3: PNG format with high compression');
48 |   try {
49 |     const result3 = await getScreenshot({
50 |       format: 'png',
51 |       compressionLevel: 9,
52 |       resize: {
53 |         width: 800
54 |       }
55 |     });
56 |     if (result3.success) {
57 |       console.log('✅ PNG screenshot successful');
58 |     } else {
59 |       console.error('❌ PNG screenshot failed:', result3.message);
60 |     }
61 |   } catch (error) {
62 |     console.error('❌ PNG screenshot threw exception:', error);
63 |   }
64 |   
65 |   // Test 4: Grayscale JPEG with low quality
66 |   console.log('\nTest 4: Grayscale JPEG with low quality');
67 |   try {
68 |     const result4 = await getScreenshot({
69 |       format: 'jpeg',
70 |       quality: 50,
71 |       grayscale: true
72 |     });
73 |     if (result4.success) {
74 |       console.log('✅ Grayscale screenshot successful');
75 |     } else {
76 |       console.error('❌ Grayscale screenshot failed:', result4.message);
77 |     }
78 |   } catch (error) {
79 |     console.error('❌ Grayscale screenshot threw exception:', error);
80 |   }
81 |   
82 |   console.log('\nScreenshot testing complete');
83 | }
84 | 
85 | // Run the tests
86 | runTests().catch(console.error);
87 | 
```

--------------------------------------------------------------------------------
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Publish to NPM
 2 | 
 3 | on:
 4 |   push:
 5 |     tags:
 6 |       - 'v*'
 7 | 
 8 | jobs:
 9 |   publish:
10 |     runs-on: windows-latest
11 |     
12 |     steps:
13 |       - name: Checkout code
14 |         uses: actions/checkout@v4
15 |       
16 |       - name: Setup Node.js
17 |         uses: actions/setup-node@v4
18 |         with:
19 |           node-version: '18'
20 |           registry-url: 'https://registry.npmjs.org'
21 |       
22 |       - name: Cache npm dependencies
23 |         uses: actions/cache@v3
24 |         with:
25 |           path: ~/.npm
26 |           key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
27 |           restore-keys: |
28 |             ${{ runner.os }}-node-
29 |       
30 |       # Dependencies required for native modules
31 |       - name: Install global dependencies
32 |         run: |
33 |           npm install -g node-gyp
34 |           npm install -g cmake-js
35 |       
36 |       - name: Install dependencies
37 |         run: npm ci
38 |       
39 |       - name: Lint
40 |         run: npm run lint
41 |       
42 |       - name: Run tests
43 |         run: npm run test
44 |       
45 |       - name: Build
46 |         run: npm run build:all
47 |       
48 |       - name: Extract version
49 |         id: extract_version
50 |         run: echo "VERSION=$($env:GITHUB_REF -replace 'refs/tags/v', '')" >> $env:GITHUB_OUTPUT
51 |         shell: pwsh
52 |       
53 |       - name: Update package version if needed
54 |         run: |
55 |           $current_version = $(node -p "require('./package.json').version")
56 |           $tag_version = "${{ steps.extract_version.outputs.VERSION }}"
57 |           
58 |           if ($current_version -ne $tag_version) {
59 |             npm version $tag_version --no-git-tag-version
60 |             Write-Host "Updated version from $current_version to $tag_version"
61 |           } else {
62 |             Write-Host "Version already set to $current_version, skipping update"
63 |           }
64 |         shell: pwsh
65 |       
66 |       - name: Verify package contents
67 |         run: |
68 |           # Just check what will be included in the package without actually extracting
69 |           $package = npm pack --dry-run
70 |           
71 |           # List the build directory to verify it exists and has content
72 |           Write-Host "`nVerifying build directory contents:"
73 |           if (Test-Path -Path "./build") {
74 |             Get-ChildItem -Path "./build" -Recurse -Depth 1 | Select-Object FullName
75 |           } else {
76 |             Write-Error "Build directory not found!"
77 |             exit 1
78 |           }
79 |       
80 |       - name: Publish to NPM
81 |         run: npm publish
82 |         env:
83 |           NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
84 |       
85 |       - name: Verify publish
86 |         run: npm view $(node -p "require('./package.json').name") version
87 |         if: success()
88 | 
```

--------------------------------------------------------------------------------
/scripts/test-window.cjs:
--------------------------------------------------------------------------------

```
 1 | // Direct test script for window handling
 2 | // Use CommonJS require for keysender
 3 | const keysender = require('keysender');
 4 | const { Hardware } = keysender;
 5 | const getAllWindows = keysender.getAllWindows;
 6 | 
 7 | console.log("Testing keysender window handling directly");
 8 | 
 9 | // Get all windows
10 | const allWindows = getAllWindows();
11 | console.log("\nAll windows:");
12 | allWindows.forEach(window => {
13 |   console.log(`- "${window.title}" (handle: ${window.handle}, class: ${window.className})`);
14 | });
15 | 
16 | // Try to find Notepad
17 | console.log("\nLooking for Notepad...");
18 | const notepad = allWindows.find(w => w.title && w.title.includes('Notepad'));
19 | 
20 | if (notepad) {
21 |   console.log(`Found Notepad: "${notepad.title}" (handle: ${notepad.handle})`);
22 |   
23 |   // Create hardware instance for Notepad
24 |   try {
25 |     const hw = new Hardware(notepad.handle);
26 |     console.log("Created Hardware instance for Notepad");
27 |     
28 |     // Try to get window view
29 |     try {
30 |       const view = hw.workwindow.getView();
31 |       console.log("Notepad view:", view);
32 |     } catch (e) {
33 |       console.error("Error getting Notepad view:", e.message);
34 |     }
35 |     
36 |     // Try to set as foreground
37 |     try {
38 |       hw.workwindow.setForeground();
39 |       console.log("Set Notepad as foreground window");
40 |     } catch (e) {
41 |       console.error("Error setting Notepad as foreground:", e.message);
42 |     }
43 |     
44 |     // Try to resize
45 |     try {
46 |       hw.workwindow.setView({
47 |         x: 200,
48 |         y: 200,
49 |         width: 800,
50 |         height: 600
51 |       });
52 |       console.log("Resized Notepad to 800x600 at position (200, 200)");
53 |       
54 |       // Get updated view
55 |       const updatedView = hw.workwindow.getView();
56 |       console.log("Updated Notepad view:", updatedView);
57 |     } catch (e) {
58 |       console.error("Error resizing Notepad:", e.message);
59 |     }
60 |   } catch (e) {
61 |     console.error("Error creating Hardware instance for Notepad:", e.message);
62 |   }
63 | } else {
64 |   console.log("Notepad not found. Please make sure Notepad is running.");
65 | }
66 | 
67 | // Try with default Hardware instance
68 | console.log("\nTesting default Hardware instance:");
69 | try {
70 |   const defaultHw = new Hardware();
71 |   console.log("Created default Hardware instance");
72 |   
73 |   // Try to get current window
74 |   try {
75 |     const currentWindow = defaultHw.workwindow.get();
76 |     console.log("Current window:", currentWindow);
77 |   } catch (e) {
78 |     console.error("Error getting current window:", e.message);
79 |   }
80 |   
81 |   // Try to get view
82 |   try {
83 |     const view = defaultHw.workwindow.getView();
84 |     console.log("Current view:", view);
85 |   } catch (e) {
86 |     console.error("Error getting current view:", e.message);
87 |   }
88 | } catch (e) {
89 |   console.error("Error creating default Hardware instance:", e.message);
90 | }
91 | 
```

--------------------------------------------------------------------------------
/scripts/test-window.js:
--------------------------------------------------------------------------------

```javascript
 1 | // Direct test script for window handling
 2 | // Use CommonJS require for keysender
 3 | const keysender = require('keysender');
 4 | const { Hardware } = keysender;
 5 | const getAllWindows = keysender.getAllWindows;
 6 | 
 7 | console.log("Testing keysender window handling directly");
 8 | 
 9 | // Get all windows
10 | const allWindows = getAllWindows();
11 | console.log("\nAll windows:");
12 | allWindows.forEach(window => {
13 |   console.log(`- "${window.title}" (handle: ${window.handle}, class: ${window.className})`);
14 | });
15 | 
16 | // Try to find Notepad
17 | console.log("\nLooking for Notepad...");
18 | const notepad = allWindows.find(w => w.title && w.title.includes('Notepad'));
19 | 
20 | if (notepad) {
21 |   console.log(`Found Notepad: "${notepad.title}" (handle: ${notepad.handle})`);
22 |   
23 |   // Create hardware instance for Notepad
24 |   try {
25 |     const hw = new Hardware(notepad.handle);
26 |     console.log("Created Hardware instance for Notepad");
27 |     
28 |     // Try to get window view
29 |     try {
30 |       const view = hw.workwindow.getView();
31 |       console.log("Notepad view:", view);
32 |     } catch (e) {
33 |       console.error("Error getting Notepad view:", e.message);
34 |     }
35 |     
36 |     // Try to set as foreground
37 |     try {
38 |       hw.workwindow.setForeground();
39 |       console.log("Set Notepad as foreground window");
40 |     } catch (e) {
41 |       console.error("Error setting Notepad as foreground:", e.message);
42 |     }
43 |     
44 |     // Try to resize
45 |     try {
46 |       hw.workwindow.setView({
47 |         x: 200,
48 |         y: 200,
49 |         width: 800,
50 |         height: 600
51 |       });
52 |       console.log("Resized Notepad to 800x600 at position (200, 200)");
53 |       
54 |       // Get updated view
55 |       const updatedView = hw.workwindow.getView();
56 |       console.log("Updated Notepad view:", updatedView);
57 |     } catch (e) {
58 |       console.error("Error resizing Notepad:", e.message);
59 |     }
60 |   } catch (e) {
61 |     console.error("Error creating Hardware instance for Notepad:", e.message);
62 |   }
63 | } else {
64 |   console.log("Notepad not found. Please make sure Notepad is running.");
65 | }
66 | 
67 | // Try with default Hardware instance
68 | console.log("\nTesting default Hardware instance:");
69 | try {
70 |   const defaultHw = new Hardware();
71 |   console.log("Created default Hardware instance");
72 |   
73 |   // Try to get current window
74 |   try {
75 |     const currentWindow = defaultHw.workwindow.get();
76 |     console.log("Current window:", currentWindow);
77 |   } catch (e) {
78 |     console.error("Error getting current window:", e.message);
79 |   }
80 |   
81 |   // Try to get view
82 |   try {
83 |     const view = defaultHw.workwindow.getView();
84 |     console.log("Current view:", view);
85 |   } catch (e) {
86 |     console.error("Error getting current view:", e.message);
87 |   }
88 | } catch (e) {
89 |   console.error("Error creating default Hardware instance:", e.message);
90 | }
91 | 
```

--------------------------------------------------------------------------------
/scripts/test-provider.js:
--------------------------------------------------------------------------------

```javascript
 1 | // Simple script to test provider selection
 2 | import { loadConfig } from '../build/config.js';
 3 | import { createAutomationProvider } from '../build/providers/factory.js';
 4 | 
 5 | // Override the provider from command line argument if provided
 6 | if (process.argv.length > 2) {
 7 |   process.env.AUTOMATION_PROVIDER = process.argv[2];
 8 | }
 9 | 
10 | // Load configuration
11 | const config = loadConfig();
12 | console.log(`Using provider: ${config.provider}`);
13 | 
14 | // Create provider
15 | const provider = createAutomationProvider(config.provider);
16 | console.log(`Provider created: ${provider.constructor.name}`);
17 | 
18 | // Print provider details
19 | console.log('\nProvider components:');
20 | console.log(`- Keyboard: ${provider.keyboard.constructor.name}`);
21 | console.log(`- Mouse: ${provider.mouse.constructor.name}`);
22 | console.log(`- Screen: ${provider.screen.constructor.name}`);
23 | console.log(`- Clipboard: ${provider.clipboard.constructor.name}`);
24 | 
25 | // Test window operations if requested
26 | const testWindowOps = process.argv.includes('--test-window');
27 | if (testWindowOps) {
28 |   console.log('\nTesting window operations:');
29 |   
30 |   // Get screen size
31 |   const screenSizeResult = provider.screen.getScreenSize();
32 |   console.log(`\nScreen size: ${JSON.stringify(screenSizeResult.data)}`);
33 |   
34 |   // Get active window
35 |   const activeWindowResult = provider.screen.getActiveWindow();
36 |   console.log(`\nActive window: ${JSON.stringify(activeWindowResult.data)}`);
37 |   
38 |   // Test window focus
39 |   if (activeWindowResult.success && activeWindowResult.data?.title) {
40 |     const windowTitle = activeWindowResult.data.title;
41 |     console.log(`\nFocusing window: "${windowTitle}"`);
42 |     const focusResult = provider.screen.focusWindow(windowTitle);
43 |     console.log(`Focus result: ${focusResult.success ? 'Success' : 'Failed'} - ${focusResult.message}`);
44 |     
45 |     // Test window resize
46 |     console.log(`\nResizing window: "${windowTitle}" to 800x600`);
47 |     const resizeResult = provider.screen.resizeWindow(windowTitle, 800, 600);
48 |     console.log(`Resize result: ${resizeResult.success ? 'Success' : 'Failed'} - ${resizeResult.message}`);
49 |     
50 |     // Wait a bit to see the resize
51 |     console.log('Waiting 2 seconds...');
52 |     setTimeout(() => {
53 |       // Test window reposition
54 |       console.log(`\nRepositioning window: "${windowTitle}" to (100, 100)`);
55 |       const repositionResult = provider.screen.repositionWindow(windowTitle, 100, 100);
56 |       console.log(`Reposition result: ${repositionResult.success ? 'Success' : 'Failed'} - ${repositionResult.message}`);
57 |       
58 |       // Get active window again to verify changes
59 |       setTimeout(() => {
60 |         const updatedWindowResult = provider.screen.getActiveWindow();
61 |         console.log(`\nUpdated window info: ${JSON.stringify(updatedWindowResult.data)}`);
62 |       }, 1000);
63 |     }, 2000);
64 |   }
65 | }
66 | 
```

--------------------------------------------------------------------------------
/src/server.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
  2 | 
  3 | // We'll define the mocks before importing the module being tested
  4 | const mockMcpServer = {
  5 |   connect: vi.fn(),
  6 | };
  7 | 
  8 | const mockApp = {
  9 |   get: vi.fn(),
 10 |   use: vi.fn(),
 11 |   post: vi.fn(),
 12 | };
 13 | 
 14 | const mockHttpServer = {
 15 |   listen: vi.fn((port, host, callback) => {
 16 |     if (callback) callback();
 17 |     return mockHttpServer;
 18 |   }),
 19 |   close: vi.fn(),
 20 |   on: vi.fn(),
 21 | };
 22 | 
 23 | const mockSSETransport = {
 24 |   sessionId: 'test-session-id',
 25 |   onclose: null,
 26 |   handlePostMessage: vi.fn(),
 27 | };
 28 | 
 29 | // Mock the dependencies before importing the module to test
 30 | vi.mock('express', () => {
 31 |   const jsonMiddlewareMock = vi.fn();
 32 |   return {
 33 |     default: vi.fn(() => mockApp),
 34 |     json: vi.fn().mockReturnValue(jsonMiddlewareMock),
 35 |   };
 36 | });
 37 | 
 38 | vi.mock('http', () => {
 39 |   return {
 40 |     createServer: vi.fn(() => mockHttpServer),
 41 |   };
 42 | });
 43 | 
 44 | vi.mock('@modelcontextprotocol/sdk/server/sse.js', () => {
 45 |   return {
 46 |     SSEServerTransport: vi.fn(() => mockSSETransport),
 47 |   };
 48 | });
 49 | 
 50 | vi.mock('os', () => {
 51 |   return {
 52 |     networkInterfaces: vi.fn(() => ({
 53 |       eth0: [
 54 |         {
 55 |           family: 'IPv4',
 56 |           internal: false,
 57 |           address: '192.168.1.100',
 58 |         },
 59 |       ],
 60 |     })),
 61 |   };
 62 | });
 63 | 
 64 | // Now import the module being tested
 65 | import { createHttpServer } from './server.js';
 66 | 
 67 | describe('HTTP Server', () => {
 68 |   beforeEach(() => {
 69 |     vi.clearAllMocks();
 70 |     process.env.MAX_SSE_CLIENTS = '100';
 71 | 
 72 |     // Mock console.log to prevent test output
 73 |     vi.spyOn(console, 'log').mockImplementation(() => {});
 74 |     vi.spyOn(console, 'error').mockImplementation(() => {});
 75 |   });
 76 | 
 77 |   afterEach(() => {
 78 |     vi.restoreAllMocks();
 79 |     vi.resetAllMocks();
 80 |   });
 81 | 
 82 |   it('should create an HTTP server with SSE transport', () => {
 83 |     const result = createHttpServer(mockMcpServer as any);
 84 | 
 85 |     // Should have created a server
 86 |     expect(mockHttpServer).toBeDefined();
 87 |     expect(result.app).toBe(mockApp);
 88 |     expect(result.httpServer).toBe(mockHttpServer);
 89 |   });
 90 | 
 91 |   it('should register client limit middleware', () => {
 92 |     createHttpServer(mockMcpServer as any);
 93 | 
 94 |     // Should have registered middleware for client limits
 95 |     expect(mockApp.use).toHaveBeenCalledWith('/mcp', expect.any(Function));
 96 |   });
 97 | 
 98 |   it('should register metrics endpoint', () => {
 99 |     createHttpServer(mockMcpServer as any);
100 | 
101 |     // Should have registered the metrics endpoint
102 |     expect(mockApp.get).toHaveBeenCalledWith('/metrics', expect.any(Function));
103 |   });
104 | 
105 |   it('should start listening on the specified port', () => {
106 |     const port = 5555;
107 |     createHttpServer(mockMcpServer as any, port);
108 | 
109 |     // Should have started listening on the specified port
110 |     expect(mockHttpServer.listen).toHaveBeenCalledWith(port, '0.0.0.0', expect.any(Function));
111 |   });
112 | });
113 | 
```

--------------------------------------------------------------------------------
/src/providers/clipboard/powershell/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { exec } from 'child_process';
 2 | import { promisify } from 'util';
 3 | import { ClipboardInput } from '../../../types/common.js';
 4 | import { WindowsControlResponse } from '../../../types/responses.js';
 5 | import { ClipboardAutomation } from '../../../interfaces/automation.js';
 6 | 
 7 | const execAsync = promisify(exec);
 8 | 
 9 | /**
10 |  * PowerShell implementation of the ClipboardAutomation interface
11 |  *
12 |  * Uses PowerShell commands for clipboard operations on Windows
13 |  * NOTE: This provider is Windows-only
14 |  */
15 | export class PowerShellClipboardProvider implements ClipboardAutomation {
16 |   private async executePowerShell(command: string): Promise<string> {
17 |     try {
18 |       const { stdout, stderr } = await execAsync(`powershell.exe -Command "${command}"`);
19 |       if (stderr) {
20 |         throw new Error(stderr);
21 |       }
22 |       return stdout.trim();
23 |     } catch (error) {
24 |       throw new Error(
25 |         `PowerShell execution failed: ${error instanceof Error ? error.message : String(error)}`,
26 |       );
27 |     }
28 |   }
29 | 
30 |   async getClipboardContent(): Promise<WindowsControlResponse> {
31 |     try {
32 |       const content = await this.executePowerShell('Get-Clipboard');
33 |       return {
34 |         success: true,
35 |         message: 'Clipboard content retrieved',
36 |         data: content,
37 |       };
38 |     } catch (error) {
39 |       return {
40 |         success: false,
41 |         message: `Failed to get clipboard content: ${error instanceof Error ? error.message : String(error)}`,
42 |       };
43 |     }
44 |   }
45 | 
46 |   async setClipboardContent(input: ClipboardInput): Promise<WindowsControlResponse> {
47 |     try {
48 |       // Escape quotes in the text for PowerShell
49 |       const escapedText = input.text.replace(/"/g, '`"');
50 |       await this.executePowerShell(`Set-Clipboard -Value "${escapedText}"`);
51 |       return {
52 |         success: true,
53 |         message: 'Clipboard content set',
54 |       };
55 |     } catch (error) {
56 |       return {
57 |         success: false,
58 |         message: `Failed to set clipboard content: ${error instanceof Error ? error.message : String(error)}`,
59 |       };
60 |     }
61 |   }
62 | 
63 |   async hasClipboardText(): Promise<WindowsControlResponse> {
64 |     try {
65 |       const content = await this.executePowerShell('Get-Clipboard');
66 |       const hasText = content.length > 0;
67 |       return {
68 |         success: true,
69 |         message: `Clipboard ${hasText ? 'has' : 'does not have'} text`,
70 |         data: hasText,
71 |       };
72 |     } catch (error) {
73 |       return {
74 |         success: false,
75 |         message: `Failed to check clipboard: ${error instanceof Error ? error.message : String(error)}`,
76 |       };
77 |     }
78 |   }
79 | 
80 |   async clearClipboard(): Promise<WindowsControlResponse> {
81 |     try {
82 |       await this.executePowerShell('Set-Clipboard -Value ""');
83 |       return {
84 |         success: true,
85 |         message: 'Clipboard cleared',
86 |       };
87 |     } catch (error) {
88 |       return {
89 |         success: false,
90 |         message: `Failed to clear clipboard: ${error instanceof Error ? error.message : String(error)}`,
91 |       };
92 |     }
93 |   }
94 | }
95 | 
```

--------------------------------------------------------------------------------
/src/providers/registry.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, it, expect, beforeEach } from 'vitest';
 2 | import { DefaultProviderRegistry } from './registry.js';
 3 | import { ClipboardAutomation } from '../interfaces/automation.js';
 4 | import { ClipboardInput } from '../types/common.js';
 5 | import { WindowsControlResponse } from '../types/responses.js';
 6 | 
 7 | class MockClipboardProvider implements ClipboardAutomation {
 8 |   // eslint-disable-next-line @typescript-eslint/require-await
 9 |   async getClipboardContent(): Promise<WindowsControlResponse> {
10 |     return { success: true, message: 'Mock clipboard content', data: 'test' };
11 |   }
12 | 
13 |   // eslint-disable-next-line @typescript-eslint/require-await
14 |   async setClipboardContent(_input: ClipboardInput): Promise<WindowsControlResponse> {
15 |     return { success: true, message: 'Mock set clipboard' };
16 |   }
17 | 
18 |   // eslint-disable-next-line @typescript-eslint/require-await
19 |   async hasClipboardText(): Promise<WindowsControlResponse> {
20 |     return { success: true, message: 'Mock has text', data: true };
21 |   }
22 | 
23 |   // eslint-disable-next-line @typescript-eslint/require-await
24 |   async clearClipboard(): Promise<WindowsControlResponse> {
25 |     return { success: true, message: 'Mock clear clipboard' };
26 |   }
27 | }
28 | 
29 | describe('DefaultProviderRegistry', () => {
30 |   let registry: DefaultProviderRegistry;
31 | 
32 |   beforeEach(() => {
33 |     registry = new DefaultProviderRegistry();
34 |   });
35 | 
36 |   describe('registration', () => {
37 |     it('should register and retrieve clipboard provider', () => {
38 |       const provider = new MockClipboardProvider();
39 |       registry.registerClipboard('mock', provider);
40 | 
41 |       const retrieved = registry.getClipboard('mock');
42 |       expect(retrieved).toBe(provider);
43 |     });
44 | 
45 |     it('should return undefined for non-existent provider', () => {
46 |       const retrieved = registry.getClipboard('non-existent');
47 |       expect(retrieved).toBeUndefined();
48 |     });
49 | 
50 |     it('should allow overwriting existing provider', () => {
51 |       const provider1 = new MockClipboardProvider();
52 |       const provider2 = new MockClipboardProvider();
53 | 
54 |       registry.registerClipboard('mock', provider1);
55 |       registry.registerClipboard('mock', provider2);
56 | 
57 |       const retrieved = registry.getClipboard('mock');
58 |       expect(retrieved).toBe(provider2);
59 |     });
60 |   });
61 | 
62 |   describe('getAvailableProviders', () => {
63 |     it('should return empty arrays initially', () => {
64 |       const available = registry.getAvailableProviders();
65 | 
66 |       expect(available.keyboards).toEqual([]);
67 |       expect(available.mice).toEqual([]);
68 |       expect(available.screens).toEqual([]);
69 |       expect(available.clipboards).toEqual([]);
70 |     });
71 | 
72 |     it('should return registered provider names', () => {
73 |       registry.registerClipboard('provider1', new MockClipboardProvider());
74 |       registry.registerClipboard('provider2', new MockClipboardProvider());
75 | 
76 |       const available = registry.getAvailableProviders();
77 | 
78 |       expect(available.clipboards).toContain('provider1');
79 |       expect(available.clipboards).toContain('provider2');
80 |       expect(available.clipboards.length).toBe(2);
81 |     });
82 |   });
83 | });
84 | 
```

--------------------------------------------------------------------------------
/src/tools/screenshot-file.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { createAutomationProvider } from '../providers/factory.js';
 2 | import { ScreenshotOptions } from '../types/common.js';
 3 | import { WindowsControlResponse } from '../types/responses.js';
 4 | import * as fs from 'fs';
 5 | import * as path from 'path';
 6 | import * as os from 'os';
 7 | import { v4 as uuidv4 } from 'uuid';
 8 | 
 9 | /**
10 |  * Captures a screenshot and saves it to a temporary file
11 |  *
12 |  * @param options Optional configuration for the screenshot
13 |  * @returns Promise resolving to a WindowsControlResponse with the file path
14 |  */
15 | export async function getScreenshotToFile(
16 |   options?: ScreenshotOptions,
17 | ): Promise<WindowsControlResponse> {
18 |   try {
19 |     // Create a provider instance to handle the screenshot
20 |     const provider = createAutomationProvider();
21 | 
22 |     // Delegate to the provider's screenshot implementation
23 |     const result = await provider.screen.getScreenshot(options);
24 | 
25 |     // If the screenshot was successful and contains image data
26 |     if (result.success && result.content && result.content[0]?.type === 'image') {
27 |       // Create a unique filename in the system's temp directory
28 |       const tempDir = os.tmpdir();
29 |       const fileExt = options?.format === 'png' ? 'png' : 'jpg';
30 |       const filename = `screenshot-${uuidv4()}.${fileExt}`;
31 |       const filePath = path.join(tempDir, filename);
32 | 
33 |       // Get the base64 image data and ensure it's a string
34 |       let base64Image: string;
35 | 
36 |       try {
37 |         const imageData = result.content[0].data;
38 | 
39 |         // Validate the data is a string
40 |         if (typeof imageData !== 'string') {
41 |           return {
42 |             success: false,
43 |             message: 'Screenshot data is not in expected string format',
44 |           };
45 |         }
46 | 
47 |         // Remove the data URL prefix if present
48 |         base64Image = imageData.includes('base64,') ? imageData.split('base64,')[1] : imageData;
49 |       } catch {
50 |         return {
51 |           success: false,
52 |           message: 'Failed to process screenshot data',
53 |         };
54 |       }
55 | 
56 |       // Write the image data to the file
57 |       fs.writeFileSync(filePath, Buffer.from(base64Image, 'base64'));
58 | 
59 |       // Extract dimensions safely
60 |       const width =
61 |         typeof result.data === 'object' && result.data && 'width' in result.data
62 |           ? Number(result.data.width)
63 |           : undefined;
64 | 
65 |       const height =
66 |         typeof result.data === 'object' && result.data && 'height' in result.data
67 |           ? Number(result.data.height)
68 |           : undefined;
69 | 
70 |       // Return a response with the file path instead of the image data
71 |       return {
72 |         success: true,
73 |         message: 'Screenshot saved to temporary file',
74 |         data: {
75 |           filePath,
76 |           format: options?.format || 'jpeg',
77 |           width,
78 |           height,
79 |           timestamp: new Date().toISOString(),
80 |         },
81 |       };
82 |     }
83 | 
84 |     // If the screenshot failed or doesn't contain image data, return the original result
85 |     return result;
86 |   } catch (error) {
87 |     const errorMessage =
88 |       error instanceof Error ? error.message : 'Unknown error occurred while capturing screenshot';
89 | 
90 |     return {
91 |       success: false,
92 |       message: `Failed to capture screenshot to file: ${errorMessage}`,
93 |     };
94 |   }
95 | }
96 | 
```

--------------------------------------------------------------------------------
/scripts/compare-providers.js:
--------------------------------------------------------------------------------

```javascript
 1 | /* eslint-disable */
 2 | // Script to compare window handling between Keysender and NutJS providers
 3 | import { loadConfig } from '../build/config.js';
 4 | import { createAutomationProvider } from '../build/providers/factory.js';
 5 | 
 6 | // Test function to try all window operations with a provider
 7 | async function testProvider(providerName) {
 8 |   console.log(`\n=== TESTING ${providerName.toUpperCase()} PROVIDER ===\n`);
 9 |   
10 |   // Configure environment to use the specified provider
11 |   process.env.AUTOMATION_PROVIDER = providerName;
12 |   
13 |   // Load configuration and create provider
14 |   const config = loadConfig();
15 |   console.log(`Using provider: ${config.provider}`);
16 |   
17 |   const provider = createAutomationProvider(config.provider);
18 |   console.log(`Provider created: ${provider.constructor.name}`);
19 |   
20 |   // 1. Get screen size
21 |   console.log('\n1. Getting screen size:');
22 |   const screenSizeResult = provider.screen.getScreenSize();
23 |   console.log(`Success: ${screenSizeResult.success}`);
24 |   console.log(`Message: ${screenSizeResult.message}`);
25 |   console.log(`Data: ${JSON.stringify(screenSizeResult.data)}`);
26 |   
27 |   // 2. Get active window
28 |   console.log('\n2. Getting active window:');
29 |   const activeWindowResult = provider.screen.getActiveWindow();
30 |   console.log(`Success: ${activeWindowResult.success}`);
31 |   console.log(`Message: ${activeWindowResult.message}`);
32 |   console.log(`Data: ${JSON.stringify(activeWindowResult.data)}`);
33 |   
34 |   // Extract window title for later operations
35 |   const windowTitle = activeWindowResult.success && activeWindowResult.data?.title 
36 |     ? activeWindowResult.data.title 
37 |     : "Unknown";
38 |   
39 |   // 3. Focus window
40 |   console.log(`\n3. Focusing window "${windowTitle}":`);
41 |   const focusResult = provider.screen.focusWindow(windowTitle);
42 |   console.log(`Success: ${focusResult.success}`);
43 |   console.log(`Message: ${focusResult.message}`);
44 |   console.log(`Data: ${JSON.stringify(focusResult.data)}`);
45 |   
46 |   // 4. Resize window
47 |   console.log(`\n4. Resizing window "${windowTitle}" to 800x600:`);
48 |   const resizeResult = provider.screen.resizeWindow(windowTitle, 800, 600);
49 |   console.log(`Success: ${resizeResult.success}`);
50 |   console.log(`Message: ${resizeResult.message}`);
51 |   console.log(`Data: ${JSON.stringify(resizeResult.data)}`);
52 |   
53 |   // 5. Reposition window
54 |   console.log(`\n5. Repositioning window "${windowTitle}" to position (100, 100):`);
55 |   const repositionResult = provider.screen.repositionWindow(windowTitle, 100, 100);
56 |   console.log(`Success: ${repositionResult.success}`);
57 |   console.log(`Message: ${repositionResult.message}`);
58 |   console.log(`Data: ${JSON.stringify(repositionResult.data)}`);
59 |   
60 |   // 6. Final window check
61 |   console.log('\n6. Final window check:');
62 |   const finalWindowResult = provider.screen.getActiveWindow();
63 |   console.log(`Success: ${finalWindowResult.success}`);
64 |   console.log(`Message: ${finalWindowResult.message}`);
65 |   console.log(`Data: ${JSON.stringify(finalWindowResult.data)}`);
66 |   
67 |   console.log(`\n=== COMPLETED ${providerName.toUpperCase()} PROVIDER TESTS ===\n`);
68 | }
69 | 
70 | // Main execution
71 | (async () => {
72 |   try {
73 |     // First test keysender provider
74 |     await testProvider('keysender');
75 |     
76 |     // Only test keysender provider
77 |     // await testProvider('other-provider');
78 |   } catch (error) {
79 |     console.error('Error in testing:', error);
80 |   }
81 | })();
82 | 
```

--------------------------------------------------------------------------------
/src/tools/screenshot.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
  2 | import { getScreenshot } from './screenshot';
  3 | import { createAutomationProvider } from '../providers/factory';
  4 | 
  5 | // Mock the factory module
  6 | vi.mock('../providers/factory', () => ({
  7 |   createAutomationProvider: vi.fn(),
  8 | }));
  9 | 
 10 | describe('Screenshot Functions', () => {
 11 |   beforeEach(() => {
 12 |     vi.resetAllMocks();
 13 |   });
 14 | 
 15 |   afterEach(() => {
 16 |     vi.restoreAllMocks();
 17 |     vi.clearAllMocks();
 18 |   });
 19 | 
 20 |   describe('getScreenshot', () => {
 21 |     it('should delegate to provider and return screenshot data on success', async () => {
 22 |       // Setup mock provider
 23 |       const mockProvider = {
 24 |         screen: {
 25 |           getScreenshot: vi.fn().mockResolvedValue({
 26 |             success: true,
 27 |             message: 'Screenshot captured successfully',
 28 |             content: [
 29 |               {
 30 |                 type: 'image',
 31 |                 data: 'test-image-data-base64',
 32 |                 mimeType: 'image/png',
 33 |               },
 34 |             ],
 35 |           }),
 36 |         },
 37 |       };
 38 | 
 39 |       // Make createAutomationProvider return our mock
 40 |       vi.mocked(createAutomationProvider).mockReturnValue(mockProvider as any);
 41 | 
 42 |       // Execute
 43 |       const result = await getScreenshot();
 44 | 
 45 |       // Verify
 46 |       expect(createAutomationProvider).toHaveBeenCalledTimes(1);
 47 |       expect(mockProvider.screen.getScreenshot).toHaveBeenCalledTimes(1);
 48 |       expect(result).toEqual({
 49 |         success: true,
 50 |         message: 'Screenshot captured successfully',
 51 |         content: [
 52 |           {
 53 |             type: 'image',
 54 |             data: 'test-image-data-base64',
 55 |             mimeType: 'image/png',
 56 |           },
 57 |         ],
 58 |       });
 59 |     });
 60 | 
 61 |     it('should pass options to provider when specified', async () => {
 62 |       // Setup mock provider
 63 |       const mockProvider = {
 64 |         screen: {
 65 |           getScreenshot: vi.fn().mockResolvedValue({
 66 |             success: true,
 67 |             message: 'Screenshot captured successfully',
 68 |             content: [
 69 |               {
 70 |                 type: 'image',
 71 |                 data: 'test-image-data-base64',
 72 |                 mimeType: 'image/jpeg',
 73 |               },
 74 |             ],
 75 |           }),
 76 |         },
 77 |       };
 78 | 
 79 |       // Make createAutomationProvider return our mock
 80 |       vi.mocked(createAutomationProvider).mockReturnValue(mockProvider as any);
 81 | 
 82 |       // Options to pass
 83 |       const options = {
 84 |         region: { x: 100, y: 100, width: 800, height: 600 },
 85 |         format: 'jpeg' as const,
 86 |       };
 87 | 
 88 |       // Execute
 89 |       const result = await getScreenshot(options);
 90 | 
 91 |       // Verify
 92 |       expect(createAutomationProvider).toHaveBeenCalledTimes(1);
 93 |       expect(mockProvider.screen.getScreenshot).toHaveBeenCalledWith(options);
 94 |       expect(result).toEqual({
 95 |         success: true,
 96 |         message: 'Screenshot captured successfully',
 97 |         content: [
 98 |           {
 99 |             type: 'image',
100 |             data: 'test-image-data-base64',
101 |             mimeType: 'image/jpeg',
102 |           },
103 |         ],
104 |       });
105 |     });
106 | 
107 |     it('should return error response when provider throws an error', async () => {
108 |       // Setup mock provider that throws
109 |       const mockProvider = {
110 |         screen: {
111 |           getScreenshot: vi.fn().mockImplementation(() => {
112 |             throw new Error('Capture failed');
113 |           }),
114 |         },
115 |       };
116 | 
117 |       // Make createAutomationProvider return our mock
118 |       vi.mocked(createAutomationProvider).mockReturnValue(mockProvider as any);
119 | 
120 |       // Execute
121 |       const result = await getScreenshot();
122 | 
123 |       // Verify
124 |       expect(result).toEqual({
125 |         success: false,
126 |         message: 'Failed to capture screenshot: Capture failed',
127 |       });
128 |     });
129 |   });
130 | });
131 | 
```

--------------------------------------------------------------------------------
/src/providers/autohotkey/index.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, it, expect, vi, beforeEach } from 'vitest';
  2 | import { AutoHotkeyProvider } from './index.js';
  3 | 
  4 | // Mock child_process module properly
  5 | vi.mock('child_process', () => ({
  6 |   execSync: vi.fn(),
  7 |   exec: vi.fn(),
  8 |   spawn: vi.fn(),
  9 |   fork: vi.fn(),
 10 |   execFile: vi.fn(),
 11 | }));
 12 | 
 13 | describe('AutoHotkeyProvider', () => {
 14 |   let provider: AutoHotkeyProvider;
 15 | 
 16 |   beforeEach(() => {
 17 |     provider = new AutoHotkeyProvider();
 18 |   });
 19 | 
 20 |   it('should create an instance with all required automation interfaces', () => {
 21 |     expect(provider).toBeDefined();
 22 |     expect(provider.keyboard).toBeDefined();
 23 |     expect(provider.mouse).toBeDefined();
 24 |     expect(provider.screen).toBeDefined();
 25 |     expect(provider.clipboard).toBeDefined();
 26 |   });
 27 | 
 28 |   it('should implement KeyboardAutomation interface', () => {
 29 |     expect(provider.keyboard).toBeDefined();
 30 |     expect(provider.keyboard.typeText).toBeDefined();
 31 |     expect(provider.keyboard.pressKey).toBeDefined();
 32 |     expect(provider.keyboard.pressKeyCombination).toBeDefined();
 33 |     expect(provider.keyboard.holdKey).toBeDefined();
 34 |   });
 35 | 
 36 |   it('should implement MouseAutomation interface', () => {
 37 |     expect(provider.mouse).toBeDefined();
 38 |     expect(provider.mouse.moveMouse).toBeDefined();
 39 |     expect(provider.mouse.clickMouse).toBeDefined();
 40 |     expect(provider.mouse.doubleClick).toBeDefined();
 41 |     expect(provider.mouse.getCursorPosition).toBeDefined();
 42 |     expect(provider.mouse.scrollMouse).toBeDefined();
 43 |     expect(provider.mouse.dragMouse).toBeDefined();
 44 |     expect(provider.mouse.clickAt).toBeDefined();
 45 |   });
 46 | 
 47 |   it('should implement ScreenAutomation interface', () => {
 48 |     expect(provider.screen).toBeDefined();
 49 |     expect(provider.screen.getScreenSize).toBeDefined();
 50 |     expect(provider.screen.getActiveWindow).toBeDefined();
 51 |     expect(provider.screen.focusWindow).toBeDefined();
 52 |     expect(provider.screen.resizeWindow).toBeDefined();
 53 |     expect(provider.screen.repositionWindow).toBeDefined();
 54 |     expect(provider.screen.getScreenshot).toBeDefined();
 55 |   });
 56 | 
 57 |   it('should implement ClipboardAutomation interface', () => {
 58 |     expect(provider.clipboard).toBeDefined();
 59 |     expect(provider.clipboard.getClipboardContent).toBeDefined();
 60 |     expect(provider.clipboard.setClipboardContent).toBeDefined();
 61 |     expect(provider.clipboard.hasClipboardText).toBeDefined();
 62 |     expect(provider.clipboard.clearClipboard).toBeDefined();
 63 |   });
 64 | });
 65 | 
 66 | describe('AutoHotkeyProvider - Factory Integration', () => {
 67 |   beforeEach(() => {
 68 |     // Mock the factory module to avoid keysender ELF header issue
 69 |     vi.doMock('../factory.js', () => ({
 70 |       createAutomationProvider: vi.fn().mockImplementation((config: any) => {
 71 |         if (config?.provider === 'autohotkey' || config?.providers) {
 72 |           return new AutoHotkeyProvider();
 73 |         }
 74 |         return {};
 75 |       }),
 76 |     }));
 77 |   });
 78 | 
 79 |   it('should be available through the factory', async () => {
 80 |     const { createAutomationProvider } = await import('../factory.js');
 81 | 
 82 |     const provider = createAutomationProvider({ provider: 'autohotkey' });
 83 |     expect(provider).toBeDefined();
 84 |     expect(provider).toBeInstanceOf(AutoHotkeyProvider);
 85 |   });
 86 | 
 87 |   it('should support modular configuration', async () => {
 88 |     const { createAutomationProvider } = await import('../factory.js');
 89 | 
 90 |     const provider = createAutomationProvider({
 91 |       providers: {
 92 |         keyboard: 'autohotkey',
 93 |         mouse: 'autohotkey',
 94 |         screen: 'autohotkey',
 95 |         clipboard: 'autohotkey',
 96 |       },
 97 |     });
 98 | 
 99 |     expect(provider).toBeDefined();
100 |     expect(provider.keyboard).toBeDefined();
101 |     expect(provider.mouse).toBeDefined();
102 |     expect(provider.screen).toBeDefined();
103 |     expect(provider.clipboard).toBeDefined();
104 |   });
105 | });
106 | 
```

--------------------------------------------------------------------------------
/RELEASE_NOTES_v0.2.0.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Release Notes v0.2.0
  2 | 
  3 | ## 🎉 Major Features
  4 | 
  5 | ### SSE Transport Now Officially Supported
  6 | - Full implementation of Server-Sent Events (SSE) transport for network access
  7 | - Built using the MCP SDK transport layer for improved reliability
  8 | - HTTP/HTTPS server integration for secure connections
  9 | - Configurable port settings (default: 3232)
 10 | 
 11 | ### HTTPS/TLS Support
 12 | - Added secure TLS/SSL support for production deployments
 13 | - New CLI flags: `--https`, `--cert`, and `--key`
 14 | - Certificate validation for enhanced security
 15 | - Meets MCP specification requirements for secure remote access
 16 | 
 17 | ### Improved Documentation
 18 | - Added comprehensive Quick Start guide with build tools setup
 19 | - Enhanced installation instructions for Windows users
 20 | - Clear prerequisites including VC++ workload requirements
 21 | - Better guidance for Python and Node.js installation
 22 | 
 23 | ## 🚀 Enhancements
 24 | 
 25 | ### Infrastructure Improvements
 26 | - Optimized build process with npm ci and caching
 27 | - Standardized default port (3232) across entire codebase
 28 | - Removed unused dependencies (express, jimp, mcp-control)
 29 | - Improved GitHub Actions with better error handling
 30 | 
 31 | ### Testing Framework
 32 | - Added end-to-end testing suite for integration testing
 33 | - Better test coverage for SSE transport features
 34 | - Enhanced CI/CD pipeline reliability
 35 | 
 36 | ### Developer Experience
 37 | - Simplified SSE implementation using SDK transport
 38 | - Better error handling for client connections
 39 | - Buffer management improvements
 40 | - Platform-specific path fixes
 41 | 
 42 | ## 🔧 CLI Updates
 43 | 
 44 | New command line options:
 45 | ```bash
 46 | # Run with SSE transport
 47 | mcp-control --sse
 48 | 
 49 | # Run with HTTPS/TLS
 50 | mcp-control --sse --https --cert /path/to/cert.pem --key /path/to/key.pem
 51 | 
 52 | # Custom port
 53 | mcp-control --sse --port 3000
 54 | ```
 55 | 
 56 | ## 📦 Dependency Updates
 57 | 
 58 | - Updated `@modelcontextprotocol/sdk` to latest version
 59 | - Bumped TypeScript ESLint packages to v8.32.0+
 60 | - Updated `zod` to v3.24.4
 61 | - Various dev dependency updates for security
 62 | 
 63 | ## 📚 Documentation
 64 | 
 65 | - Added SSE transport documentation
 66 | - Updated README with release badges
 67 | - Improved branch structure documentation
 68 | - Added build tools setup instructions
 69 | - Enhanced security guidelines
 70 | 
 71 | ## 🐛 Bug Fixes
 72 | 
 73 | - Fixed TypeScript errors related to HTTP server usage
 74 | - Resolved client error handling in SSE transport
 75 | - Corrected platform-specific path issues
 76 | - Fixed npm ci error handling in build scripts
 77 | 
 78 | ## ⚠️ Breaking Changes
 79 | 
 80 | - SSE is now the recommended transport method
 81 | - HTTPS is required for production deployments per MCP spec
 82 | - Some internal API changes for transport handling
 83 | 
 84 | ## 🔐 Security
 85 | 
 86 | - Added proper TLS certificate validation
 87 | - Implemented security options for HTTPS connections
 88 | - Updated dependencies to address known vulnerabilities
 89 | 
 90 | ## 📈 Migration Guide
 91 | 
 92 | To upgrade from v0.1.x to v0.2.0:
 93 | 
 94 | 1. Update your Claude client configuration to use SSE transport:
 95 | ```json
 96 | {
 97 |   "mcpServers": {
 98 |     "MCPControl": {
 99 |       "command": "mcp-control",
100 |       "args": ["--transport", "sse"]
101 |     }
102 |   }
103 | }
104 | ```
105 | 
106 | 2. For production deployments, use HTTPS:
107 | ```bash
108 | mcp-control --sse --https --cert cert.pem --key key.pem
109 | ```
110 | 
111 | 3. Ensure you have the latest build tools installed as per the Quick Start guide
112 | 
113 | ## 👥 Contributors
114 | 
115 | Special thanks to all contributors who made this release possible, including:
116 | - @Cheffromspace for SSE transport and HTTPS implementation
117 | - @lwsinclair for adding the MseeP.ai security badge
118 | - All the community members who reported issues and provided feedback
119 | 
120 | ## 🔮 What's Next
121 | 
122 | - Multi-monitor support improvements
123 | - Enhanced click accuracy at different resolutions
124 | - Additional transport options
125 | - Performance optimizations
126 | 
127 | ---
128 | 
129 | Thank you for using MCPControl! We're excited to bring you these improvements and look forward to your feedback.
```

--------------------------------------------------------------------------------
/src/providers/keysender/index.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, it, expect, vi } from 'vitest';
  2 | import { createAutomationProvider } from '../factory.js';
  3 | // Imported for type checking but used indirectly through factory
  4 | import './index.js';
  5 | 
  6 | // Mock keysender module
  7 | vi.mock('keysender', async () => {
  8 |   await vi.importActual('vitest');
  9 | 
 10 |   const mockObject = {
 11 |     Hardware: vi.fn().mockImplementation(() => ({
 12 |       workwindow: {
 13 |         capture: vi.fn(),
 14 |         get: vi.fn(),
 15 |         set: vi.fn(),
 16 |         getView: vi.fn(),
 17 |         setForeground: vi.fn(),
 18 |         setView: vi.fn(),
 19 |         isForeground: vi.fn(),
 20 |         isOpen: vi.fn(),
 21 |       },
 22 |       mouse: {
 23 |         move: vi.fn(),
 24 |         leftClick: vi.fn(),
 25 |         rightClick: vi.fn(),
 26 |         middleClick: vi.fn(),
 27 |         doubleClick: vi.fn(),
 28 |         leftDown: vi.fn(),
 29 |         leftUp: vi.fn(),
 30 |         rightDown: vi.fn(),
 31 |         rightUp: vi.fn(),
 32 |         scroll: vi.fn(),
 33 |       },
 34 |       keyboard: {
 35 |         pressKey: vi.fn(),
 36 |         releaseKey: vi.fn(),
 37 |         typeString: vi.fn(),
 38 |       },
 39 |       clipboard: {
 40 |         getClipboard: vi.fn(),
 41 |         setClipboard: vi.fn(),
 42 |       },
 43 |     })),
 44 |     getScreenSize: vi.fn().mockReturnValue({ width: 1920, height: 1080 }),
 45 |     getAllWindows: vi.fn().mockReturnValue([{ title: 'Test Window', handle: 12345 }]),
 46 |     getWindowChildren: vi.fn().mockReturnValue([]),
 47 |   };
 48 | 
 49 |   return {
 50 |     default: mockObject,
 51 |     ...mockObject,
 52 |   };
 53 | });
 54 | 
 55 | // Create a simple mock of KeysenderProvider for use in tests
 56 | class MockKeysenderProvider {
 57 |   keyboard = { keyTap: vi.fn() };
 58 |   mouse = { moveMouse: vi.fn() };
 59 |   screen = { getScreenSize: vi.fn() };
 60 |   clipboard = { readClipboard: vi.fn() };
 61 | }
 62 | 
 63 | // Mock the factory to avoid native module loading issues
 64 | vi.mock('../factory.js', async () => {
 65 |   await vi.importActual('vitest');
 66 | 
 67 |   return {
 68 |     createAutomationProvider: vi.fn().mockImplementation((_providerType) => {
 69 |       return new MockKeysenderProvider();
 70 |     }),
 71 |   };
 72 | });
 73 | 
 74 | // Mock the automation classes
 75 | vi.mock('./keyboard.js', async () => {
 76 |   await vi.importActual('vitest');
 77 |   return {
 78 |     KeysenderKeyboardAutomation: vi.fn().mockImplementation(() => ({
 79 |       keyTap: vi.fn(),
 80 |       keyToggle: vi.fn(),
 81 |       typeString: vi.fn(),
 82 |       typeStringDelayed: vi.fn(),
 83 |       setKeyboardDelay: vi.fn(),
 84 |     })),
 85 |   };
 86 | });
 87 | 
 88 | vi.mock('./mouse.js', async () => {
 89 |   await vi.importActual('vitest');
 90 |   return {
 91 |     KeysenderMouseAutomation: vi.fn().mockImplementation(() => ({
 92 |       moveMouse: vi.fn(),
 93 |       moveMouseSmooth: vi.fn(),
 94 |       mouseClick: vi.fn(),
 95 |       mouseDoubleClick: vi.fn(),
 96 |       mouseToggle: vi.fn(),
 97 |       dragMouse: vi.fn(),
 98 |       scrollMouse: vi.fn(),
 99 |       getMousePosition: vi.fn(),
100 |       setMousePosition: vi.fn(),
101 |       setMouseSpeed: vi.fn(),
102 |     })),
103 |   };
104 | });
105 | 
106 | vi.mock('./screen.js', async () => {
107 |   await vi.importActual('vitest');
108 |   return {
109 |     KeysenderScreenAutomation: vi.fn().mockImplementation(() => ({
110 |       getScreenSize: vi.fn(),
111 |       getScreenshot: vi.fn(),
112 |       getActiveWindow: vi.fn(),
113 |       focusWindow: vi.fn(),
114 |       resizeWindow: vi.fn(),
115 |       repositionWindow: vi.fn(),
116 |     })),
117 |   };
118 | });
119 | 
120 | vi.mock('./clipboard.js', async () => {
121 |   await vi.importActual('vitest');
122 |   return {
123 |     KeysenderClipboardAutomation: vi.fn().mockImplementation(() => ({
124 |       readClipboard: vi.fn(),
125 |       writeClipboard: vi.fn(),
126 |     })),
127 |   };
128 | });
129 | 
130 | describe('KeysenderProvider', () => {
131 |   it('should be created through the factory', () => {
132 |     const provider = createAutomationProvider({ provider: 'keysender' });
133 |     expect(provider).toBeInstanceOf(MockKeysenderProvider);
134 |   });
135 | 
136 |   it('should have all required automation interfaces', () => {
137 |     const provider = new MockKeysenderProvider();
138 | 
139 |     expect(provider.keyboard).toBeDefined();
140 |     expect(provider.mouse).toBeDefined();
141 |     expect(provider.screen).toBeDefined();
142 |     expect(provider.clipboard).toBeDefined();
143 |   });
144 | });
145 | 
```

--------------------------------------------------------------------------------
/.github/pr-webhook-utils.cjs:
--------------------------------------------------------------------------------

```
  1 | /**
  2 |  * Utilities for PR webhook data handling and sanitization
  3 |  */
  4 | 
  5 | /**
  6 |  * Sanitizes text content to remove truly sensitive information
  7 |  * @param {string} text - Text content to sanitize
  8 |  * @returns {string} Sanitized text
  9 |  */
 10 | function sanitizeText(text) {
 11 |   if (!text) return '';
 12 |   
 13 |   try {
 14 |     return text
 15 |       // Remove common API tokens with specific patterns
 16 |       .replace(/(\b)(gh[ps]_[A-Za-z0-9_]{36,})(\b)/g, '[GH_TOKEN_REDACTED]')
 17 |       .replace(/(\b)(xox[pbar]-[0-9a-zA-Z-]{10,})(\b)/g, '[SLACK_TOKEN_REDACTED]')
 18 |       .replace(/(\b)(sk-[a-zA-Z0-9]{32,})(\b)/g, '[API_KEY_REDACTED]')
 19 |       .replace(/(\b)(AKIA[0-9A-Z]{16})(\b)/g, '[AWS_KEY_REDACTED]')
 20 |       // Remove emails, but only likely real ones (with valid TLDs)
 21 |       .replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}/g, '[EMAIL_REDACTED]')
 22 |       // Remove IP addresses
 23 |       .replace(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, '[IP_REDACTED]')
 24 |       // Remove control characters that might break JSON
 25 |       .replace(/[\u0000-\u001F\u007F-\u009F]/g, '');
 26 |   } catch (error) {
 27 |     console.warn(`Error sanitizing text: ${error.message}`);
 28 |     return '[Content omitted due to sanitization error]';
 29 |   }
 30 | }
 31 | 
 32 | /**
 33 |  * Checks if a file should be included in webhook data
 34 |  * @param {string} filename - Filename to check
 35 |  * @returns {boolean} Whether file should be included
 36 |  */
 37 | function shouldIncludeFile(filename) {
 38 |   if (!filename) return false;
 39 |   
 40 |   const sensitivePatterns = [
 41 |     // Only exclude actual sensitive files
 42 |     /\.env($|\.)/i,
 43 |     /\.key$/i, 
 44 |     /\.pem$/i, 
 45 |     /\.pfx$/i, 
 46 |     /\.p12$/i,
 47 |     // Binary files that would bloat the payload
 48 |     /\.(jpg|jpeg|png|gif|ico|pdf|zip|tar|gz|bin|exe)$/i
 49 |   ];
 50 |   
 51 |   return !sensitivePatterns.some(pattern => pattern.test(filename));
 52 | }
 53 | 
 54 | /**
 55 |  * Safely limits patch size to prevent payload issues
 56 |  * @param {string} patch - Git patch content
 57 |  * @returns {string|undefined} Limited patch or undefined on error
 58 |  */
 59 | function limitPatch(patch) {
 60 |   if (!patch) return undefined;
 61 |   
 62 |   try {
 63 |     // Increase reasonable patch size limit to 30KB
 64 |     const maxPatchSize = 30 * 1024;
 65 |     if (patch.length > maxPatchSize) {
 66 |       return patch.substring(0, maxPatchSize) + '\n[... PATCH TRUNCATED DUE TO SIZE ...]';
 67 |     }
 68 |     return patch;
 69 |   } catch (error) {
 70 |     console.warn(`Error limiting patch: ${error.message}`);
 71 |     return undefined; // Return undefined on error
 72 |   }
 73 | }
 74 | 
 75 | /**
 76 |  * Safely stringifies JSON with error handling
 77 |  * @param {Object} data - Data to stringify
 78 |  * @returns {Object} Result with success status and data/error
 79 |  */
 80 | function safeStringify(data) {
 81 |   try {
 82 |     const jsonData = JSON.stringify(data);
 83 |     return { success: true, data: jsonData };
 84 |   } catch (error) {
 85 |     console.error(`JSON stringify error: ${error.message}`);
 86 |     return {
 87 |       success: false,
 88 |       error: error.message
 89 |     };
 90 |   }
 91 | }
 92 | 
 93 | /**
 94 |  * Creates a simplified version of PR data that's less likely to cause parsing issues
 95 |  * Used as a fallback when the full PR data cannot be stringified
 96 |  * @param {Object} pr - Full PR data
 97 |  * @param {Object} context - GitHub context object
 98 |  * @returns {Object} Simplified PR data with essential information only
 99 |  */
100 | function createSimplifiedPrData(pr, context) {
101 |   return {
102 |     id: pr.data.id,
103 |     number: pr.data.number,
104 |     title: sanitizeText(pr.data.title),
105 |     state: pr.data.state,
106 |     created_at: pr.data.created_at,
107 |     repository: context.repo.repo,
108 |     owner: context.repo.owner,
109 |     body: sanitizeText(pr.data.body?.substring(0, 1000)),
110 |     head: { 
111 |       ref: pr.data.head.ref,
112 |       sha: pr.data.head.sha
113 |     },
114 |     base: {
115 |       ref: pr.data.base.ref,
116 |       sha: pr.data.base.sha
117 |     },
118 |     labels: pr.data.labels?.map(l => l.name),
119 |     error: 'Using simplified payload due to JSON serialization issues with full payload'
120 |   };
121 | }
122 | 
123 | module.exports = {
124 |   sanitizeText,
125 |   shouldIncludeFile,
126 |   limitPatch,
127 |   safeStringify,
128 |   createSimplifiedPrData
129 | };
130 | 
```

--------------------------------------------------------------------------------
/src/providers/factory.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { AutomationProvider } from '../interfaces/provider.js';
  2 | import { KeysenderProvider } from './keysender/index.js';
  3 | import { AutoHotkeyProvider } from './autohotkey/index.js';
  4 | import { registry } from './registry.js';
  5 | import { AutomationConfig } from '../config.js';
  6 | import {
  7 |   KeyboardAutomation,
  8 |   MouseAutomation,
  9 |   ScreenAutomation,
 10 |   ClipboardAutomation,
 11 | } from '../interfaces/automation.js';
 12 | 
 13 | // Import individual providers
 14 | import { PowerShellClipboardProvider } from './clipboard/powershell/index.js';
 15 | import { ClipboardyProvider } from './clipboard/clipboardy/index.js';
 16 | 
 17 | // Cache to store provider instances
 18 | const providerCache: Record<string, AutomationProvider> = {};
 19 | 
 20 | /**
 21 |  * Initialize the provider registry with available providers
 22 |  */
 23 | export function initializeProviders(): void {
 24 |   // Register clipboard providers
 25 |   registry.registerClipboard('powershell', new PowerShellClipboardProvider());
 26 |   registry.registerClipboard('clipboardy', new ClipboardyProvider());
 27 | 
 28 |   // Register AutoHotkey providers
 29 |   const autohotkeyProvider = new AutoHotkeyProvider();
 30 |   registry.registerKeyboard('autohotkey', autohotkeyProvider.keyboard);
 31 |   registry.registerMouse('autohotkey', autohotkeyProvider.mouse);
 32 |   registry.registerScreen('autohotkey', autohotkeyProvider.screen);
 33 |   registry.registerClipboard('autohotkey', autohotkeyProvider.clipboard);
 34 | 
 35 |   // TODO: Register other providers as they are implemented
 36 | }
 37 | 
 38 | /**
 39 |  * Composite provider that allows mixing different component providers
 40 |  */
 41 | class CompositeProvider implements AutomationProvider {
 42 |   keyboard: KeyboardAutomation;
 43 |   mouse: MouseAutomation;
 44 |   screen: ScreenAutomation;
 45 |   clipboard: ClipboardAutomation;
 46 | 
 47 |   constructor(
 48 |     keyboard: KeyboardAutomation,
 49 |     mouse: MouseAutomation,
 50 |     screen: ScreenAutomation,
 51 |     clipboard: ClipboardAutomation,
 52 |   ) {
 53 |     this.keyboard = keyboard;
 54 |     this.mouse = mouse;
 55 |     this.screen = screen;
 56 |     this.clipboard = clipboard;
 57 |   }
 58 | }
 59 | 
 60 | /**
 61 |  * Create an automation provider instance based on configuration
 62 |  * Supports both legacy monolithic providers and new modular providers
 63 |  */
 64 | export function createAutomationProvider(config?: AutomationConfig): AutomationProvider {
 65 |   // Initialize providers if not already done
 66 |   if (registry.getAvailableProviders().clipboards.length === 0) {
 67 |     initializeProviders();
 68 |   }
 69 | 
 70 |   if (!config || !config.providers) {
 71 |     // Legacy behavior: use monolithic provider
 72 |     const type = config?.provider || 'keysender';
 73 |     const providerType = type.toLowerCase();
 74 | 
 75 |     // Return cached instance if available
 76 |     if (providerCache[providerType]) {
 77 |       return providerCache[providerType];
 78 |     }
 79 | 
 80 |     let provider: AutomationProvider;
 81 |     switch (providerType) {
 82 |       case 'keysender':
 83 |         provider = new KeysenderProvider();
 84 |         break;
 85 |       case 'autohotkey':
 86 |         provider = new AutoHotkeyProvider();
 87 |         break;
 88 |       default:
 89 |         throw new Error(`Unknown provider type: ${providerType}`);
 90 |     }
 91 | 
 92 |     // Cache the instance
 93 |     providerCache[providerType] = provider;
 94 |     return provider;
 95 |   }
 96 | 
 97 |   // New modular approach
 98 |   const cacheKey = JSON.stringify(config.providers);
 99 |   if (providerCache[cacheKey]) {
100 |     return providerCache[cacheKey];
101 |   }
102 | 
103 |   // Get individual components from the registry
104 |   const keyboardProvider = config.providers.keyboard
105 |     ? registry.getKeyboard(config.providers.keyboard)
106 |     : new KeysenderProvider().keyboard;
107 | 
108 |   const mouseProvider = config.providers.mouse
109 |     ? registry.getMouse(config.providers.mouse)
110 |     : new KeysenderProvider().mouse;
111 | 
112 |   const screenProvider = config.providers.screen
113 |     ? registry.getScreen(config.providers.screen)
114 |     : new KeysenderProvider().screen;
115 | 
116 |   const clipboardProvider = config.providers.clipboard
117 |     ? registry.getClipboard(config.providers.clipboard)
118 |     : new KeysenderProvider().clipboard;
119 | 
120 |   if (!keyboardProvider || !mouseProvider || !screenProvider || !clipboardProvider) {
121 |     throw new Error('Failed to resolve all provider components');
122 |   }
123 | 
124 |   const compositeProvider = new CompositeProvider(
125 |     keyboardProvider,
126 |     mouseProvider,
127 |     screenProvider,
128 |     clipboardProvider,
129 |   );
130 | 
131 |   providerCache[cacheKey] = compositeProvider;
132 |   return compositeProvider;
133 | }
134 | 
```

--------------------------------------------------------------------------------
/src/logger.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Centralized logger using Pino
  3 |  * Provides consistent logging with configurable log levels
  4 |  */
  5 | 
  6 | /**
  7 |  * Log levels in order of verbosity (most verbose to least verbose)
  8 |  * - trace: Most detailed information for tracing code execution
  9 |  * - debug: Debugging information useful during development
 10 |  * - info: General information about normal operation
 11 |  * - warn: Warning conditions that should be reviewed
 12 |  * - error: Error conditions that don't interrupt operation
 13 |  * - fatal: Critical errors that might interrupt operation
 14 |  * - silent: No logs at all
 15 |  */
 16 | type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'silent';
 17 | 
 18 | /**
 19 |  * Interface for a logger
 20 |  */
 21 | export interface Logger {
 22 |   trace(msg: string, ...args: unknown[]): void;
 23 |   debug(msg: string, ...args: unknown[]): void;
 24 |   info(msg: string, ...args: unknown[]): void;
 25 |   warn(msg: string, ...args: unknown[]): void;
 26 |   error(msg: string, ...args: unknown[]): void;
 27 |   fatal(msg: string, ...args: unknown[]): void;
 28 |   child(bindings: Record<string, unknown>): Logger;
 29 | }
 30 | 
 31 | /**
 32 |  * Simple console-based logger implementation
 33 |  * This will be replaced with Pino in the package.json update
 34 |  */
 35 | class ConsoleLogger implements Logger {
 36 |   private level: number;
 37 |   private readonly levelMap: Record<LogLevel, number> = {
 38 |     trace: 10,
 39 |     debug: 20,
 40 |     info: 30,
 41 |     warn: 40,
 42 |     error: 50,
 43 |     fatal: 60,
 44 |     silent: 100,
 45 |   };
 46 |   private context: Record<string, unknown> = {};
 47 | 
 48 |   constructor(level: LogLevel = 'info', context: Record<string, unknown> = {}) {
 49 |     this.level = this.levelMap[level];
 50 |     this.context = context;
 51 |   }
 52 | 
 53 |   private formatMessage(msg: string): string {
 54 |     if (Object.keys(this.context).length === 0) {
 55 |       return msg;
 56 |     }
 57 |     
 58 |     const contextStr = Object.entries(this.context)
 59 |       .map(([key, value]) => `${key}=${String(value)}`)
 60 |       .join(' ');
 61 |     
 62 |     return `[${contextStr}] ${msg}`;
 63 |   }
 64 | 
 65 |   private shouldLog(level: LogLevel): boolean {
 66 |     return this.levelMap[level] >= this.level;
 67 |   }
 68 | 
 69 |   trace(msg: string, ...args: unknown[]): void {
 70 |     if (this.shouldLog('trace')) {
 71 |       console.log(`[TRACE] ${this.formatMessage(msg)}`, ...args);
 72 |     }
 73 |   }
 74 | 
 75 |   debug(msg: string, ...args: unknown[]): void {
 76 |     if (this.shouldLog('debug')) {
 77 |       console.log(`[DEBUG] ${this.formatMessage(msg)}`, ...args);
 78 |     }
 79 |   }
 80 | 
 81 |   info(msg: string, ...args: unknown[]): void {
 82 |     if (this.shouldLog('info')) {
 83 |       console.log(`[INFO] ${this.formatMessage(msg)}`, ...args);
 84 |     }
 85 |   }
 86 | 
 87 |   warn(msg: string, ...args: unknown[]): void {
 88 |     if (this.shouldLog('warn')) {
 89 |       console.warn(`[WARN] ${this.formatMessage(msg)}`, ...args);
 90 |     }
 91 |   }
 92 | 
 93 |   error(msg: string, ...args: unknown[]): void {
 94 |     if (this.shouldLog('error')) {
 95 |       console.error(`[ERROR] ${this.formatMessage(msg)}`, ...args);
 96 |     }
 97 |   }
 98 | 
 99 |   fatal(msg: string, ...args: unknown[]): void {
100 |     if (this.shouldLog('fatal')) {
101 |       console.error(`[FATAL] ${this.formatMessage(msg)}`, ...args);
102 |     }
103 |   }
104 | 
105 |   child(bindings: Record<string, unknown>): Logger {
106 |     return new ConsoleLogger(
107 |       Object.keys(this.levelMap).find(key => this.levelMap[key as LogLevel] === this.level) as LogLevel,
108 |       { ...this.context, ...bindings }
109 |     );
110 |   }
111 | }
112 | 
113 | /**
114 |  * Get log level from environment variable or use default
115 |  */
116 | export function getLogLevel(): LogLevel {
117 |   const envLevel = process.env.LOG_LEVEL?.toLowerCase() as LogLevel | undefined;
118 |   
119 |   // Validate that the provided level is valid
120 |   const validLevels: LogLevel[] = ['trace', 'debug', 'info', 'warn', 'error', 'fatal', 'silent'];
121 |   
122 |   if (envLevel && validLevels.includes(envLevel)) {
123 |     return envLevel;
124 |   }
125 |   
126 |   // Default to info in production, debug in development/test
127 |   if (process.env.NODE_ENV === 'production') {
128 |     return 'info';
129 |   } else if (process.env.NODE_ENV === 'test' || process.env.VITEST) {
130 |     return 'silent'; // Silent in tests unless explicitly set
131 |   } else {
132 |     return 'debug';
133 |   }
134 | }
135 | 
136 | // Create the default logger instance
137 | export const logger: Logger = new ConsoleLogger(getLogLevel());
138 | 
139 | /**
140 |  * Create a child logger with component context
141 |  * @param component Component name or identifier
142 |  * @returns Logger instance with component context
143 |  */
144 | export function createLogger(component: string): Logger {
145 |   return logger.child({ component });
146 | }
147 | 
148 | export default logger;
```

--------------------------------------------------------------------------------
/src/tools/keyboard.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, it, expect, vi, beforeEach } from 'vitest';
  2 | import { typeText, pressKey, pressKeyCombination, holdKey } from './keyboard.js';
  3 | import type { KeyboardInput, KeyCombination, KeyHoldOperation } from '../types/common.js';
  4 | 
  5 | // Mock the provider
  6 | vi.mock('../providers/factory.js', () => ({
  7 |   createAutomationProvider: () => ({
  8 |     keyboard: {
  9 |       typeText: vi.fn().mockImplementation(() => ({
 10 |         success: true,
 11 |         message: 'Typed text successfully',
 12 |       })),
 13 |       pressKey: vi.fn().mockImplementation((key) => ({
 14 |         success: true,
 15 |         message: `Pressed key: ${key}`,
 16 |       })),
 17 |       pressKeyCombination: vi.fn().mockImplementation((combination) => ({
 18 |         success: true,
 19 |         message: `Pressed key combination: ${combination.keys.join('+')}`,
 20 |       })),
 21 |       holdKey: vi.fn().mockImplementation((operation) =>
 22 |         operation.state === 'down'
 23 |           ? {
 24 |               success: true,
 25 |               message: `Key ${operation.key} held successfully for ${operation.duration}ms`,
 26 |             }
 27 |           : { success: true, message: `Key ${operation.key} released successfully` },
 28 |       ),
 29 |     },
 30 |   }),
 31 | }));
 32 | 
 33 | describe('Keyboard Tools', () => {
 34 |   beforeEach(() => {
 35 |     vi.clearAllMocks();
 36 |   });
 37 | 
 38 |   describe('typeText', () => {
 39 |     it('should successfully type text', () => {
 40 |       const input: KeyboardInput = { text: 'Hello World' };
 41 |       const result = typeText(input);
 42 | 
 43 |       expect(result).toEqual({
 44 |         success: true,
 45 |         message: 'Typed text successfully',
 46 |       });
 47 |     });
 48 | 
 49 |     it('should handle errors when text is missing', () => {
 50 |       const input: KeyboardInput = { text: '' };
 51 |       const result = typeText(input);
 52 | 
 53 |       expect(result.success).toBe(false);
 54 |       expect(result.message).toContain('Text is required');
 55 |     });
 56 | 
 57 |     it('should handle errors when text is too long', () => {
 58 |       // Create a string that's too long
 59 |       const longText = 'a'.repeat(1001);
 60 |       const input: KeyboardInput = { text: longText };
 61 |       const result = typeText(input);
 62 | 
 63 |       expect(result.success).toBe(false);
 64 |       expect(result.message).toContain('Text too long');
 65 |     });
 66 |   });
 67 | 
 68 |   describe('pressKey', () => {
 69 |     it('should successfully press a single key', () => {
 70 |       const result = pressKey('a');
 71 | 
 72 |       expect(result).toEqual({
 73 |         success: true,
 74 |         message: 'Pressed key: a',
 75 |       });
 76 |     });
 77 | 
 78 |     it('should handle errors when key is invalid', () => {
 79 |       const result = pressKey('invalid_key');
 80 | 
 81 |       expect(result.success).toBe(false);
 82 |       expect(result.message).toContain('Invalid key');
 83 |     });
 84 |   });
 85 | 
 86 |   describe('pressKeyCombination', () => {
 87 |     it('should successfully press a key combination', async () => {
 88 |       const combination: KeyCombination = { keys: ['ctrl', 'c'] };
 89 |       const result = await pressKeyCombination(combination);
 90 | 
 91 |       expect(result).toEqual({
 92 |         success: true,
 93 |         message: 'Pressed key combination: ctrl+c',
 94 |       });
 95 |     });
 96 | 
 97 |     it('should handle errors when combination is invalid', async () => {
 98 |       const result = await pressKeyCombination({ keys: [] });
 99 | 
100 |       expect(result.success).toBe(false);
101 |       expect(result.message).toContain('Key combination must contain at least one key');
102 |     });
103 |   });
104 | 
105 |   describe('holdKey', () => {
106 |     beforeEach(() => {
107 |       vi.useFakeTimers();
108 |     });
109 | 
110 |     it('should successfully hold and release a key', async () => {
111 |       const operation: KeyHoldOperation = {
112 |         key: 'shift',
113 |         duration: 1000,
114 |         state: 'down',
115 |       };
116 | 
117 |       const holdPromise = holdKey(operation);
118 | 
119 |       // Fast-forward through the duration
120 |       await vi.runAllTimersAsync();
121 |       const result = await holdPromise;
122 | 
123 |       expect(result).toEqual({
124 |         success: true,
125 |         message: 'Key shift held successfully for 1000ms',
126 |       });
127 |     });
128 | 
129 |     it('should handle just releasing a key', async () => {
130 |       const operation: KeyHoldOperation = {
131 |         key: 'shift',
132 |         duration: 0,
133 |         state: 'up',
134 |       };
135 | 
136 |       const result = await holdKey(operation);
137 | 
138 |       expect(result).toEqual({
139 |         success: true,
140 |         message: 'Key shift released successfully',
141 |       });
142 |     });
143 | 
144 |     it('should handle errors when key is invalid', async () => {
145 |       const operation: KeyHoldOperation = {
146 |         // @ts--error - Testing invalid input
147 |         key: 'invalid_key',
148 |         duration: 1000,
149 |         state: 'down',
150 |       };
151 | 
152 |       const result = await holdKey(operation);
153 | 
154 |       expect(result.success).toBe(false);
155 |       expect(result.message).toContain('Invalid key');
156 |     });
157 |   });
158 | });
159 | 
```

--------------------------------------------------------------------------------
/src/tools/mouse.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { MousePosition } from '../types/common.js';
  2 | import { WindowsControlResponse } from '../types/responses.js';
  3 | import { createAutomationProvider } from '../providers/factory.js';
  4 | import { MousePositionSchema, MouseButtonSchema, ScrollAmountSchema } from './validation.zod.js';
  5 | import { createLogger } from '../logger.js';
  6 | 
  7 | // Get the automation provider
  8 | const provider = createAutomationProvider();
  9 | 
 10 | // Create a logger for mouse module
 11 | const logger = createLogger('mouse');
 12 | 
 13 | // Define button types
 14 | type MouseButton = 'left' | 'right' | 'middle';
 15 | 
 16 | export function moveMouse(position: MousePosition): WindowsControlResponse {
 17 |   try {
 18 |     // Validate the position
 19 |     MousePositionSchema.parse(position);
 20 | 
 21 |     // Additional screen bounds check if not in test environment
 22 |     if (!(process.env.NODE_ENV === 'test' || process.env.VITEST)) {
 23 |       try {
 24 |         const screenSizeResponse = provider.screen.getScreenSize();
 25 |         if (screenSizeResponse.success && screenSizeResponse.data) {
 26 |           const screenSize = screenSizeResponse.data as { width: number; height: number };
 27 |           if (
 28 |             position.x < 0 ||
 29 |             position.x >= screenSize.width ||
 30 |             position.y < 0 ||
 31 |             position.y >= screenSize.height
 32 |           ) {
 33 |             throw new Error(
 34 |               `Position (${position.x},${position.y}) is outside screen bounds (0,0)-(${screenSize.width - 1},${screenSize.height - 1})`,
 35 |             );
 36 |           }
 37 |         }
 38 |       } catch (screenError) {
 39 |         logger.warn('Error checking screen bounds', screenError);
 40 |         // Continue without screen bounds check
 41 |       }
 42 |     }
 43 | 
 44 |     return provider.mouse.moveMouse(position);
 45 |   } catch (error) {
 46 |     return {
 47 |       success: false,
 48 |       message: `Failed to move mouse: ${error instanceof Error ? error.message : String(error)}`,
 49 |     };
 50 |   }
 51 | }
 52 | 
 53 | export function clickMouse(button: MouseButton = 'left'): WindowsControlResponse {
 54 |   try {
 55 |     // Validate button
 56 |     MouseButtonSchema.parse(button);
 57 |     const validatedButton = button;
 58 | 
 59 |     return provider.mouse.clickMouse(validatedButton);
 60 |   } catch (error) {
 61 |     return {
 62 |       success: false,
 63 |       message: `Failed to click mouse: ${error instanceof Error ? error.message : String(error)}`,
 64 |     };
 65 |   }
 66 | }
 67 | 
 68 | export function doubleClick(position?: MousePosition): WindowsControlResponse {
 69 |   try {
 70 |     // Validate position if provided
 71 |     if (position) {
 72 |       MousePositionSchema.parse(position);
 73 |     }
 74 | 
 75 |     return provider.mouse.doubleClick(position);
 76 |   } catch (error) {
 77 |     return {
 78 |       success: false,
 79 |       message: `Failed to double click: ${error instanceof Error ? error.message : String(error)}`,
 80 |     };
 81 |   }
 82 | }
 83 | 
 84 | export function getCursorPosition(): WindowsControlResponse {
 85 |   try {
 86 |     return provider.mouse.getCursorPosition();
 87 |   } catch (error) {
 88 |     return {
 89 |       success: false,
 90 |       message: `Failed to get cursor position: ${error instanceof Error ? error.message : String(error)}`,
 91 |     };
 92 |   }
 93 | }
 94 | 
 95 | export function scrollMouse(amount: number): WindowsControlResponse {
 96 |   try {
 97 |     // Validate amount
 98 |     ScrollAmountSchema.parse(amount);
 99 | 
100 |     return provider.mouse.scrollMouse(amount);
101 |   } catch (error) {
102 |     return {
103 |       success: false,
104 |       message: `Failed to scroll mouse: ${error instanceof Error ? error.message : String(error)}`,
105 |     };
106 |   }
107 | }
108 | 
109 | export function dragMouse(
110 |   from: MousePosition,
111 |   to: MousePosition,
112 |   button: MouseButton = 'left',
113 | ): WindowsControlResponse {
114 |   try {
115 |     // Validate positions
116 |     MousePositionSchema.parse(from);
117 |     MousePositionSchema.parse(to);
118 | 
119 |     // Validate button
120 |     MouseButtonSchema.parse(button);
121 |     const validatedButton = button;
122 | 
123 |     return provider.mouse.dragMouse(from, to, validatedButton);
124 |   } catch (error) {
125 |     return {
126 |       success: false,
127 |       message: `Failed to drag mouse: ${error instanceof Error ? error.message : String(error)}`,
128 |     };
129 |   }
130 | }
131 | 
132 | export function clickAt(
133 |   x: number,
134 |   y: number,
135 |   button: MouseButton = 'left',
136 | ): WindowsControlResponse {
137 |   // Special case for test compatibility (match original implementation)
138 |   if (typeof x !== 'number' || typeof y !== 'number' || isNaN(x) || isNaN(y)) {
139 |     return {
140 |       success: false,
141 |       message: 'Invalid coordinates provided',
142 |     };
143 |   }
144 | 
145 |   try {
146 |     // Validate position against screen bounds
147 |     MousePositionSchema.parse({ x, y });
148 | 
149 |     // Validate button
150 |     MouseButtonSchema.parse(button);
151 |     const validatedButton = button;
152 | 
153 |     return provider.mouse.clickAt(x, y, validatedButton);
154 |   } catch (error) {
155 |     return {
156 |       success: false,
157 |       message: `Failed to click at position: ${error instanceof Error ? error.message : String(error)}`,
158 |     };
159 |   }
160 | }
161 | 
```

--------------------------------------------------------------------------------
/src/providers/clipboard/powershell/index.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, it, expect, vi, beforeEach } from 'vitest';
  2 | import type { ClipboardInput } from '../../../types/common.js';
  3 | 
  4 | // Set up mocks at the top level
  5 | const execAsyncMock = vi.fn();
  6 | 
  7 | vi.mock('child_process', () => ({
  8 |   exec: vi.fn(),
  9 | }));
 10 | 
 11 | vi.mock('util', () => ({
 12 |   promisify: vi.fn().mockReturnValue(execAsyncMock),
 13 | }));
 14 | 
 15 | // Dynamic import to ensure mocks are setup
 16 | describe('PowerShellClipboardProvider', () => {
 17 |   let PowerShellClipboardProvider: any;
 18 |   let provider: any;
 19 | 
 20 |   beforeEach(async () => {
 21 |     vi.clearAllMocks();
 22 |     execAsyncMock.mockReset();
 23 | 
 24 |     // Dynamically import after mocks are setup
 25 |     const module = await import('./index.js');
 26 |     PowerShellClipboardProvider = module.PowerShellClipboardProvider;
 27 |     provider = new PowerShellClipboardProvider();
 28 |   });
 29 | 
 30 |   describe('getClipboardContent', () => {
 31 |     it('should get clipboard content successfully', async () => {
 32 |       execAsyncMock.mockResolvedValue({ stdout: 'Test content\n', stderr: '' });
 33 | 
 34 |       const result = await provider.getClipboardContent();
 35 | 
 36 |       expect(result.success).toBe(true);
 37 |       expect(result.data).toBe('Test content');
 38 |       expect(execAsyncMock).toHaveBeenCalledWith('powershell.exe -Command "Get-Clipboard"');
 39 |     });
 40 | 
 41 |     it('should handle errors', async () => {
 42 |       execAsyncMock.mockRejectedValue(new Error('PowerShell error'));
 43 | 
 44 |       const result = await provider.getClipboardContent();
 45 | 
 46 |       expect(result.success).toBe(false);
 47 |       expect(result.message).toContain('Failed to get clipboard content');
 48 |     });
 49 | 
 50 |     it('should handle stderr', async () => {
 51 |       execAsyncMock.mockResolvedValue({ stdout: '', stderr: 'Error output' });
 52 | 
 53 |       const result = await provider.getClipboardContent();
 54 | 
 55 |       expect(result.success).toBe(false);
 56 |       expect(result.message).toContain('Error output');
 57 |     });
 58 |   });
 59 | 
 60 |   describe('setClipboardContent', () => {
 61 |     it('should set clipboard content successfully', async () => {
 62 |       execAsyncMock.mockResolvedValue({ stdout: '', stderr: '' });
 63 |       const input: ClipboardInput = { text: 'New content' };
 64 | 
 65 |       const result = await provider.setClipboardContent(input);
 66 | 
 67 |       expect(result.success).toBe(true);
 68 |       expect(execAsyncMock).toHaveBeenCalledWith(
 69 |         'powershell.exe -Command "Set-Clipboard -Value "New content""',
 70 |       );
 71 |     });
 72 | 
 73 |     it('should escape quotes in text', async () => {
 74 |       execAsyncMock.mockResolvedValue({ stdout: '', stderr: '' });
 75 |       const input: ClipboardInput = { text: 'Text with "quotes"' };
 76 | 
 77 |       await provider.setClipboardContent(input);
 78 | 
 79 |       expect(execAsyncMock).toHaveBeenCalledWith(
 80 |         'powershell.exe -Command "Set-Clipboard -Value "Text with `"quotes`"""',
 81 |       );
 82 |     });
 83 | 
 84 |     it('should handle errors', async () => {
 85 |       execAsyncMock.mockRejectedValue(new Error('PowerShell error'));
 86 | 
 87 |       const input: ClipboardInput = { text: 'Test' };
 88 |       const result = await provider.setClipboardContent(input);
 89 | 
 90 |       expect(result.success).toBe(false);
 91 |       expect(result.message).toContain('Failed to set clipboard content');
 92 |     });
 93 |   });
 94 | 
 95 |   describe('hasClipboardText', () => {
 96 |     it('should return true when clipboard has text', async () => {
 97 |       execAsyncMock.mockResolvedValue({ stdout: 'Some text\n', stderr: '' });
 98 | 
 99 |       const result = await provider.hasClipboardText();
100 | 
101 |       expect(result.success).toBe(true);
102 |       expect(result.data).toBe(true);
103 |     });
104 | 
105 |     it('should return false when clipboard is empty', async () => {
106 |       execAsyncMock.mockResolvedValue({ stdout: '\n', stderr: '' });
107 | 
108 |       const result = await provider.hasClipboardText();
109 | 
110 |       expect(result.success).toBe(true);
111 |       expect(result.data).toBe(false);
112 |     });
113 | 
114 |     it('should return false when clipboard is empty string', async () => {
115 |       execAsyncMock.mockResolvedValue({ stdout: '', stderr: '' });
116 | 
117 |       const result = await provider.hasClipboardText();
118 | 
119 |       expect(result.success).toBe(true);
120 |       expect(result.data).toBe(false);
121 |     });
122 |   });
123 | 
124 |   describe('clearClipboard', () => {
125 |     it('should clear clipboard successfully', async () => {
126 |       execAsyncMock.mockResolvedValue({ stdout: '', stderr: '' });
127 | 
128 |       const result = await provider.clearClipboard();
129 | 
130 |       expect(result.success).toBe(true);
131 |       expect(execAsyncMock).toHaveBeenCalledWith(
132 |         'powershell.exe -Command "Set-Clipboard -Value """',
133 |       );
134 |     });
135 | 
136 |     it('should handle errors in clearClipboard', async () => {
137 |       execAsyncMock.mockRejectedValue(new Error('PowerShell error'));
138 | 
139 |       const result = await provider.clearClipboard();
140 | 
141 |       expect(result.success).toBe(false);
142 |       expect(result.message).toContain('Failed to clear clipboard');
143 |     });
144 |   });
145 | });
146 | 
```

--------------------------------------------------------------------------------
/src/providers/autohotkey/clipboard.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { execSync } from 'child_process';
  2 | import { writeFileSync, unlinkSync, readFileSync } from 'fs';
  3 | import { tmpdir } from 'os';
  4 | import { join } from 'path';
  5 | import { WindowsControlResponse } from '../../types/responses.js';
  6 | import { ClipboardAutomation } from '../../interfaces/automation.js';
  7 | import { ClipboardInput } from '../../types/common.js';
  8 | import { getAutoHotkeyPath } from './utils.js';
  9 | 
 10 | /**
 11 |  * AutoHotkey implementation of the ClipboardAutomation interface
 12 |  */
 13 | export class AutoHotkeyClipboardAutomation implements ClipboardAutomation {
 14 |   /**
 15 |    * Execute an AutoHotkey script
 16 |    */
 17 |   private executeScript(script: string): void {
 18 |     const scriptPath = join(tmpdir(), `mcp-ahk-${Date.now()}.ahk`);
 19 | 
 20 |     try {
 21 |       // Write the script to a temporary file
 22 |       writeFileSync(scriptPath, script, 'utf8');
 23 | 
 24 |       // Execute the script with AutoHotkey v2
 25 |       const autohotkeyPath = getAutoHotkeyPath();
 26 |       execSync(`"${autohotkeyPath}" "${scriptPath}"`, { stdio: 'pipe' });
 27 |     } finally {
 28 |       // Clean up the temporary script file
 29 |       try {
 30 |         unlinkSync(scriptPath);
 31 |       } catch {
 32 |         // Ignore cleanup errors
 33 |       }
 34 |     }
 35 |   }
 36 | 
 37 |   /**
 38 |    * Execute a script and return output from a temporary file
 39 |    */
 40 |   private executeScriptWithOutput(script: string, _outputPath: string): void {
 41 |     const scriptPath = join(tmpdir(), `mcp-ahk-${Date.now()}.ahk`);
 42 | 
 43 |     try {
 44 |       writeFileSync(scriptPath, script, 'utf8');
 45 |       const autohotkeyPath = getAutoHotkeyPath();
 46 |       execSync(`"${autohotkeyPath}" "${scriptPath}"`, { stdio: 'pipe' });
 47 |     } finally {
 48 |       try {
 49 |         unlinkSync(scriptPath);
 50 |       } catch {
 51 |         // Ignore cleanup errors
 52 |       }
 53 |     }
 54 |   }
 55 | 
 56 |   // eslint-disable-next-line @typescript-eslint/require-await
 57 |   async setClipboardContent(input: ClipboardInput): Promise<WindowsControlResponse> {
 58 |     try {
 59 |       // Escape special characters
 60 |       const escapedText = input.text
 61 |         .replace(/\\/g, '\\\\')
 62 |         .replace(/"/g, '\\"')
 63 |         .replace(/`/g, '``')
 64 |         .replace(/{/g, '{{')
 65 |         .replace(/}/g, '}}');
 66 | 
 67 |       const script = `
 68 |         A_Clipboard := "${escapedText}"
 69 |         ExitApp
 70 |       `;
 71 | 
 72 |       this.executeScript(script);
 73 | 
 74 |       return {
 75 |         success: true,
 76 |         message: 'Text copied to clipboard',
 77 |       };
 78 |     } catch (error) {
 79 |       return {
 80 |         success: false,
 81 |         message: `Failed to copy to clipboard: ${error instanceof Error ? error.message : String(error)}`,
 82 |       };
 83 |     }
 84 |   }
 85 | 
 86 |   // This method is not part of the interface - removing it
 87 |   /*
 88 |   paste(): WindowsControlResponse {
 89 |     try {
 90 |       const script = `
 91 |         Send("^v")
 92 |         ExitApp
 93 |       `;
 94 | 
 95 |       this.executeScript(script);
 96 | 
 97 |       return {
 98 |         success: true,
 99 |         message: 'Pasted from clipboard',
100 |       };
101 |     } catch (error) {
102 |       return {
103 |         success: false,
104 |         message: `Failed to paste from clipboard: ${error instanceof Error ? error.message : String(error)}`,
105 |       };
106 |     }
107 |   }
108 |   */
109 | 
110 |   // eslint-disable-next-line @typescript-eslint/require-await
111 |   async hasClipboardText(): Promise<WindowsControlResponse> {
112 |     try {
113 |       const outputPath = join(tmpdir(), `mcp-ahk-output-${Date.now()}.txt`);
114 |       const script = `
115 |         hasText := A_Clipboard != ""
116 |         FileAppend(hasText ? "true" : "false", "${outputPath}")
117 |         ExitApp
118 |       `;
119 | 
120 |       this.executeScriptWithOutput(script, outputPath);
121 | 
122 |       try {
123 |         const result = readFileSync(outputPath, 'utf8');
124 |         const hasText = result === 'true';
125 | 
126 |         return {
127 |           success: true,
128 |           message: hasText ? 'Clipboard contains text' : 'Clipboard is empty',
129 |           data: { hasText },
130 |         };
131 |       } finally {
132 |         try {
133 |           unlinkSync(outputPath);
134 |         } catch {
135 |           // Ignore cleanup errors
136 |         }
137 |       }
138 |     } catch (error) {
139 |       return {
140 |         success: false,
141 |         message: `Failed to check clipboard content: ${error instanceof Error ? error.message : String(error)}`,
142 |       };
143 |     }
144 |   }
145 | 
146 |   // eslint-disable-next-line @typescript-eslint/require-await
147 |   async getClipboardContent(): Promise<WindowsControlResponse> {
148 |     try {
149 |       const outputPath = join(tmpdir(), `mcp-ahk-output-${Date.now()}.txt`);
150 |       const script = `
151 |         content := A_Clipboard
152 |         FileAppend(content, "${outputPath}")
153 |         ExitApp
154 |       `;
155 | 
156 |       this.executeScriptWithOutput(script, outputPath);
157 | 
158 |       try {
159 |         const content = readFileSync(outputPath, 'utf8');
160 |         return {
161 |           success: true,
162 |           message: 'Retrieved clipboard content',
163 |           data: { text: content },
164 |         };
165 |       } finally {
166 |         try {
167 |           unlinkSync(outputPath);
168 |         } catch {
169 |           // Ignore cleanup errors
170 |         }
171 |       }
172 |     } catch (error) {
173 |       return {
174 |         success: false,
175 |         message: `Failed to read from clipboard: ${error instanceof Error ? error.message : String(error)}`,
176 |       };
177 |     }
178 |   }
179 | 
180 |   // eslint-disable-next-line @typescript-eslint/require-await
181 |   async clearClipboard(): Promise<WindowsControlResponse> {
182 |     try {
183 |       const script = `
184 |         A_Clipboard := ""
185 |         ExitApp
186 |       `;
187 | 
188 |       this.executeScript(script);
189 | 
190 |       return {
191 |         success: true,
192 |         message: 'Clipboard cleared',
193 |       };
194 |     } catch (error) {
195 |       return {
196 |         success: false,
197 |         message: `Failed to clear clipboard: ${error instanceof Error ? error.message : String(error)}`,
198 |       };
199 |     }
200 |   }
201 | }
202 | 
```

--------------------------------------------------------------------------------
/src/providers/autohotkey/keyboard.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { execSync } from 'child_process';
  2 | import { writeFileSync, unlinkSync } from 'fs';
  3 | import { tmpdir } from 'os';
  4 | import { join } from 'path';
  5 | import { KeyboardInput, KeyCombination, KeyHoldOperation } from '../../types/common.js';
  6 | import { WindowsControlResponse } from '../../types/responses.js';
  7 | import { KeyboardAutomation } from '../../interfaces/automation.js';
  8 | import {
  9 |   MAX_TEXT_LENGTH,
 10 |   KeySchema,
 11 |   KeyCombinationSchema,
 12 |   KeyHoldOperationSchema,
 13 | } from '../../tools/validation.zod.js';
 14 | import { getAutoHotkeyPath } from './utils.js';
 15 | 
 16 | /**
 17 |  * AutoHotkey implementation of the KeyboardAutomation interface
 18 |  */
 19 | export class AutoHotkeyKeyboardAutomation implements KeyboardAutomation {
 20 |   /**
 21 |    * Execute an AutoHotkey script
 22 |    */
 23 |   private executeScript(script: string): void {
 24 |     const scriptPath = join(tmpdir(), `mcp-ahk-${Date.now()}.ahk`);
 25 | 
 26 |     try {
 27 |       // Write the script to a temporary file
 28 |       writeFileSync(scriptPath, script, 'utf8');
 29 | 
 30 |       // Execute the script with AutoHotkey v2
 31 |       const autohotkeyPath = getAutoHotkeyPath();
 32 |       execSync(`"${autohotkeyPath}" "${scriptPath}"`, { stdio: 'pipe' });
 33 |     } finally {
 34 |       // Clean up the temporary script file
 35 |       try {
 36 |         unlinkSync(scriptPath);
 37 |       } catch {
 38 |         // Ignore cleanup errors
 39 |       }
 40 |     }
 41 |   }
 42 | 
 43 |   /**
 44 |    * Convert key name to AutoHotkey format
 45 |    */
 46 |   private formatKey(key: string): string {
 47 |     const keyMap: Record<string, string> = {
 48 |       control: 'Ctrl',
 49 |       ctrl: 'Ctrl',
 50 |       shift: 'Shift',
 51 |       alt: 'Alt',
 52 |       meta: 'LWin',
 53 |       windows: 'LWin',
 54 |       enter: 'Enter',
 55 |       return: 'Enter',
 56 |       escape: 'Escape',
 57 |       esc: 'Escape',
 58 |       backspace: 'Backspace',
 59 |       delete: 'Delete',
 60 |       tab: 'Tab',
 61 |       space: 'Space',
 62 |       up: 'Up',
 63 |       down: 'Down',
 64 |       left: 'Left',
 65 |       right: 'Right',
 66 |       home: 'Home',
 67 |       end: 'End',
 68 |       pageup: 'PgUp',
 69 |       pagedown: 'PgDn',
 70 |       f1: 'F1',
 71 |       f2: 'F2',
 72 |       f3: 'F3',
 73 |       f4: 'F4',
 74 |       f5: 'F5',
 75 |       f6: 'F6',
 76 |       f7: 'F7',
 77 |       f8: 'F8',
 78 |       f9: 'F9',
 79 |       f10: 'F10',
 80 |       f11: 'F11',
 81 |       f12: 'F12',
 82 |     };
 83 | 
 84 |     const lowerKey = key.toLowerCase();
 85 |     return keyMap[lowerKey] || key;
 86 |   }
 87 | 
 88 |   typeText(input: KeyboardInput): WindowsControlResponse {
 89 |     try {
 90 |       // Validate text
 91 |       if (!input.text) {
 92 |         throw new Error('Text is required');
 93 |       }
 94 | 
 95 |       if (input.text.length > MAX_TEXT_LENGTH) {
 96 |         throw new Error(`Text too long: ${input.text.length} characters (max ${MAX_TEXT_LENGTH})`);
 97 |       }
 98 | 
 99 |       // Escape special characters for AutoHotkey
100 |       const escapedText = input.text
101 |         .replace(/\\/g, '\\\\')
102 |         .replace(/"/g, '\\"')
103 |         .replace(/`/g, '``')
104 |         .replace(/{/g, '{{')
105 |         .replace(/}/g, '}}');
106 | 
107 |       const script = `
108 |         SendText("${escapedText}")
109 |         ExitApp
110 |       `;
111 | 
112 |       this.executeScript(script);
113 | 
114 |       return {
115 |         success: true,
116 |         message: `Typed text successfully`,
117 |       };
118 |     } catch (error) {
119 |       return {
120 |         success: false,
121 |         message: `Failed to type text: ${error instanceof Error ? error.message : String(error)}`,
122 |       };
123 |     }
124 |   }
125 | 
126 |   pressKey(key: string): WindowsControlResponse {
127 |     try {
128 |       // Validate key
129 |       KeySchema.parse(key);
130 | 
131 |       const formattedKey = this.formatKey(key);
132 |       const script = `
133 |         Send("{${formattedKey}}")
134 |         ExitApp
135 |       `;
136 | 
137 |       this.executeScript(script);
138 | 
139 |       return {
140 |         success: true,
141 |         message: `Pressed key: ${key}`,
142 |       };
143 |     } catch (error) {
144 |       return {
145 |         success: false,
146 |         message: `Failed to press key: ${error instanceof Error ? error.message : String(error)}`,
147 |       };
148 |     }
149 |   }
150 | 
151 |   // eslint-disable-next-line @typescript-eslint/require-await
152 |   async pressKeyCombination(combination: KeyCombination): Promise<WindowsControlResponse> {
153 |     try {
154 |       // Validate combination
155 |       KeyCombinationSchema.parse(combination);
156 | 
157 |       // Build the key combination string
158 |       const keys = combination.keys.map((key) => this.formatKey(key));
159 |       const comboString = keys.join('+');
160 | 
161 |       const script = `
162 |         Send("{${comboString}}")
163 |         ExitApp
164 |       `;
165 | 
166 |       this.executeScript(script);
167 | 
168 |       return {
169 |         success: true,
170 |         message: `Pressed key combination: ${combination.keys.join('+')}`,
171 |       };
172 |     } catch (error) {
173 |       return {
174 |         success: false,
175 |         message: `Failed to press key combination: ${error instanceof Error ? error.message : String(error)}`,
176 |       };
177 |     }
178 |   }
179 | 
180 |   // eslint-disable-next-line @typescript-eslint/require-await
181 |   async holdKey(operation: KeyHoldOperation): Promise<WindowsControlResponse> {
182 |     try {
183 |       // Validate operation
184 |       KeyHoldOperationSchema.parse(operation);
185 | 
186 |       const formattedKey = this.formatKey(operation.key);
187 |       const script =
188 |         operation.state === 'up'
189 |           ? `
190 |           Send("{${formattedKey} up}")
191 |           ExitApp
192 |         `
193 |           : `
194 |           Send("{${formattedKey} down}")
195 |           ExitApp
196 |         `;
197 | 
198 |       this.executeScript(script);
199 | 
200 |       return {
201 |         success: true,
202 |         message:
203 |           operation.state === 'up'
204 |             ? `Released key: ${operation.key}`
205 |             : `Holding key: ${operation.key}`,
206 |       };
207 |     } catch (error) {
208 |       return {
209 |         success: false,
210 |         message: `Failed to ${operation.state === 'up' ? 'release' : 'hold'} key: ${error instanceof Error ? error.message : String(error)}`,
211 |       };
212 |     }
213 |   }
214 | }
215 | 
```
Page 1/4FirstPrevNextLast