keyspy 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,19 +1,43 @@
1
- # KeySpy 🕵️
1
+ # keyspy 🕵️
2
2
 
3
3
  > A powerful, cross-platform keyboard and mouse event listener for Node.js
4
4
 
5
- KeySpy is a modern, lightweight library that provides global keyboard and mouse event monitoring across Windows, macOS, and Linux. Unlike other solutions, KeySpy uses pre-compiled native binaries and a multi-process architecture for maximum stability and compatibility.
5
+ keyspy is a modern, lightweight library that provides global keyboard and mouse event monitoring across Windows, macOS, and Linux. Unlike other solutions, keyspy uses pre-compiled native binaries and a multi-process architecture for maximum stability and compatibility.
6
+
7
+ ## 🤔 Why keyspy?
8
+
9
+ Choosing the right keyboard listener for your Node.js project can be challenging. Here's how keyspy compares to other popular solutions:
10
+
11
+ | Feature | Electron globalShortcut | IOHook | **keyspy** |
12
+ |---------|-------------------------|---------|------------|
13
+ | **Zero compilation** | ❌ Electron required | ❌ node-gyp required | ✅ Pre-compiled binaries |
14
+ | **Package size** | 🟡 Large (Electron) | 🟡 Medium | ✅ Small (on-demand download) |
15
+ | **System shortcuts** | ✅ Yes | ✅ Yes | ✅ Yes |
16
+ | **Event blocking** | ✅ Yes | ✅ Yes | ✅ Yes |
17
+ | **Cross-platform** | ✅ Yes | ✅ Yes | ✅ Yes |
18
+ | **TypeScript** | ✅ Built-in | 🟡 Community types | ✅ Built-in |
19
+ | **Stability** | ✅ Very stable | 🟡 Can crash Node.js | ✅ Multi-process isolation |
20
+
21
+ ### 🎯 **keyspy Advantages**
22
+
23
+ - **🚀 Zero Setup**: Pre-compiled binaries downloaded automatically, no compilation required
24
+ - **📦 Smaller Package**: Binaries downloaded on-demand (not bundled), reducing package size by ~90%
25
+ - **📱 Universal macOS**: ARM64 + x86_64 universal binaries for all Apple Silicon and Intel Macs
26
+ - **🔄 Modern Stack**: TypeScript, automated testing, and modern development tools
27
+ - **🏗️ Automated Releases**: GitHub Actions handle cross-platform compilation and publishing
28
+ - **🛡️ Stable Architecture**: Multi-process design prevents Node.js crashes
29
+ - **⚡ High Performance**: Optimized native implementations for each platform
30
+
31
+ > **📚 Project Origin**: keyspy is a modernized version of [node-global-key-listener](https://github.com/LaunchMenu/node-global-key-listener), rebuilt with modern tooling and enhanced features.
6
32
 
7
33
  ## ✨ Features
8
34
 
9
- - 🌍 **Cross-platform**: Works on Windows, macOS, and Linux (X11)
10
- - 🚀 **Zero compilation**: Pre-compiled binaries downloaded automatically, no node-gyp required
11
- - 🔒 **System-level capture**: Can intercept system shortcuts like Ctrl+Alt+Delete, Cmd+Space
35
+ - 🌍 **Cross-platform**: Windows, macOS, and Linux (X11) support
36
+ - 🔒 **System-level capture**: Intercept any key combination, including OS shortcuts
12
37
  - 🎯 **Event blocking**: Prevent captured events from reaching other applications
13
- - 📦 **TypeScript ready**: Full TypeScript support with comprehensive type definitions
14
- - 🏗️ **Modern architecture**: Multi-process design for enhanced stability
38
+ - 📦 **TypeScript ready**: Full type definitions included
39
+ - 🛡️ **Stable architecture**: Multi-process design prevents crashes
15
40
  - ⚡ **High performance**: Optimized native implementations for each platform
16
- - 📥 **Smart installation**: Automatically downloads platform-specific binaries from GitHub Releases
17
41
 
18
42
  ## 🔧 Platform Support
19
43
 
@@ -106,89 +130,69 @@ keyspy.kill(); // Removes all listeners and stops the key server
106
130
  ```bash
107
131
  npm install keyspy
108
132
  # or
109
- pnpm add keyspy
110
- # or
111
133
  yarn add keyspy
112
134
  ```
113
135
 
114
- **What happens during installation:**
115
- 1. 📦 Downloads the main package (TypeScript code)
116
- 2. 🔍 Detects your platform (Windows/macOS/Linux) and architecture
117
- 3. 📥 Automatically downloads the appropriate pre-compiled binary from GitHub Releases
118
- 4. ✅ Ready to use immediately!
136
+ The package automatically detects your platform and downloads the appropriate pre-compiled binary during installation. No compilation required!
119
137
 
120
- ## 🤔 Why KeySpy?
121
138
 
122
- Choosing the right keyboard listener for your Node.js project can be challenging. Here's how KeySpy compares to other popular solutions:
123
-
124
- | Feature | Electron globalShortcut | IOHook | **KeySpy** |
125
- |---------|------------------------|--------|------------|
126
- | **Setup Complexity** | Simple | Complex (node-gyp) | **Simple** |
127
- | **System Shortcuts** | ❌ Limited | ✅ Full | **✅ Full** |
128
- | **Event Blocking** | ❌ No | ✅ Yes | **✅ Yes** |
129
- | **Node.js Compatibility** | ❌ Electron only | ⚠️ Version dependent | **✅ All versions** |
130
- | **Compilation Required** | ❌ No | ❌ Yes | **✅ No** |
131
- | **Arbitrary Key Support** | ❌ Limited | ⚠️ Limited | **✅ Full** |
132
- | **Process Architecture** | In-process | In-process | **Multi-process** |
133
-
134
- ### 🎯 **KeySpy Advantages**
135
-
136
- - **🔧 Zero Setup**: Pre-compiled binaries work out of the box
137
- - **🌐 Universal**: Compatible with all Node.js versions (14+)
138
- - **🔒 System-Level**: Capture any key combination, including OS shortcuts
139
- - **🛡️ Stable**: Multi-process architecture prevents crashes
140
- - **📝 TypeScript**: Full type definitions included
141
- - **🎮 Flexible**: Listen to individual keys or complex combinations
142
-
143
- ### ⚠️ **Considerations**
144
-
145
- - **Permissions**: macOS requires Accessibility permissions
146
- - **Antivirus**: Some antivirus software may flag the native binaries
147
- - **Performance**: Small overhead due to inter-process communication
148
139
 
149
140
  ## 🛠️ Development
150
141
 
151
- ### Quick Development Setup
152
-
153
142
  ```bash
154
143
  # Clone and setup
155
144
  git clone https://github.com/teomyth/keyspy.git
156
145
  cd keyspy
157
- pnpm install
146
+ npm install
147
+
148
+ # Build native binary for development
149
+ npm run build:native
158
150
 
159
- # Development workflow
160
- pnpm dev # Watch mode compilation
161
- pnpm build # Production build
162
- pnpm test # Run all tests
163
- pnpm test:manual # Interactive testing
151
+ # Development commands
152
+ npm run dev # TypeScript watch mode
153
+ npm run build # Production build
154
+ npm test # Run all tests
155
+ npm run test:manual # Interactive testing
164
156
  ```
165
157
 
166
158
  ### Building Native Binaries
167
159
 
168
- KeySpy includes pre-compiled binaries, but you can rebuild them if needed:
160
+ For development, you can rebuild the native binaries:
169
161
 
170
162
  ```bash
171
- # Platform-specific builds
172
- pnpm build:win # Windows (requires MinGW)
173
- pnpm build:swift # macOS (requires Xcode)
174
- pnpm build:x11 # Linux (requires X11 dev libraries)
163
+ # Auto-detect your platform
164
+ npm run build:native
165
+
166
+ # Force specific platform
167
+ npm run build:native -- --platform darwin # macOS (requires Xcode)
168
+ npm run build:native -- --platform linux # Linux (requires X11 dev libraries)
169
+ npm run build:native -- --platform win32 # Windows (requires MinGW)
170
+
171
+ # Show detailed build output
172
+ npm run build:native -- --verbose
175
173
  ```
176
174
 
177
175
  ### Code Quality
178
176
 
179
177
  ```bash
180
- pnpm lint # Check code quality with Biome
181
- pnpm lint:fix # Auto-fix issues
182
- pnpm format # Format code
183
- pnpm clean # Remove build artifacts
178
+ npm run lint # Check code quality with Biome
179
+ npm run lint:fix # Auto-fix issues
180
+ npm run format # Format code
181
+ npm run clean # Remove build artifacts
182
+
183
+ # Release management
184
+ npm run release:patch # Bump patch version (1.0.0 → 1.0.1)
185
+ npm run release:minor # Bump minor version (1.0.0 → 1.1.0)
186
+ npm run release:major # Bump major version (1.0.0 → 2.0.0)
187
+ npm run release:dry # Preview release without publishing
184
188
  ```
185
189
 
186
190
  ### Testing
187
191
 
188
192
  ```bash
189
- pnpm test:unit # Unit tests
190
- pnpm test:integration # Integration tests
191
- pnpm test:manual # Manual interactive testing
193
+ npm run test:unit # Unit tests
194
+ npm run test:integration # Integration tests
195
+ npm run test:manual # Manual interactive testing
192
196
  ```
193
197
 
194
198
  ## 📋 API Reference
@@ -236,36 +240,17 @@ interface IGlobalKeyEvent {
236
240
 
237
241
  ## 🔧 Troubleshooting
238
242
 
239
- ### Binary Download Issues
240
-
241
- If the automatic binary download fails during installation:
242
-
243
- ```bash
244
- # Option 1: Manual download from GitHub Releases
245
- # Visit: https://github.com/teomyth/keyspy/releases
246
- # Download the appropriate file for your platform
247
-
248
- # Option 2: Build from source
249
- npm run build:swift # macOS
250
- npm run build:x11 # Linux
251
- npm run build:win # Windows
252
-
253
- # Option 3: Skip download in CI environments
254
- export KEYSPY_SKIP_DOWNLOAD=true
255
- npm install keyspy
256
- ```
243
+ ### Common Issues
257
244
 
258
- ### Platform Support
245
+ 1. **Binary download fails**: Visit [GitHub Releases](https://github.com/teomyth/keyspy/releases) to download manually, or build from source with `npm run build:native`
246
+ 2. **Permission denied**: Make sure the binary has execute permissions (`chmod +x bin/*`)
247
+ 3. **CI environments**: Set `KEYSPY_SKIP_DOWNLOAD=true` to skip binary download
259
248
 
260
- - **macOS**: ARM64 (Apple Silicon) and x64 (Intel) - Universal binary
261
- - **Linux**: x64 only, requires X11
262
- - **Windows**: x64 only
249
+ ### Platform Requirements
263
250
 
264
- ### Common Issues
265
-
266
- 1. **"Binary not found"**: Run the appropriate build command for your platform
267
- 2. **Permission denied**: Make sure the binary has execute permissions (`chmod +x`)
268
- 3. **Network issues**: Check your internet connection and GitHub access
251
+ - **macOS**: macOS 10.14+, supports both Intel and Apple Silicon
252
+ - **Linux**: X11 display server, x64 architecture
253
+ - **Windows**: Windows 10+, x64 architecture
269
254
 
270
255
  ## 🤝 Contributing
271
256
 
@@ -280,8 +265,3 @@ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) f
280
265
  ## 📄 License
281
266
 
282
267
  MIT License - see [LICENSE](LICENSE) file for details.
283
-
284
- ## 🙏 Acknowledgments
285
-
286
- - Original concept inspired by [LaunchMenu](https://github.com/LaunchMenu/LaunchMenu)
287
- - Built with modern tooling: TypeScript, Biome, Jest, and Turbo
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keyspy",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "A cross-platform global keyboard and mouse listener for Node.js",
5
5
  "types": "build/index.d.ts",
6
6
  "main": "build/index.js",
@@ -13,28 +13,29 @@
13
13
  "engines": {
14
14
  "node": ">=14.0.0"
15
15
  },
16
- "packageManager": "pnpm@8.0.0",
17
16
  "scripts": {
18
17
  "dev": "tsc --watch",
19
18
  "build": "tsc",
20
19
  "postinstall": "node scripts/download-binaries.js",
21
- "build:swift": "scripts/compile-swift.sh",
22
- "build:mac": "mkdir -p bin && swiftc src/bin/MacKeyServer/main.swift -o bin/MacKeyServer",
23
- "build:win": ".\\scripts\\compile-win.bat",
24
- "build:x11": "scripts/compile-x11.sh",
20
+ "build:native": "node scripts/build-native.js",
25
21
  "test": "jest",
26
22
  "test:unit": "jest tests/unit",
27
23
  "test:integration": "jest tests/integration",
24
+ "test:coverage": "jest --coverage",
25
+ "test:watch": "jest --watch",
28
26
  "test:manual": "npx ts-node examples/manual-test.ts",
29
27
  "lint": "biome lint .",
30
28
  "lint:fix": "biome lint --write .",
31
29
  "format": "biome format --write .",
32
30
  "format:check": "biome format .",
33
- "clean": "rm -rf build dist coverage test-reports .turbo",
31
+ "clean": "rm -rf build dist coverage test-results .turbo",
34
32
  "prepare": "npm run build",
35
33
  "prepublishOnly": "npm run build && npm run test",
36
34
  "release": "release-it",
37
- "release:dry": "release-it --dry-run"
35
+ "release:dry": "release-it --dry-run",
36
+ "release:patch": "release-it patch",
37
+ "release:minor": "release-it minor",
38
+ "release:major": "release-it major"
38
39
  },
39
40
  "repository": {
40
41
  "type": "git",
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const fs = require('node:fs');
4
- const path = require('node:path');
5
- const https = require('node:https');
6
- const { execSync } = require('node:child_process');
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+ const https = require("node:https");
6
+ const { execSync } = require("node:child_process");
7
7
 
8
- const packageJson = require('../package.json');
8
+ const packageJson = require("../package.json");
9
9
  const version = packageJson.version;
10
10
 
11
11
  // Platform detection
@@ -14,16 +14,16 @@ const arch = process.arch;
14
14
 
15
15
  // Binary mapping
16
16
  const binaryMap = {
17
- 'darwin': {
18
- 'arm64': { file: 'MacKeyServer', archive: 'keyspy-darwin-arm64.tar.gz' },
19
- 'x64': { file: 'MacKeyServer', archive: 'keyspy-darwin-x64.tar.gz' }
17
+ darwin: {
18
+ arm64: { file: "MacKeyServer", archive: "keyspy-darwin-arm64.tar.gz" },
19
+ x64: { file: "MacKeyServer", archive: "keyspy-darwin-x64.tar.gz" },
20
20
  },
21
- 'linux': {
22
- 'x64': { file: 'X11KeyServer', archive: 'keyspy-linux-x64.tar.gz' }
21
+ linux: {
22
+ x64: { file: "X11KeyServer", archive: "keyspy-linux-x64.tar.gz" },
23
+ },
24
+ win32: {
25
+ x64: { file: "WinKeyServer.exe", archive: "keyspy-win32-x64.tar.gz" },
23
26
  },
24
- 'win32': {
25
- 'x64': { file: 'WinKeyServer.exe', archive: 'keyspy-win32-x64.tar.gz' }
26
- }
27
27
  };
28
28
 
29
29
  function log(message) {
@@ -39,56 +39,58 @@ function getPlatformInfo() {
39
39
  if (!platformInfo) {
40
40
  throw new Error(`Unsupported platform: ${platform}`);
41
41
  }
42
-
42
+
43
43
  const archInfo = platformInfo[arch];
44
44
  if (!archInfo) {
45
45
  throw new Error(`Unsupported architecture: ${arch} on ${platform}`);
46
46
  }
47
-
47
+
48
48
  return archInfo;
49
49
  }
50
50
 
51
51
  function downloadFile(url, dest) {
52
52
  return new Promise((resolve, reject) => {
53
53
  log(`Downloading ${url}`);
54
-
54
+
55
55
  const file = fs.createWriteStream(dest);
56
-
57
- https.get(url, (response) => {
58
- if (response.statusCode === 302 || response.statusCode === 301) {
59
- // Handle redirect
60
- return downloadFile(response.headers.location, dest).then(resolve).catch(reject);
61
- }
62
-
63
- if (response.statusCode !== 200) {
64
- reject(new Error(`Download failed with status ${response.statusCode}`));
65
- return;
66
- }
67
-
68
- response.pipe(file);
69
-
70
- file.on('finish', () => {
71
- file.close();
72
- resolve();
73
- });
74
-
75
- file.on('error', (err) => {
76
- fs.unlink(dest, () => {
77
- // Ignore unlink errors - file cleanup is best effort
78
- }); // Delete partial file
79
- reject(err);
80
- });
81
- }).on('error', reject);
56
+
57
+ https
58
+ .get(url, (response) => {
59
+ if (response.statusCode === 302 || response.statusCode === 301) {
60
+ // Handle redirect
61
+ return downloadFile(response.headers.location, dest).then(resolve).catch(reject);
62
+ }
63
+
64
+ if (response.statusCode !== 200) {
65
+ reject(new Error(`Download failed with status ${response.statusCode}`));
66
+ return;
67
+ }
68
+
69
+ response.pipe(file);
70
+
71
+ file.on("finish", () => {
72
+ file.close();
73
+ resolve();
74
+ });
75
+
76
+ file.on("error", (err) => {
77
+ fs.unlink(dest, () => {
78
+ // Ignore unlink errors - file cleanup is best effort
79
+ }); // Delete partial file
80
+ reject(err);
81
+ });
82
+ })
83
+ .on("error", reject);
82
84
  });
83
85
  }
84
86
 
85
87
  function extractTarGz(archivePath, extractDir) {
86
88
  try {
87
89
  // Try using tar command
88
- execSync(`tar -xzf "${archivePath}" -C "${extractDir}"`, { stdio: 'inherit' });
90
+ execSync(`tar -xzf "${archivePath}" -C "${extractDir}"`, { stdio: "inherit" });
89
91
  return true;
90
92
  } catch (_err) {
91
- log('tar command failed, trying alternative extraction...');
93
+ log("tar command failed, trying alternative extraction...");
92
94
  return false;
93
95
  }
94
96
  }
@@ -96,39 +98,39 @@ function extractTarGz(archivePath, extractDir) {
96
98
  async function downloadAndExtract() {
97
99
  try {
98
100
  const { file: binaryFile, archive } = getPlatformInfo();
99
-
101
+
100
102
  // Create bin directory
101
- const binDir = path.join(__dirname, '..', 'bin');
103
+ const binDir = path.join(__dirname, "..", "bin");
102
104
  if (!fs.existsSync(binDir)) {
103
105
  fs.mkdirSync(binDir, { recursive: true });
104
106
  }
105
-
107
+
106
108
  const binaryPath = path.join(binDir, binaryFile);
107
-
109
+
108
110
  // Check if binary already exists
109
111
  if (fs.existsSync(binaryPath)) {
110
112
  log(`Binary already exists: ${binaryPath}`);
111
113
  return;
112
114
  }
113
-
115
+
114
116
  // Download URL
115
117
  const downloadUrl = `https://github.com/teomyth/keyspy/releases/download/v${version}/${archive}`;
116
118
  const archivePath = path.join(binDir, archive);
117
-
119
+
118
120
  log(`Platform: ${platform}-${arch}`);
119
121
  log(`Binary: ${binaryFile}`);
120
-
122
+
121
123
  try {
122
124
  // Download archive
123
125
  await downloadFile(downloadUrl, archivePath);
124
126
  log(`Downloaded: ${archive}`);
125
-
127
+
126
128
  // Extract archive
127
129
  if (extractTarGz(archivePath, binDir)) {
128
130
  log(`Extracted: ${archive}`);
129
-
131
+
130
132
  // Make binary executable (Unix systems)
131
- if (platform !== 'win32') {
133
+ if (platform !== "win32") {
132
134
  try {
133
135
  fs.chmodSync(binaryPath, 0o755);
134
136
  log(`Made executable: ${binaryFile}`);
@@ -136,43 +138,41 @@ async function downloadAndExtract() {
136
138
  log(`Warning: Could not make binary executable: ${err.message}`);
137
139
  }
138
140
  }
139
-
141
+
140
142
  // Clean up archive
141
143
  fs.unlinkSync(archivePath);
142
144
  log(`Cleaned up: ${archive}`);
143
-
145
+
144
146
  log(`✅ Successfully installed binary: ${binaryFile}`);
145
147
  } else {
146
- throw new Error('Failed to extract archive');
148
+ throw new Error("Failed to extract archive");
147
149
  }
148
-
149
150
  } catch (downloadErr) {
150
151
  error(`Failed to download binary: ${downloadErr.message}`);
151
- log('');
152
- log('📝 Manual installation options:');
153
- log('1. Download from: https://github.com/teomyth/keyspy/releases');
154
- log('2. Build from source:');
155
- log(' - macOS: npm run build:swift');
156
- log(' - Linux: npm run build:x11');
157
- log(' - Windows: npm run build:win');
158
- log('');
159
- log('⚠️ KeySpy will not work without the platform-specific binary.');
160
-
152
+ log("");
153
+ log("📝 Manual installation options:");
154
+ log("1. Download from: https://github.com/teomyth/keyspy/releases");
155
+ log("2. Build from source:");
156
+ log(" - macOS: npm run build:swift");
157
+ log(" - Linux: npm run build:x11");
158
+ log(" - Windows: npm run build:win");
159
+ log("");
160
+ log("⚠️ keyspy will not work without the platform-specific binary.");
161
+
161
162
  // Don't fail the installation, just warn
162
163
  process.exit(0);
163
164
  }
164
-
165
165
  } catch (err) {
166
166
  error(err.message);
167
- log('');
168
- log('📋 Supported platforms:');
169
- log('- macOS (ARM64, x64)');
170
- log('- Linux (x64)');
171
- log('- Windows (x64)');
172
- log('');
173
- log('If your platform should be supported, please report this issue:');
174
- log('https://github.com/teomyth/keyspy/issues');
175
-
167
+ log("");
168
+ log("📋 Supported platforms:");
169
+ log("- macOS (ARM64, x64)");
170
+ log("- Linux (x64)");
171
+ log("- Windows (x64)");
172
+ log("");
173
+ log("If your platform should be supported, please report this issue:");
174
+ log("https://github.com/teomyth/keyspy/issues");
175
+
176
176
  // Don't fail the installation for unsupported platforms
177
177
  process.exit(0);
178
178
  }
@@ -180,7 +180,7 @@ async function downloadAndExtract() {
180
180
 
181
181
  // Skip download in CI environments or if explicitly disabled
182
182
  if (process.env.CI || process.env.KEYSPY_SKIP_DOWNLOAD) {
183
- log('Skipping binary download (CI environment or explicitly disabled)');
183
+ log("Skipping binary download (CI environment or explicitly disabled)");
184
184
  process.exit(0);
185
185
  }
186
186